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 / create-chart.php

create-chart.php in wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin trunk, at Infrastructure/WP/MCP/Abilities/create-chart.php

340 lines 14.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ability: wpdatatables/create-chart
4 *
5 * Creates a new wpDataTables chart linked to an existing wpDataTable.
6 *
7 * Supports all chart engines available on the installation:
8 * - Google Charts (all tiers)
9 * - Chart.js (all tiers)
10 * - HighCharts (Standard+, requires WDT_HC_INTEGRATION)
11 * - ApexCharts (Standard+, requires WDT_AC_INTEGRATION)
12 * - HighCharts Stock (Pro+, requires WDT_HS_INTEGRATION)
13 *
14 * The AI should first call get-system-info to check which engines are available,
15 * then list-tables / get-table-info to find the source table and its columns.
16 *
17 * @package wpDataTables_MCP_Server
18 * @since 0.2.0
19 */
20
21 defined( 'ABSPATH' ) or die('Access denied.');
22
23 add_action( 'wp_abilities_api_init', 'wdtmcp_register_create_chart_ability' );
24
25 function wdtmcp_register_create_chart_ability() {
26 wp_register_ability(
27 'wpdatatables/create-chart',
28 array(
29 'label' => __( 'Create Chart', 'wpdatatables' ),
30 'description' => __( 'Creates a new chart from an existing wpDataTable. Specify the source table ID, chart engine (google or chartjs — these are the engines available in wpDataTables Lite), chart type (line, column, pie, area, bar, scatter, etc.), which columns to include, and optional display settings (title, dimensions, colours). Returns the chart ID and shortcode. Check get-system-info first to see which chart engines are available on this installation. NOTE: Simple tables cannot be used as a chart source. WHEN TO CALL: user wants a visual chart from existing table data. DO NOT CALL before a source table exists — create or find the table first. REQUIRED INPUT: title, wpdatatable_id (from list-tables), engine (google or chartjs), type, selected_columns (orig_header strings from get-table-info). OPTIONAL: width, height, show_title, show_grid, range_type, row_range, and other display flags. TYPICAL WORKFLOW: get-system-info → list-tables → get-table-info → create-chart. RETURNS: chart_id and shortcode. VERIFY with list-charts and get-chart-info.', 'wpdatatables' ),
31 'category' => 'wpdatatables-data',
32
33 'input_schema' => array(
34 'type' => 'object',
35 'properties' => array(
36 'title' => array(
37 'type' => 'string',
38 'description' => 'Chart title.',
39 ),
40 'wpdatatable_id' => array(
41 'type' => 'integer',
42 'description' => 'Source wpDataTable ID.',
43 ),
44 'engine' => array(
45 'type' => 'string',
46 'description' => 'Chart rendering engine. wpDataTables Lite supports google and chartjs (HighCharts/ApexCharts require a premium licence).',
47 'enum' => array( 'google', 'chartjs' ),
48 ),
49 'type' => array(
50 'type' => 'string',
51 'description' => 'Chart type (e.g. line, column, pie, area, bar, scatter, donut, bubble, radar, polar, gauge, histogram, candlestick, waterfall, stepped_area, stacked_bar, stacked_column, stacked_area).',
52 ),
53 'selected_columns' => array(
54 'type' => 'array',
55 'description' => 'Column orig_headers to include in the chart. First column is typically the X-axis label.',
56 'items' => array( 'type' => 'string' ),
57 ),
58 'width' => array(
59 'type' => 'integer',
60 'description' => 'Chart width in pixels (default 400).',
61 ),
62 'height' => array(
63 'type' => 'integer',
64 'description' => 'Chart height in pixels (default 400).',
65 ),
66 'responsive_width' => array(
67 'type' => 'boolean',
68 'description' => 'Use responsive width (default true).',
69 ),
70 'follow_filtering' => array(
71 'type' => 'boolean',
72 'description' => 'Chart reacts to table front-end filters (default false).',
73 ),
74 'group_chart' => array(
75 'type' => 'boolean',
76 'description' => 'Group rows with the same label (default false).',
77 ),
78 ),
79 'required' => array( 'title', 'wpdatatable_id', 'engine', 'type', 'selected_columns' ),
80 ),
81
82 'output_schema' => array(
83 'type' => 'object',
84 'properties' => array(
85 'chart_id' => array( 'type' => 'integer', 'description' => 'Created chart ID.' ),
86 'shortcode' => array( 'type' => 'string', 'description' => 'WordPress shortcode to embed the chart.' ),
87 ),
88 ),
89
90 'execute_callback' => 'wdtmcp_execute_create_chart',
91
92 'permission_callback' => function () {
93 return current_user_can( 'manage_options' );
94 },
95
96 'meta' => array(
97 'annotations' => array(
98 'instructions' => __( 'Call get-system-info for available engines, then get-table-info for column orig_header values. Requires wpdatatable_id, engine, type, and selected_columns.', 'wpdatatables' ),
99 'readonly' => false,
100 'destructive' => false,
101 'idempotent' => false,
102 ),
103 ),
104 )
105 );
106 }
107
108 /**
109 * Map a generic chart type to the engine-specific type used by wpDataTables.
110 *
111 * @param string $engine Chart engine key.
112 * @param string $type Generic or engine-specific chart type.
113 * @return string
114 */
115 function wdtmcp_resolve_chart_type( $engine, $type ) {
116 $engine = strtolower( sanitize_text_field( $engine ) );
117 $type = strtolower( sanitize_text_field( $type ) );
118
119 if ( '' === $type ) {
120 return '';
121 }
122
123 if ( strpos( $type, $engine . '_' ) === 0 ) {
124 return $type;
125 }
126
127 $aliases = array(
128 'google' => array(
129 'line' => 'google_line_chart',
130 'area' => 'google_area_chart',
131 'stepped_area' => 'google_stepped_area_chart',
132 'column' => 'google_column_chart',
133 'histogram' => 'google_histogram',
134 'bar' => 'google_bar_chart',
135 'stacked_bar' => 'google_stacked_bar_chart',
136 'pie' => 'google_pie_chart',
137 'donut' => 'google_donut_chart',
138 'bubble' => 'google_bubble_chart',
139 'scatter' => 'google_scatter_chart',
140 'gauge' => 'google_gauge_chart',
141 'candlestick' => 'google_candlestick_chart',
142 'waterfall' => 'google_waterfall_chart',
143 'geo' => 'google_geo_chart',
144 ),
145 'chartjs' => array(
146 'line' => 'chartjs_line_chart',
147 'bar' => 'chartjs_bar_chart',
148 'pie' => 'chartjs_pie_chart',
149 'doughnut'=> 'chartjs_doughnut_chart',
150 'donut' => 'chartjs_doughnut_chart',
151 'radar' => 'chartjs_radar_chart',
152 'polar' => 'chartjs_polar_area_chart',
153 'bubble' => 'chartjs_bubble_chart',
154 'scatter' => 'chartjs_scatter_chart',
155 ),
156 'highcharts' => array(
157 'line' => 'highcharts_line_chart',
158 'area' => 'highcharts_area_chart',
159 'column' => 'highcharts_column_chart',
160 'bar' => 'highcharts_basic_bar_chart',
161 'stacked_bar' => 'highcharts_stacked_bar_chart',
162 'stacked_column' => 'highcharts_stacked_column_chart',
163 'pie' => 'highcharts_pie_chart',
164 'donut' => 'highcharts_donut_chart',
165 'scatter' => 'highcharts_scatter_plot',
166 ),
167 'apexcharts' => array(
168 'line' => 'apexcharts_basic_line_chart',
169 'area' => 'apexcharts_basic_area_chart',
170 'column' => 'apexcharts_column_chart',
171 'bar' => 'apexcharts_stacked_bar_chart',
172 'stacked_bar' => 'apexcharts_stacked_bar_chart',
173 'stacked_column' => 'apexcharts_stacked_column_chart',
174 'pie' => 'apexcharts_pie_chart',
175 'donut' => 'apexcharts_donut_chart',
176 'radar' => 'apexcharts_radar_chart',
177 'scatter' => 'apexcharts_scatter_chart',
178 ),
179 );
180
181 if ( isset( $aliases[ $engine ][ $type ] ) ) {
182 return $aliases[ $engine ][ $type ];
183 }
184
185 if ( substr( $type, -6 ) === '_chart' ) {
186 return $engine . '_' . $type;
187 }
188
189 return $engine . '_' . $type . '_chart';
190 }
191
192 /**
193 * Execute callback for wpdatatables/create-chart.
194 *
195 * @param array $input
196 * @return array|WP_Error
197 */
198 function wdtmcp_execute_create_chart( $input ) {
199 global $wpdb;
200
201 if ( ! class_exists( 'WPDataChart' ) || ! class_exists( 'WPDataTable' ) ) {
202 return new \WP_Error(
203 'wdtmcp_missing_class',
204 __( 'wpDataTables core classes WPDataChart and WPDataTable are required.', 'wpdatatables' )
205 );
206 }
207
208 $title = isset( $input['title'] ) ? sanitize_text_field( $input['title'] ) : '';
209 $wpdatatable_id = isset( $input['wpdatatable_id'] ) ? (int) $input['wpdatatable_id'] : 0;
210 $engine = isset( $input['engine'] ) ? strtolower( sanitize_text_field( $input['engine'] ) ) : '';
211 $type = isset( $input['type'] ) ? sanitize_text_field( $input['type'] ) : '';
212 $selected_columns = isset( $input['selected_columns'] ) && is_array( $input['selected_columns'] )
213 ? array_values( array_map( 'sanitize_text_field', $input['selected_columns'] ) )
214 : array();
215
216 $allowed_engines = array( 'google', 'chartjs' );
217
218 if ( '' === $title ) {
219 return new \WP_Error( 'wdtmcp_invalid_input', __( 'A chart title is required.', 'wpdatatables' ) );
220 }
221
222 if ( $wpdatatable_id <= 0 ) {
223 return new \WP_Error( 'wdtmcp_invalid_input', __( 'A valid wpdatatable_id is required.', 'wpdatatables' ) );
224 }
225
226 if ( ! in_array( $engine, $allowed_engines, true ) ) {
227 return new \WP_Error(
228 'wdtmcp_invalid_input',
229 sprintf(
230 /* translators: %s: comma-separated engine list */
231 __( 'Engine must be one of: %s.', 'wpdatatables' ),
232 implode( ', ', $allowed_engines )
233 )
234 );
235 }
236
237 $engine_license = wdtmcp_assert_chart_engine_available( $engine, 'wpdatatables/create-chart' );
238 if ( is_wp_error( $engine_license ) ) {
239 return $engine_license;
240 }
241
242 if ( '' === $type ) {
243 return new \WP_Error( 'wdtmcp_invalid_input', __( 'A chart type is required.', 'wpdatatables' ) );
244 }
245
246 if ( count( $selected_columns ) < 2 ) {
247 return new \WP_Error(
248 'wdtmcp_invalid_input',
249 __( 'At least two selected_columns are required (label column and value column).', 'wpdatatables' )
250 );
251 }
252
253 $table_exists = (int) $wpdb->get_var(
254 $wpdb->prepare(
255 'SELECT COUNT(*) FROM ' . $wpdb->prefix . 'wpdatatables WHERE id = %d',
256 $wpdatatable_id
257 )
258 );
259
260 if ( $table_exists < 1 ) {
261 return new \WP_Error(
262 'wdtmcp_not_found',
263 sprintf( __( 'Table with ID %d was not found.', 'wpdatatables' ), $wpdatatable_id )
264 );
265 }
266
267 try {
268 $table = \WPDataTable::loadWpDataTable( $wpdatatable_id, null, true );
269 } catch ( \Exception $e ) {
270 return new \WP_Error(
271 'wdtmcp_table_load_failed',
272 sprintf( __( 'Could not load source table: %s', 'wpdatatables' ), $e->getMessage() )
273 );
274 }
275
276 $available_headers = array();
277 foreach ( $table->getColumns() as $column ) {
278 $available_headers[] = $column->getOriginalHeader();
279 }
280
281 foreach ( $selected_columns as $column_header ) {
282 if ( ! in_array( $column_header, $available_headers, true ) ) {
283 return new \WP_Error(
284 'wdtmcp_invalid_input',
285 sprintf(
286 /* translators: 1: column name, 2: table id */
287 __( 'Column "%1$s" was not found on table %2$d.', 'wpdatatables' ),
288 $column_header,
289 $wpdatatable_id
290 )
291 );
292 }
293 }
294
295 $resolved_type = wdtmcp_resolve_chart_type( $engine, $type );
296
297 $chart_data = array(
298 'title' => $title,
299 'wpdatatable_id' => $wpdatatable_id,
300 'engine' => $engine,
301 'type' => $resolved_type,
302 'selected_columns' => $selected_columns,
303 'range_type' => 'all_rows',
304 'follow_filtering' => isset( $input['follow_filtering'] ) ? (bool) $input['follow_filtering'] : false,
305 'width' => isset( $input['width'] ) ? (int) $input['width'] : 400,
306 'height' => isset( $input['height'] ) ? (int) $input['height'] : 400,
307 'responsive_width' => isset( $input['responsive_width'] ) ? (bool) $input['responsive_width'] : true,
308 'group_chart' => isset( $input['group_chart'] ) ? (bool) $input['group_chart'] : false,
309 'show_grid' => true,
310 'show_title' => true,
311 'tooltip_enabled' => true,
312 'background_color' => '#FFFFFF',
313 'loader' => (bool) get_option( 'wdtGlobalChartLoader' ),
314 );
315
316 try {
317 $chart = \WPDataChart::build( $chart_data );
318 if ( function_exists( 'wdtmcp_chart_save_persisting_table_rows' ) ) {
319 wdtmcp_chart_save_persisting_table_rows( $chart );
320 } else {
321 $chart->save();
322 }
323 } catch ( \Exception $e ) {
324 return new \WP_Error(
325 'wdtmcp_chart_create_failed',
326 sprintf( __( 'Failed to create chart: %s', 'wpdatatables' ), $e->getMessage() )
327 );
328 }
329
330 $chart_id = (int) $chart->getId();
331 if ( $chart_id <= 0 ) {
332 return new \WP_Error( 'wdtmcp_chart_create_failed', __( 'Chart was not saved.', 'wpdatatables' ) );
333 }
334
335 return array(
336 'chart_id' => $chart_id,
337 'shortcode' => $chart->getShortCode(),
338 );
339 }
340