PluginProbe
TablePress – Tables in WordPress made easy / 1.14
TablePress – Tables in WordPress made easy v1.14
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.14, at controllers/controller-frontend.php

912 lines 36.9 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 Reloaded 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'] = (int) $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 $script_type_attr = current_theme_supports( 'html5', 'script' ) ? '' : ' type="text/javascript"';
401
402 $js_wrapper = <<<'JS'
403 <script%3$s>
404 jQuery(function($){
405 %1$s%2$s
406 });
407 </script>
408 JS;
409 /**
410 * Filter the script/jQuery wrapper code for the DataTables commands calls.
411 *
412 * @since 1.14.0
413 *
414 * @param string $js_wrapper Default script/jQuery wrapper code for the DataTables commands calls.
415 */
416 $js_wrapper = apply_filters( 'tablepress_all_datatables_commands_wrapper', $js_wrapper );
417 printf( $js_wrapper, $datatables_strings, $commands, $script_type_attr );
418 }
419
420 /**
421 * Handle Shortcode [table id=<ID> /] in `the_content()`.
422 *
423 * @since 1.0.0
424 *
425 * @param array $shortcode_atts List of attributes that where included in the Shortcode.
426 * @return string Resulting HTML code for the table with the ID <ID>.
427 */
428 public function shortcode_table( $shortcode_atts ) {
429 // For empty Shortcodes like [table] or [table /], an empty string is passed, see WP Core #26927.
430 $shortcode_atts = (array) $shortcode_atts;
431
432 $_render = TablePress::load_class( 'TablePress_Render', 'class-render.php', 'classes' );
433
434 $default_shortcode_atts = $_render->get_default_render_options();
435 /**
436 * Filter the available/default attributes for the [table] Shortcode.
437 *
438 * @since 1.0.0
439 *
440 * @param array $default_shortcode_atts The [table] Shortcode default attributes.
441 */
442 $default_shortcode_atts = apply_filters( 'tablepress_shortcode_table_default_shortcode_atts', $default_shortcode_atts );
443 // Parse Shortcode attributes, only allow those that are specified.
444 $shortcode_atts = shortcode_atts( $default_shortcode_atts, $shortcode_atts ); // Optional third argument left out on purpose. Use filter in the next line instead.
445 /**
446 * Filter the attributes that were passed to the [table] Shortcode.
447 *
448 * @since 1.0.0
449 *
450 * @param array $shortcode_atts The attributes passed to the [table] Shortcode.
451 */
452 $shortcode_atts = apply_filters( 'tablepress_shortcode_table_shortcode_atts', $shortcode_atts );
453
454 // Check, if a table with the given ID exists.
455 $table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $shortcode_atts['id'] );
456 if ( ! TablePress::$model_table->table_exists( $table_id ) ) {
457 $message = "[table &#8220;{$table_id}&#8221; not found /]<br />\n";
458 /**
459 * Filter the "Table not found" message.
460 *
461 * @since 1.0.0
462 *
463 * @param string $message The "Table not found" message.
464 * @param string $table_id The current table ID.
465 */
466 $message = apply_filters( 'tablepress_table_not_found_message', $message, $table_id );
467 return $message;
468 }
469
470 // Load table, with table data, options, and visibility settings.
471 $table = TablePress::$model_table->load( $table_id, true, true );
472 if ( is_wp_error( $table ) ) {
473 $message = "[table &#8220;{$table_id}&#8221; could not be loaded /]<br />\n";
474 /**
475 * Filter the "Table could not be loaded" message.
476 *
477 * @since 1.0.0
478 *
479 * @param string $message The "Table could not be loaded" message.
480 * @param string $table_id The current table ID.
481 * @param WP_Error $table The error object for the table.
482 */
483 $message = apply_filters( 'tablepress_table_load_error_message', $message, $table_id, $table );
484 return $message;
485 }
486 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
487 $message = "<div>Attention: The internal data of table &#8220;{$table_id}&#8221; is corrupted!</div>";
488 /**
489 * Filter the "Table data is corrupted" message.
490 *
491 * @since 1.0.0
492 *
493 * @param string $message The "Table data is corrupted" message.
494 * @param string $table_id The current table ID.
495 * @param string $json_error The JSON error with information about the corrupted table.
496 */
497 $message = apply_filters( 'tablepress_table_corrupted_message', $message, $table_id, $table['json_error'] );
498 return $message;
499 }
500
501 /**
502 * Filter whether the "datatables_custom_commands" Shortcode parameter is disabled.
503 *
504 * By default, the "datatables_custom_commands" Shortcode parameter is disabled for security reasons.
505 *
506 * @since 1.0.0
507 *
508 * @param bool $disable Whether to disable the "datatables_custom_commands" Shortcode parameter. Default true.
509 */
510 if ( ! is_null( $shortcode_atts['datatables_custom_commands'] ) && apply_filters( 'tablepress_disable_custom_commands_shortcode_parameter', true ) ) {
511 $shortcode_atts['datatables_custom_commands'] = null;
512 }
513
514 // Determine options to use (if set in Shortcode, use those, otherwise use stored options, from the "Edit" screen).
515 $render_options = array();
516 foreach ( $shortcode_atts as $key => $value ) {
517 // We have to check this, because strings 'true' or 'false' are not recognized as boolean!
518 if ( is_string( $value ) && 'true' === strtolower( $value ) ) {
519 $render_options[ $key ] = true;
520 } elseif ( is_string( $value ) && 'false' === strtolower( $value ) ) {
521 $render_options[ $key ] = false;
522 } elseif ( is_null( $value ) && isset( $table['options'][ $key ] ) ) {
523 $render_options[ $key ] = $table['options'][ $key ];
524 } else {
525 $render_options[ $key ] = $value;
526 }
527 }
528
529 // Generate unique HTML ID, depending on how often this table has already been shown on this page.
530 if ( ! isset( $this->shown_tables[ $table_id ] ) ) {
531 $this->shown_tables[ $table_id ] = array(
532 'count' => 0,
533 'instances' => array(),
534 );
535 }
536 $this->shown_tables[ $table_id ]['count']++;
537 $count = $this->shown_tables[ $table_id ]['count'];
538 $render_options['html_id'] = "tablepress-{$table_id}";
539 if ( $count > 1 ) {
540 $render_options['html_id'] .= "-no-{$count}";
541 }
542 /**
543 * Filter the ID of the table HTML element.
544 *
545 * @since 1.0.0
546 *
547 * @param string $html_id The ID of the table HTML element.
548 * @param string $table_id The current table ID.
549 * @param string $count Number of copies of the table with this table ID on the page.
550 */
551 $render_options['html_id'] = apply_filters( 'tablepress_html_id', $render_options['html_id'], $table_id, $count );
552
553 // Generate the "Edit Table" link.
554 $render_options['edit_table_url'] = '';
555 /**
556 * Filter whether the "Edit" link below the table shall be shown.
557 *
558 * The "Edit" link is only shown to logged-in users who possess the necessary capability to edit the table.
559 *
560 * @since 1.0.0
561 *
562 * @param bool $show Whether to show the "Edit" link below the table. Default true.
563 * @param string $table_id The current table ID.
564 */
565 if ( is_user_logged_in() && apply_filters( 'tablepress_edit_link_below_table', true, $table['id'] ) && current_user_can( 'tablepress_edit_table', $table['id'] ) ) {
566 $render_options['edit_table_url'] = TablePress::url( array( 'action' => 'edit', 'table_id' => $table['id'] ) );
567 }
568
569 /**
570 * Filter the render options for the table.
571 *
572 * The render options are determined from the settings on a table's "Edit" screen and the Shortcode parameters.
573 *
574 * @since 1.0.0
575 *
576 * @param array $render_options The render options for the table.
577 * @param array $table The current table.
578 */
579 $render_options = apply_filters( 'tablepress_table_render_options', $render_options, $table );
580
581 // 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.
582 if ( $render_options['use_datatables'] && $render_options['table_head'] && count( $table['data'] ) > 1 ) {
583 // Get options for the DataTables JavaScript library from the table's render options.
584 $js_options = array();
585 foreach ( array(
586 'alternating_row_colors',
587 'datatables_sort',
588 'datatables_paginate',
589 'datatables_paginate',
590 'datatables_paginate_entries',
591 'datatables_lengthchange',
592 'datatables_filter',
593 'datatables_info',
594 'datatables_scrollx',
595 'datatables_scrolly',
596 'datatables_locale',
597 'datatables_custom_commands',
598 ) as $option ) {
599 $js_options[ $option ] = $render_options[ $option ];
600 }
601 /**
602 * Filter the JavaScript options for the table.
603 *
604 * The JavaScript options are determined from the settings on a table's "Edit" screen and the Shortcode parameters.
605 * They are part of the render options and can be overwritten with Shortcode parameters.
606 *
607 * @since 1.0.0
608 *
609 * @param array $js_options The JavaScript options for the table.
610 * @param string $table_id The current table ID.
611 * @param array $render_options The render options for the table.
612 */
613 $js_options = apply_filters( 'tablepress_table_js_options', $js_options, $table_id, $render_options );
614 $this->shown_tables[ $table_id ]['instances'][ $render_options['html_id'] ] = $js_options;
615 $this->_enqueue_datatables();
616 }
617
618 // Check if table output shall and can be loaded from the transient cache, otherwise generate the output.
619 if ( $render_options['cache_table_output'] && ! is_user_logged_in() ) {
620 // Hash the Render Options array to get a unique cache identifier.
621 $table_hash = md5( wp_json_encode( $render_options, TABLEPRESS_JSON_OPTIONS ) );
622 $transient_name = 'tablepress_' . $table_hash; // Attention: This string must not be longer than 45 characters!
623 $output = get_transient( $transient_name );
624 if ( false === $output || '' === $output ) {
625 // Render/generate the table HTML, as it was not found in the cache.
626 $_render->set_input( $table, $render_options );
627 $output = $_render->get_output();
628 // Save render output in a transient, set cache timeout to 24 hours.
629 set_transient( $transient_name, $output, DAY_IN_SECONDS );
630 // Update output caches list transient (necessary for cache invalidation upon table saving).
631 $caches_list_transient_name = 'tablepress_c_' . md5( $table_id );
632 $caches_list = get_transient( $caches_list_transient_name );
633 if ( false === $caches_list ) {
634 $caches_list = array();
635 } else {
636 $caches_list = (array) json_decode( $caches_list, true );
637 }
638 if ( ! in_array( $transient_name, $caches_list, true ) ) {
639 $caches_list[] = $transient_name;
640 }
641 set_transient( $caches_list_transient_name, wp_json_encode( $caches_list, TABLEPRESS_JSON_OPTIONS ), 2 * DAY_IN_SECONDS );
642 } else {
643 /**
644 * Filter the cache hit comment message.
645 *
646 * @since 1.0.0
647 *
648 * @param string $comment The cache hit comment message.
649 */
650 $output .= apply_filters( 'tablepress_cache_hit_comment', "<!-- #{$render_options['html_id']} from cache -->" );
651 }
652 } else {
653 // Render/generate the table HTML, as no cache is to be used.
654 $_render->set_input( $table, $render_options );
655 $output = $_render->get_output();
656 }
657
658 // Maybe print a list of used render options.
659 if ( $render_options['shortcode_debug'] && is_user_logged_in() ) {
660 $output .= '<pre>' . var_export( $render_options, true ) . '</pre>';
661 }
662
663 return $output;
664 }
665
666 /**
667 * Handle Shortcode [table-info id=<ID> field=<name> /] in the_content().
668 *
669 * @since 1.0.0
670 *
671 * @param array $shortcode_atts List of attributes that where included in the Shortcode.
672 * @return string Text that replaces the Shortcode (error message or asked-for information).
673 */
674 public function shortcode_table_info( $shortcode_atts ) {
675 // For empty Shortcodes like [table-info] or [table-info /], an empty string is passed, see Core #26927.
676 $shortcode_atts = (array) $shortcode_atts;
677
678 // Parse Shortcode attributes, only allow those that are specified.
679 $default_shortcode_atts = array(
680 'id' => '',
681 'field' => '',
682 'format' => '',
683 );
684 /**
685 * Filter the available/default attributes for the [table-info] Shortcode.
686 *
687 * @since 1.0.0
688 *
689 * @param array $default_shortcode_atts The [table-info] Shortcode default attributes.
690 */
691 $default_shortcode_atts = apply_filters( 'tablepress_shortcode_table_info_default_shortcode_atts', $default_shortcode_atts );
692 $shortcode_atts = shortcode_atts( $default_shortcode_atts, $shortcode_atts ); // Optional third argument left out on purpose. Use filter in the next line instead.
693 /**
694 * Filter the attributes that were passed to the [table-info] Shortcode.
695 *
696 * @since 1.0.0
697 *
698 * @param array $shortcode_atts The attributes passed to the [table-info] Shortcode.
699 */
700 $shortcode_atts = apply_filters( 'tablepress_shortcode_table_info_shortcode_atts', $shortcode_atts );
701
702 /**
703 * Filter whether the output of the [table-info] Shortcode is overwritten/short-circuited.
704 *
705 * @since 1.0.0
706 *
707 * @param bool|string $overwrite Whether the [table-info] output is overwritten. Return false for the regular content, and a string to overwrite the output.
708 * @param array $shortcode_atts The attributes passed to the [table-info] Shortcode.
709 */
710 $overwrite = apply_filters( 'tablepress_shortcode_table_info_overwrite', false, $shortcode_atts );
711 if ( $overwrite ) {
712 return $overwrite;
713 }
714
715 // Check, if a table with the given ID exists.
716 $table_id = preg_replace( '/[^a-zA-Z0-9_-]/', '', $shortcode_atts['id'] );
717 if ( ! TablePress::$model_table->table_exists( $table_id ) ) {
718 $message = "[table &#8220;{$table_id}&#8221; not found /]<br />\n";
719 /** This filter is documented in controllers/controller-frontend.php */
720 $message = apply_filters( 'tablepress_table_not_found_message', $message, $table_id );
721 return $message;
722 }
723
724 // Load table, with table data, options, and visibility settings.
725 $table = TablePress::$model_table->load( $table_id, true, true );
726 if ( is_wp_error( $table ) ) {
727 $message = "[table &#8220;{$table_id}&#8221; could not be loaded /]<br />\n";
728 /** This filter is documented in controllers/controller-frontend.php */
729 $message = apply_filters( 'tablepress_table_load_error_message', $message, $table_id, $table );
730 return $message;
731 }
732
733 $field = preg_replace( '/[^a-z_]/', '', strtolower( $shortcode_atts['field'] ) );
734 $format = preg_replace( '/[^a-z]/', '', strtolower( $shortcode_atts['format'] ) );
735
736 // Generate output, depending on what information (field) was asked for.
737 switch ( $field ) {
738 case 'name':
739 case 'description':
740 $output = $table[ $field ];
741 break;
742 case 'last_modified':
743 switch ( $format ) {
744 case 'raw':
745 case 'mysql':
746 $output = $table['last_modified'];
747 break;
748 case 'human':
749 $modified_timestamp = date_create( $table['last_modified'], wp_timezone() );
750 $modified_timestamp = $modified_timestamp->getTimestamp();
751 $current_timestamp = time();
752 $time_diff = $current_timestamp - $modified_timestamp;
753 // Time difference is only shown up to one week.
754 if ( $time_diff >= 0 && $time_diff < WEEK_IN_SECONDS ) {
755 $output = sprintf( __( '%s ago', 'default' ), human_time_diff( $modified_timestamp, $current_timestamp ) );
756 } else {
757 $output = TablePress::format_datetime( $table['last_modified'], '<br />' );
758 }
759 break;
760 case 'date':
761 $output = TablePress::format_datetime( $table['last_modified'], get_option( 'date_format' ) );
762 break;
763 case 'time':
764 $output = TablePress::format_datetime( $table['last_modified'], get_option( 'time_format' ) );
765 break;
766 default:
767 $output = TablePress::format_datetime( $table['last_modified'] );
768 break;
769 }
770 break;
771 case 'last_editor':
772 $output = TablePress::get_user_display_name( $table['options']['last_editor'] );
773 break;
774 case 'author':
775 $output = TablePress::get_user_display_name( $table['author'] );
776 break;
777 case 'number_rows':
778 $output = count( $table['data'] );
779 if ( 'raw' !== $format ) {
780 if ( $table['options']['table_head'] ) {
781 $output = $output - 1;
782 }
783 if ( $table['options']['table_foot'] ) {
784 $output = $output - 1;
785 }
786 }
787 break;
788 case 'number_columns':
789 $output = count( $table['data'][0] );
790 break;
791 default:
792 $output = "[table-info field &#8220;{$field}&#8221; not found in table &#8220;{$table_id}&#8221; /]<br />\n";
793 /**
794 * Filter the "table info field not found" message.
795 *
796 * @since 1.0.0
797 *
798 * @param string $output The "table info field not found" message.
799 * @param array $table The current table ID.
800 * @param string $field The field that was not found.
801 * @param string $format The return format for the field.
802 */
803 $output = apply_filters( 'tablepress_table_info_not_found_message', $output, $table, $field, $format );
804 }
805
806 /**
807 * Filter the output of the [table-info] Shortcode.
808 *
809 * @since 1.0.0
810 *
811 * @param string $output The output of the [table-info] Shortcode.
812 * @param array $table The current table.
813 * @param array $shortcode_atts The attributes passed to the [table-info] Shortcode.
814 */
815 $output = apply_filters( 'tablepress_shortcode_table_info_output', $output, $table, $shortcode_atts );
816 return $output;
817 }
818
819 /**
820 * Expand WP Search to also find posts and pages that have a search term in a table that is shown in them.
821 *
822 * This is done by looping through all search terms and TablePress tables and searching there for the search term,
823 * 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
824 * Shortcode for one of these tables in their content.
825 *
826 * @since 1.0.0
827 *
828 * @global wpdb $wpdb WordPress database abstraction object.
829 *
830 * @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.
831 * @return string Eventually extended SQL "WHERE" clause, to also find posts/pages with Shortcodes in them.
832 */
833 public function posts_search_filter( $search_sql ) {
834 global $wpdb;
835
836 if ( ! is_search() || ! is_main_query() ) {
837 return $search_sql;
838 }
839
840 // Get variable that contains all search terms, parsed from $_GET['s'] by WP.
841 $search_terms = get_query_var( 'search_terms' );
842 if ( empty( $search_terms ) || ! is_array( $search_terms ) ) {
843 return $search_sql;
844 }
845
846 // 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.
847 $table_ids = TablePress::$model_table->load_all( true, false );
848 // Array of all search words that were found, and the table IDs where they were found.
849 $query_result = array();
850
851 foreach ( $table_ids as $table_id ) {
852 // Load table, with table data, options, and visibility settings.
853 $table = TablePress::$model_table->load( $table_id, true, true );
854
855 if ( isset( $table['is_corrupted'] ) && $table['is_corrupted'] ) {
856 // Do not search in corrupted tables.
857 continue;
858 }
859
860 foreach ( $search_terms as $search_term ) {
861 if ( ( $table['options']['print_name'] && false !== stripos( $table['name'], $search_term ) )
862 || ( $table['options']['print_description'] && false !== stripos( $table['description'], $search_term ) ) ) {
863 // Found the search term in the name or description (and they are shown).
864 $query_result[ $search_term ][] = $table_id; // Add table ID to result list.
865 // No need to continue searching this search term in this table.
866 continue;
867 }
868
869 // Search search term in visible table cells (without taking Shortcode parameters into account!).
870 foreach ( $table['data'] as $row_idx => $table_row ) {
871 if ( 0 === $table['visibility']['rows'][ $row_idx ] ) {
872 // Row is hidden, so don't search in it.
873 continue;
874 }
875 foreach ( $table_row as $col_idx => $table_cell ) {
876 if ( 0 === $table['visibility']['columns'][ $col_idx ] ) {
877 // Column is hidden, so don't search in it.
878 continue;
879 }
880 // @TODO: Cells are not evaluated here, so math formulas are searched.
881 if ( false !== stripos( $table_cell, $search_term ) ) {
882 // Found the search term in the cell content.
883 $query_result[ $search_term ][] = $table_id; // Add table ID to result list
884 // No need to continue searching this search term in this table.
885 continue 3;
886 }
887 }
888 }
889 }
890 }
891
892 // For all found table IDs for each search term, add additional OR statement to the SQL "WHERE" clause.
893
894 // If $_GET['exact'] is set, WordPress doesn't use % in SQL LIKE clauses.
895 $exact = get_query_var( 'exact' );
896 $n = ( empty( $exact ) ) ? '%' : '';
897 $search_sql = $wpdb->remove_placeholder_escape( $search_sql );
898 foreach ( $query_result as $search_term => $table_ids ) {
899 $search_term = esc_sql( $wpdb->esc_like( $search_term ) );
900 $old_or = "OR ({$wpdb->posts}.post_content LIKE '{$n}{$search_term}{$n}')";
901 $table_ids = implode( '|', $table_ids );
902 $regexp = '\\\\[' . TablePress::$shortcode . ' id=(["\\\']?)(' . $table_ids . ')([\]"\\\' /])'; // ' needs to be single escaped, [ double escaped (with \\) in mySQL
903 $new_or = $old_or . " OR ({$wpdb->posts}.post_content REGEXP '{$regexp}')";
904 $search_sql = str_replace( $old_or, $new_or, $search_sql );
905 }
906 $search_sql = $wpdb->add_placeholder_escape( $search_sql );
907
908 return $search_sql;
909 }
910
911 } // class TablePress_Frontend_Controller
912