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 / classes / class-render.php

class-render.php in TablePress – Tables in WordPress made easy 1.14, at classes/class-render.php

955 lines 34.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * TablePress Rendering Class
4 *
5 * @package TablePress
6 * @subpackage Rendering
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 * TablePress Rendering Class
16 * @package TablePress
17 * @subpackage Rendering
18 * @author Tobias Bäthge
19 * @since 1.0.0
20 */
21 class TablePress_Render {
22
23 /**
24 * Table data that is rendered.
25 *
26 * @since 1.0.0
27 * @var array
28 */
29 protected $table = array();
30
31 /**
32 * Table options that influence the output result.
33 *
34 * @since 1.0.0
35 * @var array
36 */
37 protected $render_options = array();
38
39 /**
40 * Rendered HTML of the table.
41 *
42 * @since 1.0.0
43 * @var string
44 */
45 protected $output;
46
47 /**
48 * Trigger words for colspan, rowspan, or the combination of both.
49 *
50 * @since 1.0.0
51 * @var array
52 */
53 protected $span_trigger = array(
54 'colspan' => '#colspan#',
55 'rowspan' => '#rowspan#',
56 'span' => '#span#',
57 );
58
59 /**
60 * Buffer to store the counts of rowspan per column, initialized in _render_table().
61 *
62 * @since 1.0.0
63 * @var array
64 */
65 protected $rowspan = array();
66
67 /**
68 * Buffer to store the counts of colspan per row, initialized in _render_table().
69 *
70 * @since 1.0.0
71 * @var array
72 */
73 protected $colspan = array();
74
75 /**
76 * Index of the last row of the visible data in the table, set in _render_table().
77 *
78 * @since 1.0.0
79 * @var int
80 */
81 protected $last_row_idx;
82
83 /**
84 * Index of the last column of the visible data in the table, set in _render_table().
85 *
86 * @since 1.0.0
87 * @var int
88 */
89 protected $last_column_idx;
90
91 /**
92 * Class constructor.
93 *
94 * @since 1.0.0
95 */
96 public function __construct() {
97 // Unused.
98 }
99
100 /**
101 * Set the table (data, options, visibility, ...) that is to be rendered.
102 *
103 * @since 1.0.0
104 *
105 * @param array $table Table to be rendered.
106 * @param array $render_options Options for rendering, from both "Edit" screen and Shortcode.
107 */
108 public function set_input( array $table, array $render_options ) {
109 $this->table = $table;
110 $this->render_options = $render_options;
111 /**
112 * Filter the table before the render process.
113 *
114 * @since 1.0.0
115 *
116 * @param array $table The table.
117 * @param array $render_options The render options for the table.
118 */
119 $this->table = apply_filters( 'tablepress_table_raw_render_data', $this->table, $this->render_options );
120 }
121
122 /**
123 * Process the table rendering and return the HTML output.
124 *
125 * @since 1.0.0
126 *
127 * @return string HTML of the rendered table (or an error message).
128 */
129 public function get_output() {
130 // Evaluate math expressions/formulas.
131 $this->_evaluate_table_data();
132 // Remove hidden rows and columns.
133 $this->_prepare_render_data();
134 // Generate HTML output.
135 $this->_render_table();
136 return $this->output;
137 }
138
139 /**
140 * Loop through the table to evaluate math expressions/formulas.
141 *
142 * @since 1.0.0
143 */
144 protected function _evaluate_table_data() {
145 $orig_table = $this->table;
146
147 $formula_evaluator = TablePress::load_class( 'TablePress_Evaluate', 'class-evaluate.php', 'classes' );
148 $this->table['data'] = $formula_evaluator->evaluate_table_data( $this->table['data'], $this->table['id'] );
149 /**
150 * Filter the table after evaluating formulas in the table.
151 *
152 * @since 1.0.0
153 *
154 * @param array $table The table with evaluated formulas.
155 * @param array $orig_table The table with unevaluated formulas.
156 * @param array $render_options The render options for the table.
157 */
158 $this->table = apply_filters( 'tablepress_table_evaluate_data', $this->table, $orig_table, $this->render_options );
159 }
160
161 /**
162 * Remove all cells from the data set that shall not be rendered, because they are hidden.
163 *
164 * @since 1.0.0
165 */
166 protected function _prepare_render_data() {
167 $orig_table = $this->table;
168
169 $num_rows = count( $this->table['data'] );
170 $num_columns = ( $num_rows > 0 ) ? count( $this->table['data'][0] ) : 0;
171
172 // Evaluate show/hide_rows/columns parameters.
173 $actions = array( 'show', 'hide' );
174 $elements = array( 'rows', 'columns' );
175 foreach ( $actions as $action ) {
176 foreach ( $elements as $element ) {
177 if ( empty( $this->render_options[ "{$action}_{$element}" ] ) ) {
178 $this->render_options[ "{$action}_{$element}" ] = array();
179 continue;
180 }
181
182 // Add all rows/columns to array if "all" value set for one of the four parameters.
183 if ( 'all' === $this->render_options[ "{$action}_{$element}" ] ) {
184 $this->render_options[ "{$action}_{$element}" ] = range( 0, ${'num_' . $element} - 1 );
185 continue;
186 }
187
188 // We have a list of rows/columns (possibly with ranges in it).
189 $this->render_options[ "{$action}_{$element}" ] = explode( ',', $this->render_options[ "{$action}_{$element}" ] );
190 // Support for ranges like 3-6 or A-BA.
191 $range_cells = array();
192 foreach ( $this->render_options[ "{$action}_{$element}" ] as $key => $value ) {
193 $range_dash = strpos( $value, '-' );
194 if ( false !== $range_dash ) {
195 unset( $this->render_options[ "{$action}_{$element}" ][ $key ] );
196 $start = substr( $value, 0, $range_dash );
197 if ( ! is_numeric( $start ) ) {
198 $start = TablePress::letter_to_number( $start );
199 }
200 $end = substr( $value, $range_dash + 1 );
201 if ( ! is_numeric( $end ) ) {
202 $end = TablePress::letter_to_number( $end );
203 }
204 $current_range = range( $start, $end );
205 $range_cells = array_merge( $range_cells, $current_range );
206 }
207 }
208 $this->render_options[ "{$action}_{$element}" ] = array_merge( $this->render_options[ "{$action}_{$element}" ], $range_cells );
209 /*
210 * Parse single letters and change from regular numbering to zero-based numbering,
211 * as rows/columns are indexed from 0 internally, but from 1 externally
212 */
213 foreach ( $this->render_options[ "{$action}_{$element}" ] as $key => $value ) {
214 if ( ! is_numeric( $value ) ) {
215 $value = TablePress::letter_to_number( $value );
216 }
217 $this->render_options[ "{$action}_{$element}" ][ $key ] = (int) $value - 1;
218 }
219
220 // Remove duplicate entries and sort the array.
221 $this->render_options[ "{$action}_{$element}" ] = array_unique( $this->render_options[ "{$action}_{$element}" ] );
222 sort( $this->render_options[ "{$action}_{$element}" ], SORT_NUMERIC );
223 }
224 }
225
226 // Load information about hidden rows and columns.
227 // Get indexes of hidden rows (array value of 0).
228 $hidden_rows = array_keys( $this->table['visibility']['rows'], 0, true );
229 $hidden_rows = array_merge( $hidden_rows, $this->render_options['hide_rows'] );
230 $hidden_rows = array_diff( $hidden_rows, $this->render_options['show_rows'] );
231 // Get indexes of hidden columns (array value of 0).
232 $hidden_columns = array_keys( $this->table['visibility']['columns'], 0, true );
233 $hidden_columns = array_merge( $hidden_columns, $this->render_options['hide_columns'] );
234 $hidden_columns = array_merge( array_diff( $hidden_columns, $this->render_options['show_columns'] ) );
235
236 // Remove hidden rows and re-index.
237 foreach ( $hidden_rows as $row_idx ) {
238 unset( $this->table['data'][ $row_idx ] );
239 }
240 $this->table['data'] = array_merge( $this->table['data'] );
241 // Remove hidden columns and re-index.
242 foreach ( $this->table['data'] as $row_idx => $row ) {
243 foreach ( $hidden_columns as $col_idx ) {
244 unset( $row[ $col_idx ] );
245 }
246 $this->table['data'][ $row_idx ] = array_merge( $row );
247 }
248
249 /**
250 * Filter the table after processing the table visibility information.
251 *
252 * @since 1.0.0
253 *
254 * @param array $table The processed table.
255 * @param array $orig_table The unprocessed table.
256 * @param array $render_options The render options for the table.
257 */
258 $this->table = apply_filters( 'tablepress_table_render_data', $this->table, $orig_table, $this->render_options );
259 }
260
261 /**
262 * Generate the HTML output of the table.
263 *
264 * @since 1.0.0
265 */
266 protected function _render_table() {
267 $num_rows = count( $this->table['data'] );
268 $num_columns = ( $num_rows > 0 ) ? count( $this->table['data'][0] ) : 0;
269
270 // Check if there are rows and columns in the table (might not be the case after removing hidden rows/columns!).
271 if ( 0 === $num_rows || 0 === $num_columns ) {
272 $this->output = sprintf( __( '<!-- The table with the ID %s is empty! -->', 'tablepress' ), $this->table['id'] );
273 return;
274 }
275
276 // Counters for spans of rows and columns, init to 1 for each row and column (as that means no span).
277 $this->rowspan = array_fill( 0, $num_columns, 1 );
278 $this->colspan = array_fill( 0, $num_rows, 1 );
279
280 /**
281 * Filter the trigger keywords for "colspan" and "rowspan"
282 *
283 * @since 1.0.0
284 *
285 * @param array $span_trigger The trigger keywords for combining table cells.
286 * @param string $table_id The current table ID.
287 */
288 $this->span_trigger = apply_filters( 'tablepress_span_trigger_keywords', $this->span_trigger, $this->table['id'] );
289
290 // Explode from string to array.
291 $this->render_options['column_widths'] = ( ! empty( $this->render_options['column_widths'] ) ) ? explode( '|', $this->render_options['column_widths'] ) : array();
292 // Make array $this->render_options['column_widths'] have $columns entries.
293 $this->render_options['column_widths'] = array_pad( $this->render_options['column_widths'], $num_columns, '' );
294
295 $output = '';
296
297 if ( $this->render_options['print_name'] ) {
298 /**
299 * Filter the HTML tag that wraps the printed table name.
300 *
301 * @since 1.0.0
302 *
303 * @param string $tag The HTML tag around the table name. Default h2.
304 * @param string $table_id The current table ID.
305 */
306 $name_html_tag = apply_filters( 'tablepress_print_name_html_tag', 'h2', $this->table['id'] );
307
308 $name_attributes = array();
309 if ( ! empty( $this->render_options['html_id'] ) ) {
310 $name_attributes['id'] = "{$this->render_options['html_id']}-name";
311 }
312 /**
313 * Filter the class attribute for the printed table name.
314 *
315 * @since 1.0.0
316 * @deprecated 1.13.0 Use {@see 'tablepress_table_name_tag_attributes'} instead.
317 *
318 * @param string $class The class attribute for the table name that can be used in CSS code.
319 * @param string $table_id The current table ID.
320 */
321 $name_attributes['class'] = apply_filters_deprecated( 'tablepress_print_name_css_class', array( "tablepress-table-name tablepress-table-name-id-{$this->table['id']}", $this->table['id'] ), 'TablePress 1.13.0', 'tablepress_table_name_tag_attributes' );
322 /**
323 * Filter the attributes for the table name (HTML h2 element, by default).
324 *
325 * @since 1.13.0
326 *
327 * @param array $name_attributes The attributes for the table name element.
328 * @param array $table The current table.
329 * @param array $render_options The render options for the table.
330 */
331 $name_attributes = apply_filters( 'tablepress_table_name_tag_attributes', $name_attributes, $this->table, $this->render_options );
332 $name_attributes = $this->_attributes_array_to_string( $name_attributes );
333
334 $print_name_html = "<{$name_html_tag}{$name_attributes}>" . $this->safe_output( $this->table['name'] ) . "</{$name_html_tag}>\n";
335 }
336 if ( $this->render_options['print_description'] ) {
337 /**
338 * Filter the HTML tag that wraps the printed table description.
339 *
340 * @since 1.0.0
341 *
342 * @param string $tag The HTML tag around the table description. Default span.
343 * @param string $table_id The current table ID.
344 */
345 $description_html_tag = apply_filters( 'tablepress_print_description_html_tag', 'span', $this->table['id'] );
346
347 $description_attributes = array();
348 if ( ! empty( $this->render_options['html_id'] ) ) {
349 $description_attributes['id'] = "{$this->render_options['html_id']}-description";
350 }
351 /**
352 * Filter the class attribute for the printed table description.
353 *
354 * @since 1.0.0
355 * @deprecated 1.13.0 Use {@see 'tablepress_table_description_tag_attributes'} instead.
356 *
357 * @param string $class The class attribute for the table description that can be used in CSS code.
358 * @param string $table_id The current table ID.
359 */
360 $description_attributes['class'] = apply_filters_deprecated( 'tablepress_print_description_css_class', array( "tablepress-table-description tablepress-table-description-id-{$this->table['id']}", $this->table['id'] ), 'TablePress 1.13.0', 'tablepress_table_description_tag_attributes' );
361 /**
362 * Filter the attributes for the table description (HTML span element, by default).
363 *
364 * @since 1.13.0
365 *
366 * @param array $description_attributes The attributes for the table description element.
367 * @param array $table The current table.
368 * @param array $render_options The render options for the table.
369 */
370 $description_attributes = apply_filters( 'tablepress_table_description_tag_attributes', $description_attributes, $this->table, $this->render_options );
371 $description_attributes = $this->_attributes_array_to_string( $description_attributes );
372
373 $print_description_html = "<{$description_html_tag}{$description_attributes}>" . $this->safe_output( $this->table['description'] ) . "</{$description_html_tag}>\n";
374 }
375
376 if ( $this->render_options['print_name'] && 'above' === $this->render_options['print_name_position'] ) {
377 $output .= $print_name_html;
378 }
379 if ( $this->render_options['print_description'] && 'above' === $this->render_options['print_description_position'] ) {
380 $output .= $print_description_html;
381 }
382
383 // Deactivate nl2br() for this render process, if "convert_line_breaks" Shortcode parameter is set to false.
384 if ( ! $this->render_options['convert_line_breaks'] ) {
385 add_filter( 'tablepress_apply_nl2br', '__return_false', 9 ); // Priority 9, so that this filter can easily be overwritten at the default priority.
386 }
387
388 $thead = '';
389 $tfoot = '';
390 $tbody = array();
391
392 $this->last_row_idx = $num_rows - 1;
393 $this->last_column_idx = $num_columns - 1;
394 // Loop through rows in reversed order, to search for rowspan trigger keyword.
395 for ( $row_idx = $this->last_row_idx; $row_idx >= 0; $row_idx-- ) {
396 // Last row, need to check for footer (but only if at least two rows).
397 if ( $this->last_row_idx === $row_idx && $this->render_options['table_foot'] && $num_rows > 1 ) {
398 $tfoot = $this->_render_row( $row_idx, 'th' );
399 continue;
400 }
401 // First row, need to check for head (but only if at least two rows).
402 if ( 0 === $row_idx && $this->render_options['table_head'] && $num_rows > 1 ) {
403 $thead = $this->_render_row( $row_idx, 'th' );
404 continue;
405 }
406 // Neither first nor last row (with respective head/foot enabled), so render as body row.
407 $tbody[] = $this->_render_row( $row_idx, 'td' );
408 }
409
410 // Re-instate nl2br() behavior after this render process, if "convert_line_breaks" Shortcode parameter is set to false.
411 if ( ! $this->render_options['convert_line_breaks'] ) {
412 remove_filter( 'tablepress_apply_nl2br', '__return_false', 9 ); // Priority 9, so that this filter can easily be overwritten at the default priority.
413 }
414
415 // <caption> tag.
416 /**
417 * Filter the content for the HTML caption element of the table.
418 *
419 * If the "Edit" link for a table is shown, it is also added to the caption element.
420 *
421 * @since 1.0.0
422 *
423 * @param string $caption The content for the HTML caption element of the table. Default empty.
424 * @param array $table The current table.
425 */
426 $caption = apply_filters( 'tablepress_print_caption_text', '', $this->table );
427 $caption_style = '';
428 $caption_class = '';
429 if ( ! empty( $caption ) ) {
430 /**
431 * Filter the class attribute for the HTML caption element of the table.
432 *
433 * @since 1.0.0
434 *
435 * @param string $class The class attribute for the HTML caption element of the table.
436 * @param string $table_id The current table ID.
437 */
438 $caption_class = apply_filters( 'tablepress_print_caption_class', "tablepress-table-caption tablepress-table-caption-id-{$this->table['id']}", $this->table['id'] );
439 $caption_class = ' class="' . $caption_class . '"';
440 }
441 if ( ! empty( $this->render_options['edit_table_url'] ) ) {
442 if ( empty( $caption ) ) {
443 $caption_style = ' style="caption-side:bottom;text-align:left;border:none;background:none;margin:0;padding:0;"';
444 } else {
445 $caption .= '<br />';
446 }
447 $caption .= '<a href="' . esc_url( $this->render_options['edit_table_url'] ) . '" rel="nofollow">' . __( 'Edit', 'default' ) . '</a>';
448 }
449 if ( ! empty( $caption ) ) {
450 $caption = "<caption{$caption_class}{$caption_style}>{$caption}</caption>\n";
451 }
452
453 // <colgroup> tag.
454 $colgroup = '';
455 /**
456 * Filter whether the HTML colgroup tag shall be added to the table output.
457 *
458 * @since 1.0.0
459 *
460 * @param bool $print Whether the colgroup element shall be printed.
461 * @param string $table_id The current table ID.
462 */
463 if ( apply_filters( 'tablepress_print_colgroup_tag', false, $this->table['id'] ) ) {
464 for ( $col_idx = 0; $col_idx < $num_columns; $col_idx++ ) {
465 $attributes = ' class="colgroup-column-' . ( $col_idx + 1 ) . ' "';
466 /**
467 * Filter the attributes of the HTML col tags in the HTML colgroup tag.
468 *
469 * @since 1.0.0
470 *
471 * @param string $attributes The attributes in the col element.
472 * @param string $table_id The current table ID.
473 * @param int $col_idx The number of the column.
474 */
475 $attributes = apply_filters( 'tablepress_colgroup_tag_attributes', $attributes, $this->table['id'], $col_idx + 1 );
476 $colgroup .= "\t<col{$attributes}/>\n";
477 }
478 }
479 if ( ! empty( $colgroup ) ) {
480 $colgroup = "<colgroup>\n{$colgroup}</colgroup>\n";
481 }
482
483 // <thead>, <tfoot>, and <tbody> tags.
484 if ( ! empty( $thead ) ) {
485 $thead = "<thead>\n{$thead}</thead>\n";
486 }
487 if ( ! empty( $tfoot ) ) {
488 $tfoot = "<tfoot>\n{$tfoot}</tfoot>\n";
489 }
490 $tbody_class = ( $this->render_options['row_hover'] ) ? ' class="row-hover"' : '';
491 // Reverse rows because we looped through the rows in reverse order.
492 $tbody = array_reverse( $tbody );
493 $tbody = "<tbody{$tbody_class}>\n" . implode( '', $tbody ) . "</tbody>\n";
494
495 // Attributes for the table (HTML table element).
496 $table_attributes = array();
497
498 // "id" attribute.
499 if ( ! empty( $this->render_options['html_id'] ) ) {
500 $table_attributes['id'] = $this->render_options['html_id'];
501 }
502
503 // "class" attribute.
504 $css_classes = array( 'tablepress', "tablepress-id-{$this->table['id']}", $this->render_options['extra_css_classes'] );
505 /**
506 * Filter the CSS classes that are given to the HTML table element.
507 *
508 * @since 1.0.0
509 *
510 * @param array $css_classes The CSS classes for the table element.
511 * @param string $table_id The current table ID.
512 */
513 $css_classes = apply_filters( 'tablepress_table_css_classes', $css_classes, $this->table['id'] );
514 // $css_classes might contain several classes in one array entry.
515 $css_classes = explode( ' ', implode( ' ', $css_classes ) );
516 $css_classes = array_map( array( 'TablePress', 'sanitize_css_class' ), $css_classes );
517 $css_classes = array_unique( $css_classes );
518 $css_classes = trim( implode( ' ', $css_classes ) );
519 if ( ! empty( $css_classes ) ) {
520 $table_attributes['class'] = $css_classes;
521 }
522
523 // ARIA label attributes.
524 if ( $this->render_options['print_name'] && ! empty( $this->render_options['html_id'] ) ) {
525 $table_attributes['aria-labelledby'] = "{$this->render_options['html_id']}-name";
526 }
527 if ( $this->render_options['print_description'] && ! empty( $this->render_options['html_id'] ) ) {
528 $table_attributes['aria-describedby'] = "{$this->render_options['html_id']}-description";
529 }
530
531 // "summary" attribute.
532 $summary = '';
533 /**
534 * Filter the content for the summary attribute of the HTML table element.
535 *
536 * The attribute is only added if it is not empty.
537 *
538 * @since 1.0.0
539 *
540 * @param string $summary The content for the summary attribute of the table. Default empty.
541 * @param array $table The current table.
542 */
543 $summary = apply_filters( 'tablepress_print_summary_attr', $summary, $this->table );
544 if ( ! empty( $summary ) ) {
545 $table_attributes['summary'] = esc_attr( $summary );
546 }
547
548 // Legacy support for attributes that are not encouraged in HTML5.
549 foreach ( array( 'cellspacing', 'cellpadding', 'border' ) as $attribute ) {
550 if ( false !== $this->render_options[ $attribute ] ) {
551 $table_attributes[ $attribute ] = (int) $this->render_options[ $attribute ];
552 }
553 }
554
555 /**
556 * Filter the attributes for the table (HTML table element).
557 *
558 * @since 1.4.0
559 *
560 * @param array $table_attributes The attributes for the table element.
561 * @param array $table The current table.
562 * @param array $render_options The render options for the table.
563 */
564 $table_attributes = apply_filters( 'tablepress_table_tag_attributes', $table_attributes, $this->table, $this->render_options );
565 $table_attributes = $this->_attributes_array_to_string( $table_attributes );
566
567 $output .= "\n<table{$table_attributes}>\n";
568 $output .= $caption . $colgroup . $thead . $tbody . $tfoot;
569 $output .= "</table>\n";
570
571 // name/description below table (HTML already generated above).
572 if ( $this->render_options['print_name'] && 'below' === $this->render_options['print_name_position'] ) {
573 $output .= $print_name_html;
574 }
575 if ( $this->render_options['print_description'] && 'below' === $this->render_options['print_description_position'] ) {
576 $output .= $print_description_html;
577 }
578
579 /**
580 * Filter the generated HTML code for table.
581 *
582 * @since 1.0.0
583 *
584 * @param string $output The generated HTML for the table.
585 * @param array $table The current table.
586 * @param array $render_options The render options for the table.
587 */
588 $this->output = apply_filters( 'tablepress_table_output', $output, $this->table, $this->render_options );
589 }
590
591 /**
592 * Generate the HTML of a row.
593 *
594 * @since 1.0.0
595 *
596 * @param int $row_idx Index of the row to be rendered.
597 * @param string $tag HTML tag to use for the cells (td or th).
598 * @return string HTML for the row.
599 */
600 protected function _render_row( $row_idx, $tag ) {
601 $row_cells = array();
602 // Loop through cells in reversed order, to search for colspan or rowspan trigger words.
603 for ( $col_idx = $this->last_column_idx; $col_idx >= 0; $col_idx-- ) {
604 $cell_content = $this->table['data'][ $row_idx ][ $col_idx ];
605
606 // Print formulas that are escaped with '= (like in Excel) as text.
607 if ( "'=" === substr( $cell_content, 0, 2 ) ) {
608 $cell_content = substr( $cell_content, 1 );
609 }
610 $cell_content = do_shortcode( $this->safe_output( $cell_content ) );
611 /**
612 * Filter the content of a single cell.
613 *
614 * Filter the content of a single cell, after formulas have been evaluated, the output has been sanitized, and Shortcodes have been evaluated.
615 *
616 * @since 1.0.0
617 *
618 * @param string $cell_content The cell content.
619 * @param string $table_id The current table ID.
620 * @param int $row_idx The row number of the cell.
621 * @param int $col_idx The column number of the cell.
622 */
623 $cell_content = apply_filters( 'tablepress_cell_content', $cell_content, $this->table['id'], $row_idx + 1, $col_idx + 1 );
624
625 if ( $this->span_trigger['rowspan'] === $cell_content ) { // There will be a rowspan.
626 // Check for #rowspan# in first row, which doesn't make sense.
627 if ( ( $row_idx > 1 && $row_idx < $this->last_row_idx )
628 || ( 1 === $row_idx && ! $this->render_options['table_head'] ) // No rowspan into table head.
629 || ( $this->last_row_idx === $row_idx && ! $this->render_options['table_foot'] ) ) { // No rowspan out of table foot.
630 // Increase counter for rowspan in this column.
631 $this->rowspan[ $col_idx ]++;
632 // Reset counter for colspan in this row, combined col- and rowspan might be happening.
633 $this->colspan[ $row_idx ] = 1;
634 continue;
635 }
636 // Invalid rowspan, so we set cell content from #rowspan# to empty.
637 $cell_content = '';
638 } elseif ( $this->span_trigger['colspan'] === $cell_content ) { // There will be a colspan.
639 // Check for #colspan# in first column, which doesn't make sense.
640 if ( $col_idx > 1
641 || ( 1 === $col_idx && ! $this->render_options['first_column_th'] ) ) { // No colspan into first column head.
642 // Increase counter for colspan in this row.
643 $this->colspan[ $row_idx ]++;
644 // Reset counter for rowspan in this column, combined col- and rowspan might be happening.
645 $this->rowspan[ $col_idx ] = 1;
646 continue;
647 }
648 // Invalid colspan, so we set cell content from #colspan# to empty.
649 $cell_content = '';
650 } elseif ( $this->span_trigger['span'] === $cell_content ) { // There will be a combined col- and rowspan.
651 // Check for #span# in first column or first or last row, which is not always possible.
652 if ( ( $row_idx > 1 && $row_idx < $this->last_row_idx && $col_idx > 1 )
653 // We are in first, second, or last row or in the first or second column, so more checks are necessary.
654 || ( ( 1 === $row_idx && ! $this->render_options['table_head'] ) // No rowspan into table head.
655 && ( $col_idx > 1 || ( 1 === $col_idx && ! $this->render_options['first_column_th'] ) ) ) // And no colspan into first column head.
656 || ( ( $this->last_row_idx === $row_idx && ! $this->render_options['table_foot'] ) // No rowspan out of table foot.
657 && ( $col_idx > 1 || ( 1 === $col_idx && ! $this->render_options['first_column_th'] ) ) ) ) { // And no colspan into first column head.
658 continue;
659 }
660 // Invalid span, so we set cell content from #span# to empty.
661 $cell_content = '';
662 } elseif ( '' === $cell_content && 0 === $row_idx && $this->render_options['table_head'] ) {
663 // Make empty cells have a space in the table head, to give sorting arrows the correct position in IE9.
664 $cell_content = '&nbsp;';
665 }
666
667 // Attributes for the table cell (HTML td or th element).
668 $tag_attributes = array();
669
670 // "colspan" and "rowspan" attributes.
671 if ( $this->colspan[ $row_idx ] > 1 ) { // We have colspaned cells.
672 $tag_attributes['colspan'] = $this->colspan[ $row_idx ];
673 }
674 if ( $this->rowspan[ $col_idx ] > 1 ) { // We have rowspaned cells.
675 $tag_attributes['rowspan'] = $this->rowspan[ $col_idx ];
676 }
677
678 // "class" attribute.
679 $cell_class = 'column-' . ( $col_idx + 1 );
680 /**
681 * Filter the CSS classes that are given to a single cell (HTML td element) of a table.
682 *
683 * @since 1.0.0
684 *
685 * @param string $cell_class The CSS classes for the cell.
686 * @param string $table_id The current table ID.
687 * @param string $cell_content The cell content.
688 * @param int $row_idx The row number of the cell.
689 * @param int $col_idx The column number of the cell.
690 * @param int $colspan_row The number of combined columns for this cell.
691 * @param int $rowspan_col The number of combined rows for this cell.
692 */
693 $cell_class = apply_filters( 'tablepress_cell_css_class', $cell_class, $this->table['id'], $cell_content, $row_idx + 1, $col_idx + 1, $this->colspan[ $row_idx ], $this->rowspan[ $col_idx ] );
694 if ( ! empty( $cell_class ) ) {
695 $tag_attributes['class'] = $cell_class;
696 }
697
698 // "style" attribute.
699 if ( ( 0 === $row_idx ) && ! empty( $this->render_options['column_widths'][ $col_idx ] ) ) {
700 $tag_attributes['style'] = 'width:' . preg_replace( '#[^0-9a-z.%]#', '', $this->render_options['column_widths'][ $col_idx ] ) . ';';
701 }
702
703 /**
704 * Filter the attributes for the table cell (HTML td or th element).
705 *
706 * @since 1.4.0
707 *
708 * @param array $tag_attributes The attributes for the td or th element.
709 * @param string $table_id The current table ID.
710 * @param string $cell_content The cell content.
711 * @param int $row_idx The row number of the cell.
712 * @param int $col_idx The column number of the cell.
713 * @param int $colspan_row The number of combined columns for this cell.
714 * @param int $rowspan_col The number of combined rows for this cell.
715 */
716 $tag_attributes = apply_filters( 'tablepress_cell_tag_attributes', $tag_attributes, $this->table['id'], $cell_content, $row_idx + 1, $col_idx + 1, $this->colspan[ $row_idx ], $this->rowspan[ $col_idx ] );
717 $tag_attributes = $this->_attributes_array_to_string( $tag_attributes );
718
719 if ( $this->render_options['first_column_th'] && 0 === $col_idx ) {
720 $tag = 'th';
721 }
722
723 $row_cells[] = "<{$tag}{$tag_attributes}>{$cell_content}</{$tag}>";
724 $this->colspan[ $row_idx ] = 1; // Reset.
725 $this->rowspan[ $col_idx ] = 1; // Reset.
726 }
727
728 // Attributes for the table row (HTML tr element).
729 $tr_attributes = array();
730
731 // "class" attribute.
732 $row_classes = 'row-' . ( $row_idx + 1 );
733 if ( $this->render_options['alternating_row_colors'] ) {
734 $row_classes .= ( 1 === ( $row_idx % 2 ) ) ? ' even' : ' odd';
735 }
736 /**
737 * Filter the CSS classes that are given to a row (HTML tr element) of a table.
738 *
739 * @since 1.0.0
740 *
741 * @param string $row_classes The CSS classes for the row.
742 * @param string $table_id The current table ID.
743 * @param array $row_cells The HTML code for the cells of the row.
744 * @param int $row_idx The row number.
745 * @param array $row_data The content of the cells of the row.
746 */
747 $row_classes = apply_filters( 'tablepress_row_css_class', $row_classes, $this->table['id'], $row_cells, $row_idx + 1, $this->table['data'][ $row_idx ] );
748 if ( ! empty( $row_classes ) ) {
749 $tr_attributes['class'] = $row_classes;
750 }
751
752 /**
753 * Filter the attributes for the table row (HTML tr element).
754 *
755 * @since 1.4.0
756 *
757 * @param array $tr_attributes The attributes for the tr element.
758 * @param string $table_id The current table ID.
759 * @param int $row_idx The row number.
760 * @param array $row_data The content of the cells of the row.
761 */
762 $tr_attributes = apply_filters( 'tablepress_row_tag_attributes', $tr_attributes, $this->table['id'], $row_idx + 1, $this->table['data'][ $row_idx ] );
763 $tr_attributes = $this->_attributes_array_to_string( $tr_attributes );
764
765 // Reverse rows because we looped through the cells in reverse order.
766 $row_cells = array_reverse( $row_cells );
767 return "<tr{$tr_attributes}>\n\t" . implode( '', $row_cells ) . "\n</tr>\n";
768 }
769
770 /**
771 * Convert an array of HTML tag attributes to a string.
772 *
773 * @since 1.4.0
774 *
775 * @param array $attributes Attributes for the HTML tag in the array keys, and their values in the array values.
776 * @return string The attributes as a string for usage in a HTML element.
777 */
778 protected function _attributes_array_to_string( array $attributes ) {
779 $attributes_string = '';
780 foreach ( $attributes as $attribute => $value ) {
781 $attributes_string .= " {$attribute}=\"{$value}\"";
782 }
783 return $attributes_string;
784 }
785
786 /**
787 * Possibly replace certain HTML entities and replace line breaks with HTML.
788 *
789 * @TODO: Find a better solution than this function, e.g. something like wpautop().
790 *
791 * @since 1.0.0
792 *
793 * @param string $string The string to process.
794 * @return string Processed string for output.
795 */
796 protected function safe_output( $string ) {
797 /*
798 * Replace any & with &amp; that is not already an encoded entity (from function htmlentities2 in WP 2.8).
799 * A complete htmlentities2() or htmlspecialchars() would encode <HTML> tags, which we don't want.
800 */
801 $string = preg_replace( '/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,4};)/', '&amp;', $string );
802 /**
803 * Filter whether line breaks in the cell content shall be replaced with HTML br tags.
804 *
805 * @since 1.0.0
806 *
807 * @param bool $replace Whether to replace line breaks with HTML br tags. Default true.
808 * @param string $table_id The current table ID.
809 */
810 if ( apply_filters( 'tablepress_apply_nl2br', true, $this->table['id'] ) ) {
811 $string = nl2br( $string );
812 }
813 return $string;
814 }
815
816 /**
817 * Get the default render options, null means: Use option from "Edit" screen.
818 *
819 * @since 1.0.0
820 *
821 * @return array Default render options.
822 */
823 public function get_default_render_options() {
824 // Attention: Array keys have to be lowercase, otherwise they won't match the Shortcode attributes, which will be passed in lowercase by WP.
825 return array(
826 'id' => '',
827 'column_widths' => '',
828 'alternating_row_colors' => null,
829 'row_hover' => null,
830 'table_head' => null,
831 'table_foot' => null,
832 'first_column_th' => false,
833 'print_name' => null,
834 'print_name_position' => null,
835 'print_description' => null,
836 'print_description_position' => null,
837 'cache_table_output' => true,
838 'convert_line_breaks' => true,
839 'extra_css_classes' => null,
840 'use_datatables' => null,
841 'datatables_sort' => null,
842 'datatables_paginate' => null,
843 'datatables_paginate_entries' => null,
844 'datatables_lengthchange' => null,
845 'datatables_filter' => null,
846 'datatables_info' => null,
847 'datatables_scrollx' => null,
848 'datatables_scrolly' => false,
849 'datatables_custom_commands' => null,
850 'datatables_locale' => get_locale(),
851 'show_rows' => '',
852 'show_columns' => '',
853 'hide_rows' => '',
854 'hide_columns' => '',
855 'cellspacing' => false,
856 'cellpadding' => false,
857 'border' => false,
858 'shortcode_debug' => false,
859 );
860 }
861
862 /**
863 * Get the CSS code for the Preview iframe.
864 *
865 * @since 1.0.0
866 *
867 * @return string CSS for the Preview iframe.
868 */
869 public function get_preview_css() {
870 if ( is_rtl() ) {
871 $rtl = "\ndirection: rtl;";
872 $rtl_align = 'right';
873 } else {
874 $rtl = '';
875 $rtl_align = 'left';
876 }
877 return <<<CSS
878 <style type="text/css">
879 /* iframe */
880 body {
881 margin: 10px;
882 font-family: sans-serif;{$rtl}
883 }
884 /* Inline Shortcodes, in texts */
885 .table-shortcode-inline {
886 background: transparent;
887 border: none;
888 color: #333333;
889 width: 110px;
890 margin: 0;
891 padding: 0;
892 -webkit-box-shadow: none;
893 box-shadow: none;
894 text-align: center;
895 font-weight: bold;
896 font-size: 100%;
897 }
898 .table-shortcode {
899 cursor: text;
900 }
901 /* Default table styling */
902 .tablepress {
903 border-collapse: collapse;
904 border-spacing: 0;
905 width: 100%;
906 margin-bottom: 1em;
907 border: none;
908 }
909 .tablepress td,
910 .tablepress th {
911 padding: 8px;
912 border: none;
913 background: none;
914 text-align: {$rtl_align};
915 }
916 .tablepress tbody td {
917 vertical-align: top;
918 }
919 .tablepress tbody tr td,
920 .tablepress tfoot tr th {
921 border-top: 1px solid #dddddd;
922 }
923 .tablepress tbody tr:first-child td {
924 border-top: 0;
925 }
926 .tablepress thead tr th {
927 border-bottom: 1px solid #dddddd;
928 }
929 .tablepress thead th,
930 .tablepress tfoot th {
931 background-color: #d9edf7;
932 font-weight: bold;
933 vertical-align: middle;
934 }
935 .tablepress tbody tr.odd td {
936 background-color: #f9f9f9;
937 }
938 .tablepress tbody tr.even td {
939 background-color: #ffffff;
940 }
941 .tablepress .row-hover tr:hover td {
942 background-color: #f3f3f3;
943 }
944 .tablepress img {
945 margin: 0;
946 padding: 0;
947 border: none;
948 max-width: none;
949 }
950 </style>
951 CSS;
952 }
953
954 } // class TablePress_Render
955