PluginProbe
wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin / trunk
wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin vtrunk
6.5.1.7 6.5.1.6 6.5.1.5 6.5.1.4 6.5.1.3 6.5.1.2 6.5.1.1 6.5.0.9 6.5.0.8 6.5.0.7 6.5.0.6 trunk 3.4.2.40 3.4.2.41 3.4.2.42 3.4.2.43 3.4.2.44 3.4.2.45 3.4.2.46 3.4.2.47 3.4.2.48 3.4.2.49 3.4.2.50 6.3.2 6.3.3.1 All 47 releases
wpdatatables / Infrastructure / WP / MCP / Abilities / update-simple-table-styles.php

update-simple-table-styles.php in wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin trunk, at Infrastructure/WP/MCP/Abilities/update-simple-table-styles.php

352 lines 15.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ability: wpdatatables/update-simple-table-styles
4 *
5 * Updates styling and layout settings of an existing Simple wpDataTable.
6 * Supports table-level settings (template, responsive, striped, borders) and
7 * cell-level styling (colors, fonts, alignment) for specific cells.
8 *
9 * @package wpDataTables_MCP_Server
10 * @since 0.2.0
11 */
12
13 defined( 'ABSPATH' ) or die('Access denied.');
14
15 add_action( 'wp_abilities_api_init', 'wdtmcp_register_update_simple_table_styles_ability' );
16
17 function wdtmcp_register_update_simple_table_styles_ability() {
18 wp_register_ability(
19 'wpdatatables/update-simple-table-styles',
20 array(
21 'label' => __( 'Update Simple Table Styles', 'wpdatatables' ),
22 'description' => __( 'Updates styling and layout of an existing Simple wpDataTable. Change table-level settings (template, responsive, striped, borders, padding), update cells with HTML and meta classes, or set custom_css for heavily styled tables. Use #wpdtSimpleTable-{table_id} in custom_css. See wpdatatables.com pricing table tutorials. Use get-table-info first to inspect the table. WHEN TO CALL: after create-simple-table when you need to adjust design, change template (pricing/pedigree), update cell HTML, add images, or apply custom_css. ONLY FOR Simple tables (table_type=simple) — use update-table-settings for SQL/source tables. REQUIRED INPUT: table_id (integer). OPTIONAL: table_settings, cell_updates (row_index, col_index, data/attachment_id/meta), merged_cells, custom_css. TYPICAL WORKFLOW: get-table-info → update-simple-table-styles. FOR IMAGES in cells: list-media or upload-media-from-url first, then pass attachment_id in cell_updates.', 'wpdatatables' ),
23 'category' => 'wpdatatables-data',
24
25 'input_schema' => array(
26 'type' => 'object',
27 'properties' => array(
28 'table_id' => array(
29 'type' => 'integer',
30 'description' => 'The Simple wpDataTable ID to update.',
31 ),
32 'table_settings' => array(
33 'type' => 'object',
34 'description' => 'Optional table-level settings to change.',
35 'properties' => array(
36 'template_id' => array( 'type' => 'integer' ),
37 'simple_header' => array( 'type' => 'boolean' ),
38 'stripe_table' => array( 'type' => 'boolean' ),
39 'responsive' => array( 'type' => 'boolean' ),
40 'remove_borders' => array( 'type' => 'boolean' ),
41 'cell_padding' => array( 'type' => 'integer' ),
42 'show_title' => array( 'type' => 'boolean' ),
43 'show_table_description' => array( 'type' => 'boolean' ),
44 'table_description' => array( 'type' => 'string' ),
45 ),
46 ),
47 'cell_updates' => array(
48 'type' => 'array',
49 'description' => 'Each: {row_index, col_index, data?, attachment_id?, meta?}. Use data for HTML, or attachment_id (with optional size, alt) to insert a media library image. For styled tables, data must include HTML with classes your custom_css targets.',
50 ),
51 'merged_cells' => array(
52 'type' => 'array',
53 'description' => 'Optional. Replace merged cells. Each: {row, col, rowspan, colspan}. Pass empty array to clear all merges.',
54 ),
55 'custom_css' => array(
56 'type' => 'string',
57 'description' => 'Custom CSS for heavily styled tables. Use #wpdtSimpleTable-{table_id} in selectors. Pass empty string to clear stored CSS. Stored CSS is auto-injected when table is on page.',
58 ),
59 'row_heights' => array(
60 'type' => 'array',
61 'description' => 'Fix Handsontable editor "only first row visible": set explicit row heights (px). E.g. [100, 100, 100] for 3 rows. Prevents auto-sizing to huge content.',
62 'items' => array( 'type' => 'integer' ),
63 ),
64 ),
65 'required' => array( 'table_id' ),
66 ),
67
68 'output_schema' => array(
69 'type' => 'object',
70 'properties' => array(
71 'table_id' => array( 'type' => 'integer', 'description' => 'Updated table ID.' ),
72 'updated' => array( 'type' => 'array', 'description' => 'List of what was changed.', 'items' => array( 'type' => 'string' ) ),
73 'custom_css' => array( 'type' => 'string', 'description' => 'Custom CSS to add to page (if provided).' ),
74 ),
75 ),
76
77 'execute_callback' => 'wdtmcp_execute_update_simple_table_styles',
78
79 'permission_callback' => function () {
80 return current_user_can( 'manage_options' );
81 },
82
83 'meta' => array(
84 'annotations' => array(
85 'instructions' => __( 'Only for Simple tables (table_type=simple). Call get-table-info first. Use table_id plus optional table_settings, cell_updates, merged_cells, or custom_css.', 'wpdatatables' ),
86 'readonly' => false,
87 'destructive' => false,
88 'idempotent' => false,
89 ),
90 ),
91 )
92 );
93 }
94
95 /**
96 * Execute callback for wpdatatables/update-simple-table-styles.
97 *
98 * @param array $input
99 * @return array|WP_Error
100 */
101 function wdtmcp_execute_update_simple_table_styles( $input ) {
102 global $wpdb;
103
104 if ( ! class_exists( 'WPDataTableRows' ) || ! class_exists( 'WDTConfigController' ) ) {
105 return new \WP_Error(
106 'wdtmcp_missing_class',
107 __( 'wpDataTables core classes are required.', 'wpdatatables' )
108 );
109 }
110
111 $table_id = isset( $input['table_id'] ) ? (int) $input['table_id'] : 0;
112 $table_settings = isset( $input['table_settings'] ) && is_array( $input['table_settings'] ) ? $input['table_settings'] : array();
113 $cell_updates = isset( $input['cell_updates'] ) && is_array( $input['cell_updates'] ) ? $input['cell_updates'] : array();
114 $merged_cells = isset( $input['merged_cells'] ) ? $input['merged_cells'] : null;
115 $custom_css = null;
116 if ( array_key_exists( 'custom_css', $input ) ) {
117 $validated = wdtmcp_validate_custom_css_input( $input['custom_css'] );
118 if ( is_wp_error( $validated ) ) {
119 return $validated;
120 }
121 $custom_css = $validated;
122 }
123 $row_heights = isset( $input['row_heights'] ) && is_array( $input['row_heights'] ) ? $input['row_heights'] : null;
124
125 if ( $table_id <= 0 ) {
126 return new \WP_Error( 'wdtmcp_missing_param', __( 'table_id is required.', 'wpdatatables' ) );
127 }
128
129 $updated = array();
130
131 // Load existing table.
132 $table_row = $wpdb->get_row(
133 $wpdb->prepare(
134 "SELECT id, title, table_type, content, advanced_settings FROM {$wpdb->prefix}wpdatatables WHERE id = %d",
135 $table_id
136 ),
137 ARRAY_A
138 );
139
140 if ( ! $table_row || 'simple' !== $table_row['table_type'] ) {
141 return new \WP_Error(
142 'wdtmcp_invalid_table',
143 __( 'Table not found or is not a Simple table.', 'wpdatatables' )
144 );
145 }
146
147 // ── Update table-level settings (advanced_settings) ─────────────────
148 $advanced = json_decode( $table_row['advanced_settings'], true );
149 if ( ! is_array( $advanced ) ) {
150 $advanced = array();
151 }
152
153 $settings_map = array(
154 'template_id' => array( 'key' => 'simple_template_id', 'int' => true ),
155 'simple_header' => array( 'key' => 'simpleHeader', 'bool' => true ),
156 'stripe_table' => array( 'key' => 'stripeTable', 'bool' => true ),
157 'responsive' => array( 'key' => 'simpleResponsive', 'bool' => true ),
158 'remove_borders' => array( 'key' => 'removeBorders', 'bool' => true ),
159 'cell_padding' => array( 'key' => 'cellPadding', 'int' => true ),
160 'show_title' => array( 'key' => 'show_title', 'bool' => true ),
161 'show_table_description' => array( 'key' => 'show_table_description', 'bool' => true ),
162 'table_description' => array( 'key' => 'table_description', 'str' => true ),
163 );
164
165 foreach ( $table_settings as $k => $v ) {
166 if ( ! isset( $settings_map[ $k ] ) ) {
167 continue;
168 }
169 $m = $settings_map[ $k ];
170 $adv_key = $m['key'];
171 if ( ! empty( $m['bool'] ) ) {
172 $advanced[ $adv_key ] = (bool) $v ? 1 : 0;
173 } elseif ( ! empty( $m['int'] ) ) {
174 $advanced[ $adv_key ] = (int) $v;
175 } elseif ( ! empty( $m['str'] ) ) {
176 $advanced[ $adv_key ] = sanitize_textarea_field( (string) $v );
177 }
178 $updated[] = "table_settings.{$k}";
179 }
180
181 if ( ! empty( $table_settings ) ) {
182 $result = $wpdb->update(
183 $wpdb->prefix . 'wpdatatables',
184 array( 'advanced_settings' => wp_json_encode( $advanced ) ),
185 array( 'id' => $table_id ),
186 array( '%s' ),
187 array( '%d' )
188 );
189 if ( false === $result ) {
190 return new \WP_Error(
191 'wdtmcp_db_error',
192 $wpdb->last_error ?: __( 'Failed to update table settings.', 'wpdatatables' )
193 );
194 }
195 }
196
197 // ── Update content (merged cells) ──────────────────────────────────
198 if ( null !== $merged_cells && is_array( $merged_cells ) ) {
199 $content = json_decode( $table_row['content'], true );
200 if ( is_array( $content ) ) {
201 $merged_normalized = array();
202 foreach ( $merged_cells as $m ) {
203 if ( isset( $m['row'], $m['col'], $m['rowspan'], $m['colspan'] ) ) {
204 $merged_normalized[] = array(
205 'row' => (int) $m['row'],
206 'col' => (int) $m['col'],
207 'rowspan' => max( 1, (int) $m['rowspan'] ),
208 'colspan' => max( 1, (int) $m['colspan'] ),
209 'removed' => false,
210 );
211 }
212 }
213 $content['mergedCells'] = $merged_normalized;
214 $result = $wpdb->update(
215 $wpdb->prefix . 'wpdatatables',
216 array( 'content' => wp_json_encode( $content ) ),
217 array( 'id' => $table_id ),
218 array( '%s' ),
219 array( '%d' )
220 );
221 if ( false === $result ) {
222 return new \WP_Error(
223 'wdtmcp_db_error',
224 $wpdb->last_error ?: __( 'Failed to update merged cells.', 'wpdatatables' )
225 );
226 }
227 $updated[] = 'merged_cells';
228 }
229 }
230
231 // ── Update cell data and meta (rows) ─────────────────────────────────
232 if ( ! empty( $cell_updates ) ) {
233 $rows = $wpdb->get_results(
234 $wpdb->prepare(
235 "SELECT id, data FROM {$wpdb->prefix}wpdatatables_rows WHERE table_id = %d ORDER BY id",
236 $table_id
237 ),
238 ARRAY_A
239 );
240
241 $cell_updates_by_row = array();
242 foreach ( $cell_updates as $cu ) {
243 $ri = isset( $cu['row_index'] ) ? (int) $cu['row_index'] : -1;
244 if ( $ri < 0 || ! isset( $rows[ $ri ] ) ) {
245 continue;
246 }
247 $cell_updates_by_row[ $ri ][] = $cu;
248 }
249
250 foreach ( $cell_updates_by_row as $ri => $row_cell_updates ) {
251 $row_db = $rows[ $ri ];
252 $row_data = json_decode( $row_db['data'], true );
253 if ( ! is_array( $row_data ) || ! isset( $row_data['cells'] ) ) {
254 continue;
255 }
256
257 $row_changed = false;
258 foreach ( $row_cell_updates as $cu ) {
259 $ci = isset( $cu['col_index'] ) ? (int) $cu['col_index'] : -1;
260 if ( $ci < 0 || ! isset( $row_data['cells'][ $ci ] ) ) {
261 continue;
262 }
263
264 $cell = &$row_data['cells'][ $ci ];
265 if ( isset( $cu['attachment_id'] ) && (int) $cu['attachment_id'] > 0 ) {
266 $img_html = wdtmcp_attachment_to_img( (int) $cu['attachment_id'], $cu );
267 $cell['data'] = $img_html ? wdtmcp_sanitize_cell_html( $img_html ) : ( $cell['data'] ?? '' );
268 } elseif ( isset( $cu['data'] ) ) {
269 $cell['data'] = current_user_can( 'unfiltered_html' ) ? wp_unslash( (string) $cu['data'] ) : wp_kses_post( (string) $cu['data'] );
270 }
271 if ( isset( $cu['meta'] ) && is_array( $cu['meta'] ) ) {
272 $cell['meta'] = wdtmcp_sanitize_cell_meta( $cu['meta'] );
273 }
274 unset( $cell );
275
276 $row_changed = true;
277 $updated[] = "cell.{$ri}.{$ci}";
278 }
279
280 if ( $row_changed ) {
281 $encoded = wp_json_encode( $row_data );
282 $result = $wpdb->update(
283 $wpdb->prefix . 'wpdatatables_rows',
284 array( 'data' => $encoded ),
285 array( 'id' => (int) $row_db['id'] ),
286 array( '%s' ),
287 array( '%d' )
288 );
289 if ( false === $result ) {
290 return new \WP_Error(
291 'wdtmcp_db_error',
292 $wpdb->last_error ?: __( 'Failed to update table row.', 'wpdatatables' )
293 );
294 }
295 $rows[ $ri ]['data'] = $encoded;
296 }
297 }
298 }
299
300 // ── Update row heights (fixes Handsontable editor "only first row visible") ──
301 if ( null !== $row_heights && ! empty( $row_heights ) ) {
302 $rows_db = $wpdb->get_results(
303 $wpdb->prepare(
304 "SELECT id, data FROM {$wpdb->prefix}wpdatatables_rows WHERE table_id = %d ORDER BY id",
305 $table_id
306 ),
307 ARRAY_A
308 );
309 foreach ( $row_heights as $ri => $h ) {
310 if ( $ri < 0 || ! isset( $rows_db[ $ri ] ) ) {
311 continue;
312 }
313 $h = max( 20, min( 500, (int) $h ) );
314 $row_db = $rows_db[ $ri ];
315 $row_data = json_decode( $row_db['data'], true );
316 if ( is_array( $row_data ) ) {
317 $row_data['height'] = $h;
318 $result = $wpdb->update(
319 $wpdb->prefix . 'wpdatatables_rows',
320 array( 'data' => wp_json_encode( $row_data ) ),
321 array( 'id' => (int) $row_db['id'] ),
322 array( '%s' ),
323 array( '%d' )
324 );
325 if ( false === $result ) {
326 return new \WP_Error(
327 'wdtmcp_db_error',
328 $wpdb->last_error ?: __( 'Failed to update row height.', 'wpdatatables' )
329 );
330 }
331 $updated[] = "row_height.{$ri}";
332 }
333 }
334 }
335
336 $css_substituted = '';
337 if ( null !== $custom_css ) {
338 $css_substituted = wdtmcp_prepare_table_custom_css_for_storage( $custom_css, $table_id );
339 wdtmcp_set_table_custom_css( $table_id, $css_substituted );
340 $updated[] = 'custom_css';
341 }
342
343 $out = array(
344 'table_id' => $table_id,
345 'updated' => array_values( array_unique( $updated ) ),
346 );
347 if ( '' !== $css_substituted ) {
348 $out['custom_css'] = $css_substituted;
349 }
350 return $out;
351 }
352