PluginProbe
SQL Chart Builder / 3.0.4
SQL Chart Builder v3.0.4
3.0.5 3.0.4 3.0.3 3.0.2 3.0.1 trunk 1.0.2 1.0.3 2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.7.1 2.3.7.2 2.3.8 3.0.0
← All changes | functions.php +841 -251 2.3.33.0.4 View file →
@@ -31,14 +31,12 @@
31 31 <tbody><tr><td style="width: auto;vertical-align: top;padding: 20px;">
32 32 <h2>WooCommerce Search Engine – INSTANT, RELEVANT AND SMART Search Box</h2>
33 33 <h3>Turn your website search into Smart Search which find products by price, SKU, attributes, meta data, categorys, tags etc. </h3>
34 34 <p>“WooCommerce Search Engine” is a very powerful and easy to use WooCommerce Search Plugin which turns a simple search box of your WooCommerce Store to the powerful multifunctional magic box which helps you to sell more products. The plugin UI is compatible with ALL THEMES.</p>
35 - <a target="_blank" style="border:0px solid #6200ee;border-radius:0px;color:white;font-weight:bold;background: #6200ee;" class="button button-secondary"
35 + <a target="_blank" style="border:0px solid #6200ee;border-radius:0px;color:white;font-weight:bold;background: #6200ee;" class="button button-secondary"
36 36 href="https://codecanyon.net/item/woocommerce-search-box/15685698">Get the Search Box </a>
37 37 </td><td style="position:relative">
38 - <a href="'.admin_url().'/edit.php?post_type=gvn_schart&gvnsql_dismiss_recommendation=1'.'" style="position: absolute;right: 0;top: -15px;right: -10px;"><svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="11" height="14" viewBox="0 0 11 14">
39 - <path d="M10.141 10.328q0 0.312-0.219 0.531l-1.062 1.062q-0.219 0.219-0.531 0.219t-0.531-0.219l-2.297-2.297-2.297 2.297q-0.219 0.219-0.531 0.219t-0.531-0.219l-1.062-1.062q-0.219-0.219-0.219-0.531t0.219-0.531l2.297-2.297-2.297-2.297q-0.219-0.219-0.219-0.531t0.219-0.531l1.062-1.062q0.219-0.219 0.531-0.219t0.531 0.219l2.297 2.297 2.297-2.297q0.219-0.219 0.531-0.219t0.531 0.219l1.062 1.062q0.219 0.219 0.219 0.531t-0.219 0.531l-2.297 2.297 2.297 2.297q0.219 0.219 0.219 0.531z"></path>
40 - </svg>
38 + <a href="'.admin_url().'/edit.php?post_type=gvn_schart&gvnsql_dismiss_recommendation=1'.'" style="position: absolute;right: 0;top: -15px;right: -10px;">{svg}
41 39 </a>
42 40 <img src="'.plugin_dir_url( __FILE__ ) . 'asset/img/recommended1.jpg" style="max-width: 430px;"></td></tr>
43 41 </tbody></table>';
44 42 }
@@ -49,18 +47,20 @@
49 47 {
50 48 global $post;
51 49
52 50
53 - if(
54 - (!empty($_SERVER["REQUEST_URI"]) and strpos($_SERVER["REQUEST_URI"],'post_type=gvn_schart')!==false)
55 - or
51 + if(
52 + (!empty($_SERVER["REQUEST_URI"]) and strpos(sanitize_text_field(wp_unslash($_SERVER["REQUEST_URI"])),'post_type=gvn_schart')!==false)
53 + or
56 54 (!empty($post) and $post->post_type == 'gvn_schart')
57 55 ){
58 - echo guaven_sqlcharts_recommended();
56 + echo str_replace('{svg}','<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="11" height="14" viewBox="0 0 11 14">
57 + <path d="M10.141 10.328q0 0.312-0.219 0.531l-1.062 1.062q-0.219 0.219-0.531 0.219t-0.531-0.219l-2.297-2.297-2.297 2.297q-0.219 0.219-0.531 0.219t-0.531-0.219l-1.062-1.062q-0.219-0.219-0.219-0.531t0.219-0.531l2.297-2.297-2.297-2.297q-0.219-0.219-0.219-0.531t0.219-0.531l1.062-1.062q0.219-0.219 0.531-0.219t0.531 0.219l2.297 2.297 2.297-2.297q0.219-0.219 0.531-0.219t0.531 0.219l1.062 1.062q0.219 0.219 0.219 0.531t-0.219 0.531l-2.297 2.297 2.297 2.297q0.219 0.219 0.219 0.531z"></path>
58 + </svg>',wp_kses_post(guaven_sqlcharts_recommended(),[]));
59 59 }
60 -
61 60
62 61
62 +
63 63 if (!empty($post) and $post->post_type == 'gvn_schart'):
64 64 if (!current_user_can('manage_options')) {
65 65 echo '<br><br>
66 66 <div class="updated gf-alert gf-alert-danger">Only administrators can manage this page</div>';
@@ -66,24 +66,31 @@
66 66 <div class="updated gf-alert gf-alert-danger">Only administrators can manage this page</div>';
67 67 die();
68 68 }
69 69 echo '<div class="updated gf-alert gf-alert-info">';
70 - if (empty($_GET["post"]) and strpos($_SERVER["REQUEST_URI"], "post-new") === false):
71 - $gf_message = 'Use <b>Add new</b> button above to create new sql report. And click on any existing rule names below
72 - to manage them. ';
73 - else:
74 - $gf_message = '
75 - 1. Give any name to your report.<br>
76 - 2. Choose chart type, type sql query, enter field names, labels and then press to Publish/Update<br>
77 - 3. After update you will see needed shortcode below. You can use that shortcode anywhere in your website: in pages, posts, widgets etc. <br>
78 - ';
79 - endif;
80 - _e('<div style="float:left">' . $gf_message . '</div>', 'guaven_sqlcharts');
70 + if (empty($_GET["post"]) && strpos(sanitize_text_field(wp_unslash($_SERVER["REQUEST_URI"])), "post-new") === false) {
71 + $gf_message = __(
72 + 'Use <b>Add new</b> button above to create a new SQL report. And click on any existing rule names below to manage them.',
73 + 'guaven_sqlcharts'
74 + );
75 + } else {
76 + $gf_message = __(
77 + '1. Give any name to your report.<br>
78 + 2. Pick a chart type, build your SQL query with the visual builder (or type it — autocomplete will help), map the X/Y columns and press Publish/Update.<br>
79 + 3. After update, you will see the shortcode of this chart at the bottom of the page. You can use that shortcode anywhere in your website: in pages, posts, widgets, etc.',
80 + 'guaven_sqlcharts'
81 + );
82 + }
83 +
84 + echo '<div style="float:left;max-width:calc(100% - 345px)">';
85 + echo wp_kses_post( $gf_message );
86 + echo '</div>';
87 +
81 88 echo '<div style="float: right;
82 89 margin-top: 0px;
83 90 padding-top: 0px;"><a target="_blank" style="text-align:center;border:0px solid #6200ee;border-radius:0px;color:white;font-weight:bold;background: #6200ee;"
84 91 class="button button-secondary" href="https://guaven.com/contact/solution-request/">Get Premium Support </a>
85 - <span style="line-height: 30px;padding: 0 5px;">OR</span>
92 + <span style="line-height: 30px;padding: 0 5px;">OR</span>
86 93 <a target="_blank" style="text-align:center;border:0px solid #26b286;border-radius:0px;color:white;font-weight:bold;background: #26b286;"
87 94 class="button button-secondary" href="https://guaven.com/service/small-thankyou-premium-support-service/">Make a Small Donation</a>
88 95 </div> </div>';
89 96 endif;
@@ -103,11 +110,14 @@
103 110 add_action('admin_notices', 'guaven_sqlcharts_onboarding_notice');
104 111
105 112 function guaven_sqlcharts_onboarding_notice_dismissed(){
106 113 check_ajax_referer('notice_dismissed', 'nonce');
114 + if (!current_user_can('manage_options')) return;
115 +
116 + if(empty($_POST['type']))return;
107 117 switch ($_POST['type']){
108 118 case 'onboarding_notice':
109 - update_option('guaven_sqlcharts_onboarding_notice_dismissed', 1);
119 + update_option('guaven_sqlcharts_onboarding_notice_dismissed', 1);
110 120 break;
111 121 }
112 122 }
113 123 add_action('wp_ajax_guaven_sqlcharts_onboarding_notice_dismissed', 'guaven_sqlcharts_onboarding_notice_dismissed');
@@ -113,10 +123,12 @@
113 123 add_action('wp_ajax_guaven_sqlcharts_onboarding_notice_dismissed', 'guaven_sqlcharts_onboarding_notice_dismissed');
114 124
115 125 function guaven_sqlcharts_enqueue_chart()
116 126 {
117 - wp_enqueue_script('guaven_sqlcharts_chartjs', plugins_url('asset/bundle.min.js', __FILE__),array('jquery'),GVNSQLCHARTS_VERSION);
118 - wp_localize_script('guaven_sqlcharts_chartjs', 'guaven_sqlcharts_notice_dismissed', array(
127 + wp_enqueue_script('guaven_sqlcharts_chartjs', plugins_url('asset/chart.umd.min.js', __FILE__),array('jquery'),GVNSQLCHARTS_VERSION,false);
128 + wp_enqueue_script('guaven_sqlcharts_datepicker', plugins_url('asset/datepicker.min.js', __FILE__),array('jquery'),GVNSQLCHARTS_VERSION,false);
129 + wp_enqueue_script('guaven_sqlcharts_front', plugins_url('asset/front.js', __FILE__),array('jquery','guaven_sqlcharts_chartjs','guaven_sqlcharts_datepicker'),GVNSQLCHARTS_VERSION,false);
130 + wp_localize_script('guaven_sqlcharts_front', 'guaven_sqlcharts_notice_dismissed', array(
119 131 'action' => 'guaven_sqlcharts_onboarding_notice_dismissed',
120 132 'nonce' => wp_create_nonce('notice_dismissed')
121 133 ));
122 134
@@ -127,11 +139,55 @@
127 139 function guaven_sqlcharts_enqueue_main_style()
128 140 {
129 141 wp_enqueue_style('guaven_sqlcharts_main_style', plugins_url('asset/guaven_sqlcharts.css', __FILE__),array(),GVNSQLCHARTS_VERSION);
130 142 }
143 +add_action('wp_enqueue_scripts', 'guaven_sqlcharts_enqueue_main_style');
131 144 add_action('admin_enqueue_scripts', 'guaven_sqlcharts_enqueue_main_style');
132 145
146 +// admin-only assets: SQL builder, autocomplete, new metabox UI
147 +function guaven_sqlcharts_admin_assets($hook)
148 +{
149 + if (!in_array($hook, array('post.php', 'post-new.php'))) return;
150 + $screen = function_exists('get_current_screen') ? get_current_screen() : null;
151 + if (empty($screen->post_type) or $screen->post_type != 'gvn_schart') return;
152 + if (!current_user_can('manage_options')) return;
133 153
154 + wp_enqueue_style('guaven_sqlcharts_admin_style', plugins_url('asset/admin.css', __FILE__), array(), GVNSQLCHARTS_VERSION);
155 + wp_enqueue_script('guaven_sqlcharts_admin', plugins_url('asset/admin.js', __FILE__), array('jquery'), GVNSQLCHARTS_VERSION, true);
156 +
157 + global $wpdb;
158 + $tables = $wpdb->get_col('SHOW TABLES');
159 + if (!is_array($tables)) $tables = array();
160 + wp_localize_script('guaven_sqlcharts_admin', 'gvnSqlBuilder', array(
161 + 'ajaxurl' => admin_url('admin-ajax.php'),
162 + 'nonce' => wp_create_nonce('gvnsql_schema'),
163 + 'tables' => array_values($tables),
164 + 'prefix' => $wpdb->prefix,
165 + ));
166 +}
167 +add_action('admin_enqueue_scripts', 'guaven_sqlcharts_admin_assets');
168 +
169 +// returns column names of one table for the live SQL builder/autocomplete
170 +function guaven_sqlcharts_ajax_columns()
171 +{
172 + check_ajax_referer('gvnsql_schema', 'nonce');
173 + if (!current_user_can('manage_options')) wp_send_json_error('forbidden', 403);
174 + global $wpdb;
175 + $table = isset($_POST['table']) ? sanitize_text_field(wp_unslash($_POST['table'])) : '';
176 + $tables = $wpdb->get_col('SHOW TABLES');
177 + if (!is_array($tables) or !in_array($table, $tables, true)) wp_send_json_error('unknown table', 400);
178 + $cols = $wpdb->get_results('SHOW COLUMNS FROM `' . str_replace('`', '', $table) . '`');
179 + $out = array();
180 + if (is_array($cols)) {
181 + foreach ($cols as $col) {
182 + $out[] = array('name' => $col->Field, 'type' => $col->Type);
183 + }
184 + }
185 + wp_send_json_success($out);
186 +}
187 +add_action('wp_ajax_gvnsql_get_columns', 'guaven_sqlcharts_ajax_columns');
188 +
189 +
134 190 function guaven_sqlcharts_isJson($string)
135 191 {
136 192 json_decode($string);
137 193 return (json_last_error() == JSON_ERROR_NONE);
@@ -139,17 +195,61 @@
139 195
140 196 add_action('init', 'guaven_sqlcharts_register_post');
141 197 function guaven_sqlcharts_register_post()
142 198 {
143 - //register_taxonomy('guaven_update_push_tag', 'termin');
144 199 register_post_type('gvn_schart', array(
145 200 'labels' => array(
146 - 'name' => __('My SQL Charts'),
147 - 'singular_name' => __('My SQL chart')
201 + 'name' => __('My SQL Charts','guaven_sqlcharts'),
202 + 'singular_name' => __('SQL Chart','guaven_sqlcharts'),
203 + 'menu_name' => __('My SQL Charts','guaven_sqlcharts'),
204 + 'add_new' => __('Add Chart','guaven_sqlcharts'),
205 + 'add_new_item' => __('Add New Chart','guaven_sqlcharts'),
206 + 'edit_item' => __('Edit Chart','guaven_sqlcharts'),
207 + 'new_item' => __('New Chart','guaven_sqlcharts'),
208 + 'view_item' => __('View Chart','guaven_sqlcharts'),
209 + 'view_items' => __('View Charts','guaven_sqlcharts'),
210 + 'search_items' => __('Search Charts','guaven_sqlcharts'),
211 + 'not_found' => __('No charts found','guaven_sqlcharts'),
212 + 'not_found_in_trash' => __('No charts found in Trash','guaven_sqlcharts'),
213 + 'all_items' => __('All Charts','guaven_sqlcharts'),
214 + 'archives' => __('Chart Archives','guaven_sqlcharts'),
215 + 'attributes' => __('Chart Attributes','guaven_sqlcharts'),
216 + 'insert_into_item' => __('Insert into chart','guaven_sqlcharts'),
217 + 'uploaded_to_this_item' => __('Uploaded to this chart','guaven_sqlcharts'),
218 + 'filter_items_list' => __('Filter charts list','guaven_sqlcharts'),
219 + 'items_list_navigation' => __('Charts list navigation','guaven_sqlcharts'),
220 + 'items_list' => __('Charts list','guaven_sqlcharts'),
221 + 'item_published' => __('Chart published.','guaven_sqlcharts'),
222 + 'item_published_privately' => __('Chart published privately.','guaven_sqlcharts'),
223 + 'item_reverted_to_draft' => __('Chart reverted to draft.','guaven_sqlcharts'),
224 + 'item_scheduled' => __('Chart scheduled.','guaven_sqlcharts'),
225 + 'item_updated' => __('Chart updated.','guaven_sqlcharts'),
148 226 ),
149 227
150 228 'public' => true,
151 - //'taxonomies' => array('guaven_update_push_tag'),
229 + 'show_in_rest' => false,
230 + 'menu_icon' => 'dashicons-chart-pie',
231 + // Charts execute SQL, so every primitive capability of this post type maps to manage_options.
232 + // Contributors/Authors cannot create, edit, publish or delete charts through any WordPress
233 + // entry point (admin UI, XML-RPC, REST). Published charts stay viewable on the front end.
234 + // Only primitive capabilities are remapped: mapping the meta capabilities edit_post/read_post/
235 + // delete_post to manage_options would make WordPress treat manage_options itself as a meta
236 + // capability and break that check site-wide.
237 + 'capability_type' => 'post',
238 + 'map_meta_cap' => true,
239 + 'capabilities' => array(
240 + 'edit_posts' => 'manage_options',
241 + 'edit_others_posts' => 'manage_options',
242 + 'edit_published_posts' => 'manage_options',
243 + 'edit_private_posts' => 'manage_options',
244 + 'publish_posts' => 'manage_options',
245 + 'read_private_posts' => 'manage_options',
246 + 'delete_posts' => 'manage_options',
247 + 'delete_private_posts' => 'manage_options',
248 + 'delete_published_posts' => 'manage_options',
249 + 'delete_others_posts' => 'manage_options',
250 + 'create_posts' => 'manage_options',
251 + ),
152 252 'supports' => array(
153 253 'title',
154 254 'postmeta'
155 255 ),
@@ -158,8 +258,20 @@
158 258
159 259 guaven_sqlcharts_load_defaults();
160 260 }
161 261
262 +// All guaven_sqlcharts_* meta keys are protected: they cannot be written through the Custom Fields box,
263 +// XML-RPC or the REST API. The plugin's own save handler (update_post_meta) is not affected.
264 +add_filter('is_protected_meta', function ($protected, $meta_key) {
265 + return strpos((string) $meta_key, 'guaven_sqlcharts_') === 0 ? true : $protected;
266 +}, 10, 2);
267 +
268 +// "Add title" placeholder on the chart edit screen
269 +add_filter('enter_title_here', function ($title, $post) {
270 + if (!empty($post) and $post->post_type == 'gvn_schart') return __('Chart name', 'guaven_sqlcharts');
271 + return $title;
272 +}, 10, 2);
273 +
162 274 add_action('admin_footer', 'guaven_sqlcharts_admin_front');
163 275
164 276
165 277 function guaven_sqlcharts_admin_front()
@@ -174,9 +286,9 @@
174 286
175 287 // metabox for editor
176 288 function guaven_sqlcharts_metabox_area()
177 289 {
178 - add_meta_box('guaven_sqlcharts_metabox', 'Configure your graph chart', 'guaven_sqlcharts_metabox', 'gvn_schart', 'advanced', 'default');
290 + add_meta_box('guaven_sqlcharts_metabox', 'Chart Builder', 'guaven_sqlcharts_metabox', 'gvn_schart', 'advanced', 'default');
179 291 }
180 292
181 293 function guaven_sqlcharts_metabox()
182 294 {
@@ -182,25 +294,127 @@
182 294 {
183 295 require_once(dirname(__FILE__) . "/admin_metabox.php");
184 296 }
185 297
298 +/**
299 + * Catalog of all supported chart types: label, group, per-type usage guide and example query.
300 + * Used by the admin UI to render the type cards and the contextual guides.
301 + */
302 +function guaven_sqlcharts_type_catalog()
303 +{
304 + global $wpdb;
305 + $p = $wpdb->posts;
306 + $u = $wpdb->users;
307 + return array(
308 + 'pie_l' => array(
309 + 'label' => 'Pie',
310 + 'group' => 'Circular',
311 + 'guide' => 'Best for showing how a total splits into a few parts (shares/percentages). Use one query that returns a label column (X) and a numeric value column (Y). Keep it under ~8 slices for readability.',
312 + 'example_sql' => "select count(*) postcount, SUBSTR(post_date,1,4) yearnum from $p group by yearnum order by yearnum asc limit 10",
313 + 'example_x' => 'yearnum', 'example_y' => 'postcount',
314 + ),
315 + 'donut_l' => array(
316 + 'label' => 'Doughnut',
317 + 'group' => 'Circular',
318 + 'guide' => 'Same as Pie but with a hole in the middle — slightly easier to compare slice sizes. One query: label column (X) + numeric column (Y).',
319 + 'example_sql' => "select count(*) postcount, post_type from $p group by post_type order by postcount desc limit 8",
320 + 'example_x' => 'post_type', 'example_y' => 'postcount',
321 + ),
322 + 'polar_l' => array(
323 + 'label' => 'Polar Area',
324 + 'group' => 'Circular',
325 + 'guide' => 'Like a pie, but every slice has the same angle and the value controls the radius. Good for cyclic data (months, weekdays). One query: label (X) + numeric value (Y).',
326 + 'example_sql' => "select count(*) postcount, SUBSTRING(post_date,6,2) monthnum from $p group by monthnum order by monthnum",
327 + 'example_x' => 'monthnum', 'example_y' => 'postcount',
328 + ),
329 + 'radar_l' => array(
330 + 'label' => 'Radar',
331 + 'group' => 'Circular',
332 + 'guide' => 'Compares one or more series across several categories arranged in a circle. Great for profiles/ratings. Use one query per series, separated with ";".',
333 + 'example_sql' => "select count(*) postcount, SUBSTRING(post_date,6,2) monthnum from $p where post_type=\"post\" group by monthnum order by monthnum",
334 + 'example_x' => 'monthnum', 'example_y' => 'postcount',
335 + ),
336 + 'line_l' => array(
337 + 'label' => 'Line',
338 + 'group' => 'Line',
339 + 'guide' => 'The classic choice for trends over time (per day/month/year). X should be an ordered value like a date. Add more queries separated with ";" for comparison lines.',
340 + 'example_sql' => "select count(*) postcount, SUBSTRING(post_date,1,7) monthandyear from $p group by monthandyear order by monthandyear",
341 + 'example_x' => 'monthandyear', 'example_y' => 'postcount',
342 + ),
343 + 'area_l' => array(
344 + 'label' => 'Area',
345 + 'group' => 'Line',
346 + 'guide' => 'A line chart with the region under the line filled — emphasizes volume/magnitude of a trend. Works well with 2 queries (";" separated) to compare filled regions.',
347 + 'example_sql' => "select count(*) postcount, SUBSTRING(post_date,1,7) monthandyear from $p where post_type=\"post\" group by monthandyear order by monthandyear;\nselect count(*) postcount, SUBSTRING(post_date,1,7) monthandyear from $p where post_type!=\"post\" group by monthandyear order by monthandyear",
348 + 'example_x' => 'monthandyear', 'example_y' => 'Posts;Other content',
349 + ),
350 + 'steppedline_l' => array(
351 + 'label' => 'Stepped Line',
352 + 'group' => 'Line',
353 + 'guide' => 'Line chart that moves in steps instead of slopes — perfect for values that change at discrete moments (prices, stock level, settings history).',
354 + 'example_sql' => "select count(*) postcount, SUBSTRING(post_date,1,4) yearnum from $p group by yearnum order by yearnum",
355 + 'example_x' => 'yearnum', 'example_y' => 'postcount',
356 + ),
357 + 'bar_l' => array(
358 + 'label' => 'Bar',
359 + 'group' => 'Bar',
360 + 'guide' => 'Compares values across categories with vertical bars. One query: category (X) + numeric value (Y). Multiple ";" separated queries become grouped/stacked bars.',
361 + 'example_sql' => "select count(*) postcount, SUBSTRING(post_date,1,7) monthandyear from $p group by monthandyear order by monthandyear",
362 + 'example_x' => 'monthandyear', 'example_y' => 'postcount',
363 + ),
364 + 'horizontalbar_l' => array(
365 + 'label' => 'Horizontal Bar',
366 + 'group' => 'Bar',
367 + 'guide' => 'Bar chart rotated 90° — the best pick when category names are long (user names, product titles).',
368 + 'example_sql' => "select count(*) as postcount, b.display_name as dname from $p a inner join $u b ON a.post_author=b.ID where a.post_status=\"publish\" group by a.post_author order by postcount desc limit 10",
369 + 'example_x' => 'dname', 'example_y' => 'postcount',
370 + ),
371 + 'stackedbar_l' => array(
372 + 'label' => 'Stacked Bar',
373 + 'group' => 'Bar',
374 + 'guide' => 'Shows how each category total is composed of parts. Use 2+ queries separated with ";" — each query becomes one segment color of the stack.',
375 + 'example_sql' => "select count(*) postcount, SUBSTRING(post_date,1,4) yearnum from $p where post_type=\"post\" group by yearnum order by yearnum;\nselect count(*) postcount, SUBSTRING(post_date,1,4) yearnum from $p where post_type!=\"post\" group by yearnum order by yearnum",
376 + 'example_x' => 'yearnum', 'example_y' => 'Posts;Other content',
377 + ),
378 + 'scatter_l' => array(
379 + 'label' => 'Scatter',
380 + 'group' => 'Other',
381 + 'guide' => 'Plots points on numeric X/Y axes to reveal correlation between two numbers. Both X and Y columns must be numeric (e.g. comment_count vs menu_order).',
382 + 'example_sql' => "select comment_count ccount, ID from $p where post_status=\"publish\" order by ID limit 100",
383 + 'example_x' => 'ID', 'example_y' => 'ccount',
384 + ),
385 + );
386 +}
387 +
388 +/**
389 + * Maps deprecated Google-Chart era type slugs to their Chart.js equivalents.
390 + * Old charts keep working without any manual migration; when the post is re-saved
391 + * from the new UI the normalized value is stored automatically.
392 + */
393 +function guaven_sqlcharts_normalize_type($type)
394 +{
395 + $map = array(
396 + 'pie' => 'pie_l',
397 + '3dpie' => 'pie_l',
398 + 'column' => 'bar_l',
399 + 'bar' => 'horizontalbar_l',
400 + 'area' => 'area_l',
401 + );
402 + return isset($map[$type]) ? $map[$type] : $type;
403 +}
404 +
186 405 function guaven_gutenberg_wrapper($atts){
187 406 if(isset($atts['sqlcharts_inserted_script'])){
188 407 global $sqlcharts_inserted_script;
189 408 $sqlcharts_inserted_script = $atts['sqlcharts_inserted_script'];
190 409 }
191 -
192 - // if( isset($_GET["post_id"],$_GET["context"]) and $_GET["context"]=='edit'
193 - // and strpos($_SERVER["REQUEST_URI"],'block-renderer/guaven-sqlcharts/gvn-chart-gutenberg')!==false
194 - // ){
195 - // $atts['chart_id']=$_GET["post_id"];
196 - // }
410 +
197 411 $post = get_post($atts['chart_id']);
198 412 if( ! isset($atts['chart_id']) or !isset($post) or $post->post_type != 'gvn_schart'){
199 -
413 +
200 414 return "Invalid id";
201 415 }
202 -
416 +
203 417 return guaven_sqlcharts_local_shortcode(array('id' => $atts['chart_id'])); // temporary explicit value
204 418 }
205 419 function guaven_register_gutenberg_blocks()
206 420 {
@@ -207,14 +421,15 @@
207 421 wp_register_script(
208 422 'gvn_gutenberg_charts',
209 423 plugins_url( 'asset/guaven_gutenberg_charts.js', __FILE__ ),
210 424 array( 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-server-side-render' ),
211 - GVNSQLCHARTS_VERSION.'_'.filemtime( plugin_dir_path( __FILE__ ) . 'asset/guaven_gutenberg_charts.js' )
425 + GVNSQLCHARTS_VERSION.'_'.filemtime( plugin_dir_path( __FILE__ ) . 'asset/guaven_gutenberg_charts.js'),
426 + false
212 427 );
213 428 wp_localize_script('gvn_gutenberg_charts', 'guaven', array(
214 429 'description' => 'Add My SQL Chart to your post',
215 430 ));
216 -
431 +
217 432 register_block_type( 'guaven-sqlcharts/gvn-chart-gutenberg', array(
218 433 'editor_script' => 'gvn_gutenberg_charts',
219 434 'render_callback' => 'guaven_gutenberg_wrapper',
220 435 'attributes' => array(
@@ -237,8 +452,11 @@
237 452 {
238 453 if (!isset($_POST['meta_box_nonce_field']) or !wp_verify_nonce($_POST['meta_box_nonce_field'], 'meta_box_nonce_action')) {
239 454 return $post->ID;
240 455 }
456 + if ($post->post_type != 'gvn_schart' or !current_user_can('manage_options') or (defined('DOING_AUTOSAVE') and DOING_AUTOSAVE)) {
457 + return $post->ID;
458 + }
241 459 $fields = array(
242 460 "guaven_sqlcharts_chartheight",
243 461 "guaven_sqlcharts_chartwidth",
244 462 "guaven_sqlcharts_graphtype",
@@ -257,9 +475,11 @@
257 475 "guaven_sqlcharts_begin_with_0_x",
258 476 "guaven_sqlcharts_begin_with_0_y",
259 477 "guaven_sqlcharts_round_y_values",
260 478 "guaven_sqlcharts_legend_position",
261 - "guaven_sqlcharts_nostacked"
479 + "guaven_sqlcharts_nostacked",
480 + "guaven_sqlcharts_forcetooltips",
481 + "guaven_sqlcharts_timeaxis"
262 482 );
263 483 foreach ($fields as $key => $value) {
264 484 if(isset($_POST[$value]))$newval=esc_attr($_POST[$value]);
265 485 else $newval='';
@@ -269,59 +489,80 @@
269 489 if(!empty($_POST["guaven_sqlcharts_dbpass"])){
270 490 $encpass=guaven_sqlcharts_encrypt_decrypt('encrypt',$_POST["guaven_sqlcharts_dbpass"]);
271 491 update_post_meta($post->ID, 'guaven_sqlcharts_dbpass', ['encrypted',$encpass]);
272 492 }
273 - update_post_meta($post->ID, 'guaven_sqlcharts_code', esc_attr(str_replace("'",'"',stripslashes($_POST['guaven_sqlcharts_code']))) );
493 + // Store the SQL as typed. Do not HTML-encode it and do not rewrite quotes:
494 + // the editor escapes it on output and the front end decodes entities before running it.
495 + $sql_code = isset($_POST['guaven_sqlcharts_code']) ? wp_check_invalid_utf8(wp_unslash($_POST['guaven_sqlcharts_code'])) : '';
496 + update_post_meta($post->ID, 'guaven_sqlcharts_code', $sql_code);
497 + // Flag that this chart stores raw SQL. Charts without the flag were saved by
498 + // versions before 3.0.1, which HTML-encoded the query, and still need decoding.
499 + update_post_meta($post->ID, 'guaven_sqlcharts_code_raw', 1);
274 500 }
501 +
502 +// Returns the stored SQL query exactly as the user typed it.
503 +function guaven_sqlcharts_get_code($post_id)
504 +{
505 + $sql = get_post_meta($post_id, 'guaven_sqlcharts_code', true);
506 + if (get_post_meta($post_id, 'guaven_sqlcharts_code_raw', true) != 1) {
507 + $sql = html_entity_decode($sql, ENT_QUOTES, 'UTF-8');
508 + }
509 + return $sql;
510 +}
275 511 add_action('save_post', 'guaven_sqlcharts_save_metabox_area', 1, 2);
276 512 // save the custom fields
277 513
278 514
279 515
280 -
281 -function guaven_sqlcharts_libloads($type, $step)
516 +// Removes string literals (contents only), backtick identifiers and comments from SQL so keyword checks
517 +// see the same code MySQL will execute. "/*!" and "/*+" comments are executable in MySQL and are kept.
518 +function guaven_sqlcharts_strip_sql_literals($sql)
282 519 {
283 - $stty = array(
284 - 'bar' => array(
285 - 'packages' => "'corechart', 'bar'",
286 - 'charts' => "BarChart"
287 - ),
288 - 'column' => array(
289 - 'packages' => "'corechart', 'bar'",
290 - 'charts' => "ColumnChart"
291 - ),
292 - 'area' => array(
293 - 'packages' => "'corechart'",
294 - 'charts' => "AreaChart"
295 - ),
296 - 'pie' => array(
297 - 'packages' => "'corechart'",
298 - 'charts' => "PieChart"
299 - ),
300 - '3dpie' => array(
301 - 'packages' => "'corechart'",
302 - 'charts' => "PieChart"
303 - )
304 - );
520 + $out = ''; $len = strlen($sql); $i = 0;
521 + while ($i < $len) {
522 + $c = $sql[$i];
523 + if ($c === "'" or $c === '"' or $c === '`') {
524 + $out .= $c . $c; $i++;
525 + while ($i < $len) {
526 + if ($sql[$i] === '\\' and $c !== '`') { $i += 2; continue; }
527 + if ($sql[$i] === $c) { if ($i + 1 < $len and $sql[$i + 1] === $c) { $i += 2; continue; } $i++; break; }
528 + $i++;
529 + }
530 + continue;
531 + }
532 + if ($c === '#' or ($c === '-' and substr($sql, $i, 2) === '--' and ($i + 2 >= $len or ctype_space($sql[$i + 2])))) {
533 + $nl = strpos($sql, "\n", $i); $i = ($nl === false) ? $len : $nl; continue;
534 + }
535 + if ($c === '/' and substr($sql, $i, 2) === '/*' and !in_array(substr($sql, $i + 2, 1), array('!', '+'), true)) {
536 + $close = strpos($sql, '*/', $i + 2); $i = ($close === false) ? $len : $close + 2; $out .= ' '; continue;
537 + }
538 + $out .= $c; $i++;
539 + }
540 + return $out;
541 +}
305 542
306 - return $stty[$type][$step];
307 -}
543 +// Returns 1 when the (fully substituted) SQL must not run, 0 when it is a read-only query.
544 +// Called after every {tag}/{argN} replacement so user-supplied values are covered too.
308 545 function gvn_chart_check_sql_query($sql)
309 546 {
310 - $blacklister = array(
311 - "delete",
312 - "update",
313 - "insert",
314 - "drop",
315 - "truncate",
316 - "alter"
317 - ); //add all
318 - $blacklister_f = 0;
319 - foreach ($blacklister as $key => $value) {
320 - if (strpos($sql, $value) !== false)
321 - $blacklister_f = 1;
547 + // 1) data-changing statements: checked on the raw text, exactly as in every previous version
548 + $write = '/\b(delete|update|insert|replace|drop|truncate|alter|create|rename|grant|revoke|call|handler|load\s+data|load_file|outfile|dumpfile)\b/i';
549 + if (preg_match($write, $sql)) return 1;
550 +
551 + // 2) further dangerous statements, matched outside string literals and comments so that ordinary
552 + // values such as status = 'reset' keep working
553 + $danger = '/\b(prepare|execute|deallocate|lock|unlock|kill|shutdown|flush|reset|purge|install|uninstall|import'
554 + . '|set\s+(?:global|session|persist|persist_only|password|@@)|start\s+(?:replica|slave|group_replication)|stop\s+(?:replica|slave)|change\s+(?:master|replication))\b/i';
555 + if (preg_match($danger, guaven_sqlcharts_strip_sql_literals($sql))) return 1;
556 +
557 + // 3) every ";"-separated statement must be a read statement. The renderer sends each segment to the
558 + // database on its own, so this stops a value from smuggling a second statement behind a ";".
559 + foreach (explode(';', $sql) as $segment) {
560 + $segment = ltrim(guaven_sqlcharts_strip_sql_literals($segment), " \t\r\n(");
561 + if ($segment === '') continue;
562 + if (!preg_match('/^(select|with|show|describe|desc|explain)\b/i', $segment)) return 1;
322 563 }
323 - return $blacklister_f;
564 + return 0;
324 565 }
325 566
326 567 function guaven_get_labels_and_values($id, $fvs)
327 568 {
@@ -327,22 +568,16 @@
327 568 {
328 569 $values = array();
329 570 $labels = array();
330 571 $xarg_s = get_post_meta($id, 'guaven_sqlcharts_xarg_s', true);
331 - $xarg_l = get_post_meta($id, 'guaven_sqlcharts_xarg_l', true);
332 572 $yarg_s = get_post_meta($id, 'guaven_sqlcharts_yarg_s', true);
333 - $yarg_l = get_post_meta($id, 'guaven_sqlcharts_yarg_l', true);
334 - $chartype = array(
335 - 'line_l' => 'Line',
336 - 'pie_l' => 'Pie',
337 - 'donut_l' => 'Pie',
338 - 'bar_l' => 'Bar',
339 - 'horizontalbar_l' => 'Horizontal Bar',
340 - 'area_l' => 'Line'
341 - );
573 + // labels are saved through esc_attr, so "&" is stored as "&amp;"; decode before splitting on ";"
574 + // or the entity's own ";" would be taken as a series separator
575 + $xarg_l = html_entity_decode((string) get_post_meta($id, 'guaven_sqlcharts_xarg_l', true), ENT_QUOTES, 'UTF-8');
576 + $yarg_l = html_entity_decode((string) get_post_meta($id, 'guaven_sqlcharts_yarg_l', true), ENT_QUOTES, 'UTF-8');
342 577 foreach ($fvs as $key => $value) {
343 - $values[] = $value->$yarg_s;
344 - $labels[] = '"' . $value->$xarg_s . '"';
578 + $values[$value->$xarg_s] = $value->$yarg_s;
579 + $labels[$value->$xarg_s] = '"' . $value->$xarg_s . '"';
345 580 }
346 581 return array(
347 582 $labels,
348 583 $values,
@@ -350,30 +585,51 @@
350 585 explode(";", $xarg_l)
351 586 );
352 587 }
353 588
354 -function guaven_sqlcharts_print_chart_js($tip_g, $title, $labels, $values, $ylabel, $pid = null)
589 +function guaven_sqlcharts_print_chart_js($print_data)
355 590 {
356 - if ($tip_g == 'line_l') {
357 - guaven_sqlcharts_linedata($title, $labels, $values, $ylabel, 'false', $pid);
591 + extract($print_data);
592 + $tip_g = guaven_sqlcharts_normalize_type($tip_g);
593 +
594 + switch ($tip_g) {
595 + case 'line_l':
596 + guaven_sqlcharts_linedata($title, $labels, $values, $ylabel, 'false', $pid);
597 + break;
598 + case 'area_l':
599 + guaven_sqlcharts_linedata($title, $labels, $values, $ylabel, 'true', $pid);
600 + break;
601 + case 'steppedline_l':
602 + guaven_sqlcharts_linedata($title, $labels, $values, $ylabel, 'false', $pid, 'line', true);
603 + break;
604 + case 'radar_l':
605 + guaven_sqlcharts_linedata($title, $labels, $values, $ylabel, 'radarfill', $pid, 'radar');
606 + break;
607 + case 'pie_l':
608 + guaven_sqlcharts_piedata($title, $labels, $values, $ylabel, $pid);
609 + break;
610 + case 'donut_l':
611 + guaven_sqlcharts_piedata($title, $labels, $values, $ylabel, $pid, 'doughnut');
612 + break;
613 + case 'polar_l':
614 + guaven_sqlcharts_piedata($title, $labels, $values, $ylabel, $pid, 'polarArea');
615 + break;
616 + case 'bar_l':
617 + guaven_sqlcharts_bardata($title, $labels, $values, $ylabel, 'bar', $pid);
618 + break;
619 + case 'horizontalbar_l':
620 + guaven_sqlcharts_bardata($title, $labels, $values, $ylabel, 'horizontalBar', $pid);
621 + break;
622 + case 'stackedbar_l':
623 + guaven_sqlcharts_bardata($title, $labels, $values, $ylabel, 'stackedBar', $pid);
624 + break;
625 + case 'scatter_l':
626 + guaven_sqlcharts_scatterdata($title, $labels, $values, $ylabel, $pid);
627 + break;
628 + case 'custom':
629 + guaven_sqlcharts_custom($title, $labels, $values, $ylabel, $pid);
630 + break;
358 631 }
359 - if ($tip_g == 'area_l') {
360 - guaven_sqlcharts_linedata($title, $labels, $values, $ylabel, 'true', $pid);
361 - } elseif ($tip_g == 'pie_l') {
362 - guaven_sqlcharts_piedata($title, $labels, $values, $ylabel, $pid);
363 - } elseif ($tip_g == 'donut_l') {
364 - guaven_sqlcharts_piedata($title, $labels, $values, $ylabel, $pid, 'doughnut');
365 - } elseif ($tip_g == 'bar_l') {
366 - guaven_sqlcharts_bardata($title, $labels, $values, $ylabel, 'bar', $pid);
367 - } elseif ($tip_g == 'horizontalbar_l') {
368 - guaven_sqlcharts_bardata($title, $labels, $values, $ylabel, 'horizontalBar', $pid);
369 - }
370 - elseif($tip_g == 'custom'){
371 - guaven_sqlcharts_custom($title, $labels, $values, $ylabel, $pid);
372 - }
373 - elseif ($tip_g == 'polar_l') {
374 - guaven_sqlcharts_piedata($title, $labels, $values, $ylabel, $pid, 'polarArea');
375 - }
376 632 }
377 633
378 634 function guaven_sqlcharts_custom($title, $labels, $values, $ylabel, $pid){
379 635 do_action('guaven_sqlcharts_custom',$title, $labels, $values, $ylabel, $pid);
@@ -398,10 +654,23 @@
398 654 foreach($variables_arr as $varfield){
399 655 $varfield_arr=explode("~",$varfield);
400 656 if (count($varfield_arr)<3) continue;
401 657 $varfield_arr=array_map("trim",$varfield_arr);
402 - if (!empty($_GET[$varfield_arr[0]])) $varreplacement=$_GET[$varfield_arr[0]]; else $varreplacement=$varfield_arr[1];
403 - if (!is_numeric($varreplacement) and strpos($varreplacement,'()')===false) $varreplacement='"'.$varreplacement.'"';
658 + if (!empty($_GET[$varfield_arr[0]])) {
659 + // User-supplied input: no () bypass allowed — sanitize strictly
660 + $varreplacement = str_replace(';', '', sanitize_text_field(wp_unslash($_GET[$varfield_arr[0]])));
661 + if (is_numeric($varreplacement)) {
662 + $varreplacement = $varreplacement + 0;
663 + } else {
664 + $varreplacement = '"' . esc_sql($varreplacement) . '"';
665 + }
666 + } else {
667 + // Admin-configured default value: allow () for SQL functions (e.g. NOW())
668 + $varreplacement = $varfield_arr[1];
669 + if (!is_numeric($varreplacement) && strpos($varreplacement,'()')===false) {
670 + $varreplacement = '"' . esc_sql($varreplacement) . '"';
671 + }
672 + }
404 673
405 674 $sql_initial=str_replace('{'.$varfield_arr[0].'}',$varreplacement,$sql_initial);
406 675 }
407 676 return $sql_initial;
@@ -419,38 +688,59 @@
419 688 $gvalue=!empty($_GET[$vrow_arr[0]])?esc_attr(urldecode($_GET[$vrow_arr[0]])):'';
420 689 $dvalue=(strpos($vrow_arr[1],'()')===false)?esc_attr($vrow_arr[1]):'';
421 690 if ($vrow_arr[3]=='date') {
422 691 $dateexists=true;
423 - $topform.= $vrow_arr[2].' <input autocomplete="off" style="max-width:210px" type="text"
692 + $topform.= '<span class="gvn-filter-field"><label>'.$vrow_arr[2].'</label> <input class="gws_datepicker" autocomplete="off" type="text"
424 693 value="'.$gvalue.'"
425 - data-toggle="datepicker" name="'.$vrow_arr[0].'" placeholder="'.$dvalue.'">
694 + data-toggle="datepicker" name="'.$vrow_arr[0].'" placeholder="'.$dvalue.'"></span>
426 695 ';}
427 696 else {
428 - $topform.= $vrow_arr[2].' <input autocomplete="off" style="max-width:210px;'.($vrow_arr[3]=='number'?'width:100px;':'').'"
697 + $topform.= '<span class="gvn-filter-field"><label>'.$vrow_arr[2].'</label> <input autocomplete="off"
429 698 type="'.$vrow_arr[3].'"
430 - value="'.$gvalue.'" name="'.$vrow_arr[0].'" placeholder="'.$dvalue.'">
699 + value="'.$gvalue.'" name="'.$vrow_arr[0].'" placeholder="'.$dvalue.'"></span>
431 700 ';
432 701 }
433 702 }
434 703 if (!empty($topform)) {
435 - $topform='<form method="get" action="" class="guaven_sqlcharts_form">'.$topform.'
436 - <input type="submit"
437 - value="'.(get_post_meta($atts['id'], 'guaven_sqlcharts_formpartbutton', true)!=''?esc_attr(get_post_meta($atts['id'], 'guaven_sqlcharts_formpartbutton', true)):'OK').'"></form>';
438 - if ($dateexists) $topform.='<script>setTimeout(function(){jQuery(\'[data-toggle="datepicker"]\').datepicker({format: \'yyyy-mm-dd\'});
439 -},300);</script>';
440 - return $topform ;
704 + $allowed_html = array(
705 + 'form' => array(
706 + 'method' => array(),
707 + 'action' => array(),
708 + 'class' => array()
709 + ),
710 + 'input' => array(
711 + 'type' => array(),
712 + 'value' => array(),
713 + 'name' => array(),
714 + 'class' => array(),
715 + 'data-toggle'=>array(),
716 + 'placeholder'=>array(),
717 + 'autocomplete'=>array(),
718 + 'style'=>[]
719 + ),
720 + 'span' => array('class' => array()),
721 + 'label' => array(),
722 + );
723 +
724 + $submit_button_value = get_post_meta($atts['id'], 'guaven_sqlcharts_formpartbutton', true) != ''
725 + ? esc_attr(get_post_meta($atts['id'], 'guaven_sqlcharts_formpartbutton', true))
726 + : 'OK';
727 +
728 + $topform = '<form method="get" action="" class="guaven_sqlcharts_form">' . $topform . '
729 + <input type="submit" value="' . $submit_button_value . '"></form>';
730 +
731 + echo wp_kses($topform, $allowed_html);
441 732 }
442 - return;
443 733 }
444 734
445 735
446 -function guaven_sqlcharts_encrypt_decrypt($action, $string)
736 +function guaven_sqlcharts_encrypt_decrypt($action, $string)
447 737 {
448 738 $output = false;
449 739 $encrypt_method = "AES-256-CBC";
450 740 $secret_key = 'GWSCHARTPL2022.2016.';
451 741 $secret_iv = 'GWSCHARTPL2016.2022';
452 - $key = hash('sha256', $secret_key);
742 + $key = hash('sha256', $secret_key);
453 743 $iv = substr(hash('sha256', $secret_iv), 0, 16);
454 744 if ( $action == 'encrypt' ) {
455 745 $output = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
456 746 $output = base64_encode($output);
@@ -459,10 +749,13 @@
459 749 }
460 750 return $output;
461 751 }
462 752
463 -function guaven_sqlcharts_local_shortcode($atts)
464 -{
753 +function guaven_sqlcharts_local_shortcode($atts) {
754 + if(empty($atts['id']))return 'ID is missing.';
755 + $atts['id']=intval($atts['id']);
756 + $post_g = get_post($atts['id']);
757 + if (!$post_g or $post_g->post_type != 'gvn_schart') return 'Chart not found.';
465 758 $remote_host=get_post_meta($atts['id'], 'guaven_sqlcharts_dbhost', true);
466 759 if ($remote_host!=''){
467 760 $remote_db=get_post_meta($atts['id'], 'guaven_sqlcharts_dbname', true);
468 761 $remote_login=get_post_meta($atts['id'], 'guaven_sqlcharts_dblogin', true);
@@ -477,22 +770,39 @@
477 770 }
478 771
479 772 $GLOBALS["guaven_sqlcharts_atts"]=$atts;
480 773
481 - $sql = html_entity_decode(get_post_meta($atts['id'], 'guaven_sqlcharts_code', true));
774 + $sql = guaven_sqlcharts_get_code($atts['id']);
775 + if(empty($sql))return 'SQL query is missing.';
776 +
777 + // {arg1}..{arg19} come from shortcode attributes: [gvn_schart_2 id="1" arg1="41"].
778 + // Substituted directly (not via wpdb::prepare) so the same tag may appear any number of times,
779 + // e.g. in every query of a ";"-separated comparison chart. Numbers are inserted as-is, anything
780 + // else is escaped and quoted; a tag already wrapped in quotes ('{arg1}') is not double-quoted.
781 + // ";" is removed from values because the finished SQL is split on ";" below.
782 + for($i=1;$i<20;$i++){
783 + $tag = '{arg'.$i.'}';
784 + if (strpos($sql, $tag) === false) continue;
785 + $replacearg = !empty($atts['arg'.$i]) ? $atts['arg'.$i] : 0;
786 + if (is_numeric($replacearg)) $replacearg = $replacearg + 0;
787 + else $replacearg = "'" . esc_sql(str_replace(';', '', sanitize_text_field((string) $replacearg))) . "'";
788 + $sql = str_replace(array("'".$tag."'", '"'.$tag.'"', $tag), $replacearg, $sql);
789 + }
790 +
482 791 $sql=gvn_chart_put_variables($sql,$atts['id']);
792 + $sql=apply_filters('guaven_sqlcharts_rendered_sql',$sql,$atts);
793 +
794 + // command check on the final SQL, after every shortcode argument and filter value is in place
483 795 $blacklister_f = gvn_chart_check_sql_query($sql);
484 - if ($blacklister_f == 1)
485 - return 'You given SQL code contains forbidden commands. Remember that you should only use SELECT queries';
486 - $tip_g = get_post_meta($atts['id'], 'guaven_sqlcharts_graphtype', true);
796 + if ($blacklister_f == 1)return 'You given SQL code contains forbidden commands. Remember that you should only use SELECT queries';
797 + $tip_g = guaven_sqlcharts_normalize_type(get_post_meta($atts['id'], 'guaven_sqlcharts_graphtype', true));
487 798
488 - for($i=1;$i<20;$i++){ $replacearg=!empty($atts["arg".$i])?$atts["arg".$i]:0;
489 - $sql=str_replace("{arg".$i."}",esc_sql($replacearg),$sql);}
490 799 $sql_split = explode(';', $sql);
491 800 $labels_and_values = array();
492 - $post_g = get_post($atts['id']);
801 + $labels = $values = $ylabel = $xlabel = array();
493 802
494 803 global $sqlcharts_inserted_script;
804 + ob_start();
495 805 for ($i = 0; $i < count($sql_split); $i++) {
496 806 if (!empty($sql_split[$i])) {
497 807
498 808 $fvs = $wpdb->get_results($sql_split[$i]);
@@ -500,52 +810,104 @@
500 810 $wpdb->show_errors();
501 811 ob_start();
502 812 $wpdb->print_error();
503 813 $printerror = ob_get_clean();
504 - if ($printerror != '' and strpos($printerror, "[]") === false)
814 + if ($printerror != '' and strpos($printerror, "[]") === false){
815 + ob_end_clean();
505 816 return $printerror;
506 - elseif (empty($fvs))
507 - return 'Your SQL returnes empty date, please recheck your SQL query above';
817 + }
818 + elseif (empty($fvs)){
819 + ob_end_clean();
820 + return 'Your SQL returnes empty data, please recheck your SQL query above';
821 + }
508 822 }
509 823
510 - ob_start();
511 -
512 824 if (empty($sqlcharts_inserted_script))
513 825 $sqlcharts_inserted_script = 1;
514 - $labels_and_values[$i] = guaven_get_labels_and_values($atts['id'], $fvs);
515 - $labels[$i] = $labels_and_values[$i][0];
516 - $values[$i] = $labels_and_values[$i][1];
517 - $ylabel[$i] = !empty($labels_and_values[$i][2][$i]) ? $labels_and_values[$i][2][$i] : '';
518 - $xlabel[$i] = !empty($labels_and_values[$i][3][$i]) ? $labels_and_values[$i][3][$i] : '';
826 + $labels_and_values[$i] = guaven_get_labels_and_values($atts['id'], $fvs);
827 + $labels[$i] = $labels_and_values[$i][0];
828 + $values[$i] = $labels_and_values[$i][1];
829 + $ylabel[$i] = !empty($labels_and_values[$i][2][$i]) ? $labels_and_values[$i][2][$i] : '';
830 + $xlabel[$i] = !empty($labels_and_values[$i][3][$i]) ? $labels_and_values[$i][3][$i] : '';
519 831 }
520 832 }
521 833
522 -echo gvn_chart_top_form($atts);
523 -?>
524 - <canvas
525 - id="ct-chart_<?php echo $sqlcharts_inserted_script; ?>"
834 + gvn_chart_top_form($atts);
835 +
836 + // shortcode width/height attributes override the saved defaults
837 + $chart_w = !empty($atts['width']) ? $atts['width'] : get_post_meta($atts['id'], 'guaven_sqlcharts_chartwidth', true);
838 + $chart_h = !empty($atts['height']) ? $atts['height'] : get_post_meta($atts['id'], 'guaven_sqlcharts_chartheight', true);
839 + $wrap_style = '';
840 + if ($chart_w != '') $wrap_style .= 'max-width:' . (int) $chart_w . 'px;';
841 + if ($chart_h != '') $wrap_style .= 'height:' . (int) $chart_h . 'px;';
842 + ?>
843 + <div class="gvn-chartwrap"<?php echo $wrap_style != '' ? ' style="' . esc_attr($wrap_style) . '"' : ''; ?>>
844 + <canvas
845 + id="ct-chart_<?php echo esc_attr($sqlcharts_inserted_script); ?>"
526 846 class="guaven_chart_canvas"
527 - style="width: <?php echo get_post_meta($atts['id'], 'guaven_sqlcharts_chartwidth', true); ?>px !important; height: <?php echo get_post_meta($atts['id'], 'guaven_sqlcharts_chartheight', true); ?>px !important;"
528 847 ></canvas>
848 + </div>
529 849
530 850 <script type="text/javascript" class="gvn_charts_script" async>
531 851 var ctx = jQuery("#ct-chart_<?php
532 - echo $sqlcharts_inserted_script;
533 -?>");
852 + echo esc_attr($sqlcharts_inserted_script);
853 + ?>");
534 854
535 -<?php
536 - guaven_sqlcharts_print_chart_js($tip_g, $post_g->post_title, $labels, $values, $ylabel, $atts['id']);
537 -?>
538 -</script>
855 + <?php
856 + $print_data=apply_filters('guaven_sqlcharts_pre_print_vars',['tip_g'=>$tip_g, 'title'=>$post_g->post_title,
857 + 'labels'=>$labels, 'values'=>$values, 'ylabel'=>$ylabel, 'pid'=>$atts['id']]);
858 + guaven_sqlcharts_print_chart_js($print_data);
859 + ?>
860 + </script>
539 861
540 -<?php
541 - if (!empty($atts["table"])) echo guaven_sqlcharts_tablepart($post_g->post_title, $labels, $values, $ylabel,$xlabel);
862 + <?php
863 + if (!empty($atts["table"])) guaven_sqlcharts_tablepart($post_g->post_title, $labels, $values, $ylabel,$xlabel);
542 864 $sqlcharts_inserted_script++;
543 - return ob_get_clean();
865 + $ret=ob_get_clean();
866 + $ret=apply_filters( 'guaven_sqlcharts_final_output', $ret, $atts );
867 + return $ret;
544 868 }
545 869
546 870 add_shortcode('gvn_schart_2', 'guaven_sqlcharts_local_shortcode');
547 871
872 +// legacy alias: old Google-Chart era posts produced [gvn_schart id=".."] shortcodes
873 +if (!shortcode_exists('gvn_schart')) {
874 + add_shortcode('gvn_schart', 'guaven_sqlcharts_local_shortcode');
875 +}
876 +
877 +// [gvn_schart_2_cached id="1" expire="3600" arg1=".."] – same as gvn_schart_2 but the output is kept in a
878 +// transient. All other attributes (argN, width, height, table, params) are passed through, and each
879 +// distinct set of attributes gets its own cache entry. Append ?force_sql_cache_reload to the URL to bypass.
880 +add_shortcode("gvn_schart_2_cached",function($atts){
881 + if(empty($atts["id"]))return;
882 + $atts["id"]=intval($atts["id"]);
883 + $expire=!empty($atts["expire"])?intval($atts["expire"]):3600;
884 + $inner_atts=$atts;
885 + unset($inner_atts['expire']);
886 + // One cache entry per user (charts may use {current_user_*} tags), per set of shortcode attributes
887 + // and per value of every dynamic filter this chart reads from the URL. A visitor can therefore
888 + // never be served, or pre-seed, a result computed for someone else or for other filter values.
889 + $key_parts = array('atts' => $inner_atts, 'user' => is_user_logged_in() ? get_current_user_id() : 0, 'get' => array());
890 + foreach (explode('|', (string) get_post_meta($atts['id'], 'guaven_sqlcharts_variables', true)) as $vrow) {
891 + $vname = trim(current(explode('~', $vrow)));
892 + if ($vname !== '' and isset($_GET[$vname])) $key_parts['get'][$vname] = sanitize_text_field(wp_unslash($_GET[$vname]));
893 + }
894 + $key = 'cached_sql_charts_' . $atts["id"] . '_' . md5(serialize($key_parts));
895 + $cached=get_transient($key);
896 + if(!empty($cached) and !isset($_GET["force_sql_cache_reload"]) )return $cached;
897 + $tobecached=guaven_sqlcharts_local_shortcode($inner_atts);
898 + set_transient($key, $tobecached,$expire);
899 + return $tobecached;
900 +});
901 +
902 +// fixed, colorblind-friendly default palette (Tableau 10) used when no custom colors are set
903 +function guaven_sqlcharts_default_palette(){
904 + return apply_filters('guaven_sqlcharts_default_palette', array(
905 + '#4E79A7', '#F28E2B', '#E15759', '#76B7B2', '#59A14F',
906 + '#EDC948', '#B07AA1', '#FF9DA7', '#9C755F', '#BAB0AC'
907 + ));
908 +}
909 +
548 910 function guaven_sqlcharts_colors($index, $pid = null){
549 911 if(!isset($pid)) {
550 912 global $post;
551 913 $pid = $post->ID;
@@ -552,56 +914,124 @@
552 914 }
553 915 $colors=get_post_meta($pid,'guaven_sqlcharts_colors',true);
554 916 $colors=explode(",",$colors);
555 917 if (!empty($colors[$index])) return $colors[$index];
556 - return rand(0, 255) . ',' . rand(0, 255) . ',' . rand(0, 255);
918 + $palette = guaven_sqlcharts_default_palette();
919 + return $palette[$index % count($palette)];
557 920 }
558 921
559 -function guaven_sqlcharts_toshowlegend($pid){
560 - $guaven_sqlcharts_legend_position=get_post_meta($pid, 'guaven_sqlcharts_legend_position', true);
561 - if(in_array($guaven_sqlcharts_legend_position,['top','bottom','left','right'])){
562 - $display='true';$position=$guaven_sqlcharts_legend_position;
922 +// outputs 'maintainAspectRatio:false,' when an explicit height is set, so the
923 +// chart fills its sized wrapper instead of keeping the default aspect ratio
924 +function guaven_sqlcharts_mar($pid){
925 + $atts = isset($GLOBALS["guaven_sqlcharts_atts"]) ? $GLOBALS["guaven_sqlcharts_atts"] : array();
926 + $h = !empty($atts['height']) ? $atts['height'] : get_post_meta($pid, 'guaven_sqlcharts_chartheight', true);
927 + return $h != '' ? 'maintainAspectRatio: false,' : '';
928 +}
929 +
930 +// outputs 'showAllTooltips: true,' when "Value labels" is checked; the values are drawn by the
931 +// gvnShowAllValues plugin in asset/front.js (works for every chart type)
932 +function guaven_sqlcharts_value_labels($pid){
933 + return get_post_meta($pid, 'guaven_sqlcharts_forcetooltips', true) != '' ? 'showAllTooltips: true,' : '';
934 +}
935 +
936 +// Chart.js scale title block built from the "X axis label" / "Y axis label" fields.
937 +// $which is 'x' or 'y' (the *field* to use, not the scale). The Y label is only used as an axis
938 +// title for single-series charts; with several ";"-separated series the legend names them instead.
939 +function guaven_sqlcharts_axis_title($pid, $which){
940 + $key = $which == 'x' ? 'guaven_sqlcharts_xarg_l' : 'guaven_sqlcharts_yarg_l';
941 + $text = trim(html_entity_decode((string) get_post_meta($pid, $key, true), ENT_QUOTES, 'UTF-8'));
942 + if ($text === '' or ($which == 'y' and strpos($text, ';') !== false)) return '';
943 + return 'title: {display: true, text: ' . wp_json_encode($text) . '},';
944 +}
945 +
946 +// "params" shortcode attribute: extra Chart.js dataset options, e.g. params="borderWidth: 3, borderDash: [5,5],".
947 +// The text is placed inside the inline <script>, so only a conservative character set is accepted:
948 +// no parentheses, semicolons, "=", "<", ">", "/", "\\", "+" or backticks, which rules out executable JavaScript.
949 +function guaven_sqlcharts_dataset_params(){
950 + $params = isset($GLOBALS["guaven_sqlcharts_atts"]["params"]) ? (string) $GLOBALS["guaven_sqlcharts_atts"]["params"] : '';
951 + if ($params === '' or !preg_match('/^[A-Za-z0-9_\s,:.\'"#%\-\[\]{}]+$/', $params)) return '';
952 + return $params;
953 +}
954 +
955 +// dataset label as a safe JS string literal (labels saved before 3.0.1 may hold HTML entities)
956 +function guaven_sqlcharts_js_label($label){
957 + return wp_json_encode(html_entity_decode((string) $label, ENT_QUOTES, 'UTF-8'));
958 +}
959 +
960 +// Parses an X value for the "time axis" option. Accepts YYYY, YYYY-MM, YYYY-MM-DD, optionally followed
961 +// by HH:MM or HH:MM:SS. Returns a UTC timestamp in milliseconds, or false when the value is not a date.
962 +function guaven_sqlcharts_parse_date($str){
963 + $str = trim((string) $str);
964 + if (!preg_match('/^(\d{4})(?:-(\d{1,2})(?:-(\d{1,2})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2}))?)?)?)?$/', $str, $m)) return false;
965 + $y = (int) $m[1]; $mo = isset($m[2]) ? (int) $m[2] : 1; $d = isset($m[3]) ? (int) $m[3] : 1;
966 + $h = isset($m[4]) ? (int) $m[4] : 0; $mi = isset($m[5]) ? (int) $m[5] : 0; $sec = isset($m[6]) ? (int) $m[6] : 0;
967 + if (!checkdate($mo, $d, $y) or $h > 23 or $mi > 59 or $sec > 59) return false;
968 + return gmmktime($h, $mi, $sec, $mo, $d, $y) * 1000;
969 +}
970 +
971 +// "Scale X axis by date/time" option. Returns, per dataset, a list of "{x:<ms>,y:<value>}" JS point
972 +// literals when the option is on and every X value is a date; false otherwise (normal category axis).
973 +function guaven_sqlcharts_time_axis_points($pid, $values){
974 + if (get_post_meta($pid, 'guaven_sqlcharts_timeaxis', true) != 1) return false;
975 + $out = array();
976 + $has_point = false;
977 + foreach ($values as $key_ak => $series) {
978 + $out[$key_ak] = array();
979 + foreach ($series as $x => $y) {
980 + $ts = guaven_sqlcharts_parse_date($x);
981 + if ($ts === false) return false;
982 + $out[$key_ak][] = '{x:' . $ts . ',y:' . (is_numeric($y) ? $y + 0 : 'null') . '}';
983 + $has_point = true;
984 + }
563 985 }
564 - else {
565 - $display='false';$position='top';
566 - }
567 - return "legend: {display: ".$display.",position:'".$position."'},";
986 + return $has_point ? $out : false;
568 987 }
569 988
989 +// X scale options for time-axis mode; gvnSqlChartsTimeTick (asset/front.js) formats the ticks as dates
990 +function guaven_sqlcharts_time_axis_scale(){
991 + return "type: 'linear', offset: true, ticks: {callback: gvnSqlChartsTimeTick, maxRotation: 45},";
992 +}
993 +// extra entry for the Chart.js "plugins" object in time-axis mode (tooltip title shown as a date)
994 +function guaven_sqlcharts_time_axis_plugins($time_points){
995 + return $time_points !== false ? 'tooltip: {callbacks: {title: gvnSqlChartsTimeTooltipTitle}}' : '';
996 +}
997 +
570 998 function guaven_sqlcharts_bardata($title, $labels, $values, $ylabel, $type = 'bar', $pid = null)
571 999 {
1000 + $horizontal = ($type == 'horizontalBar');
1001 + $forcestack = ($type == 'stackedBar');
1002 + $stacked = ($forcestack or get_post_meta($pid, 'guaven_sqlcharts_nostacked', true) != 1) ? 'true' : 'false';
1003 + $time_points = $horizontal ? false : guaven_sqlcharts_time_axis_points($pid, $values);
572 1004 ?>
573 1005 var data = {
574 - labels: [<?php
575 - echo implode(",", $labels[0]);
576 -?>],
1006 + <?php if ($time_points === false) { ?>labels: [<?php guaven_sqlcharts_merge_labeldata($labels);?>],<?php } ?>
577 1007 datasets: [
578 1008 <?php
579 - for ($i = 0; $i < count($values); $i++) {
1009 + $values_new=guaven_sqlcharts_key_normalizer($values,$labels,$ylabel)[0];
1010 + $i=-1;
1011 + foreach ($values_new as $key_ak=>$value_ak) {
1012 + $i++;
1013 + $points = $time_points !== false ? $time_points[$key_ak] : $values_new[$key_ak];
580 1014 ?>
581 1015 {
582 - <?php
583 - if(!empty($GLOBALS["guaven_sqlcharts_atts"]["params"])){
584 - //passing chartJS params via the shortcode
585 - echo esc_js($GLOBALS["guaven_sqlcharts_atts"]["params"]);
586 - }
1016 + <?php
1017 + echo guaven_sqlcharts_dataset_params(); // "params" shortcode attribute (validated)
587 1018 ?>
588 - label: "<?php
589 - echo esc_attr($ylabel[$i]);
590 -?>",
1019 + label: <?php echo guaven_sqlcharts_js_label($ylabel[$key_ak]); ?>,
591 1020 backgroundColor: [
592 1021 <?php
593 - echo guaven_sqlcharts_colorgenerator(count($values[$i]), 0, 0, guaven_sqlcharts_colors($i, $pid));
1022 + echo wp_kses(guaven_sqlcharts_colorgenerator(count($points), 0, 0, guaven_sqlcharts_colors($i, $pid)),[]);
594 1023 ?>
595 1024 ],
596 1025 borderColor: [
597 1026 <?php
598 - echo guaven_sqlcharts_colorgenerator(count($values[$i]), 0, 0.2, guaven_sqlcharts_colors($i, $pid));
1027 + echo wp_kses(guaven_sqlcharts_colorgenerator(count($points), 0, 0.2, guaven_sqlcharts_colors($i, $pid)),[]);
599 1028 ?>
600 1029 ],
601 1030 borderWidth: 1,
1031 + <?php if ($time_points !== false) echo 'maxBarThickness: 48,'; ?>
602 1032 data: [<?php
603 - echo implode(",", $values[$i]);
1033 + echo wp_kses(implode(",", $points),[]);
604 1034 ?>],
605 1035 },
606 1036 <?php
607 1037 }
@@ -608,30 +1038,34 @@
608 1038 ?>
609 1039 ]
610 1040 };
611 1041 var options={
612 - <?php echo guaven_sqlcharts_toshowlegend($pid);?>
613 1042 responsive: true,
1043 + <?php echo wp_kses(guaven_sqlcharts_mar($pid),[]); ?>
1044 + <?php echo wp_kses(guaven_sqlcharts_value_labels($pid),[]); ?>
1045 + <?php if ($horizontal) echo "indexAxis: 'y',"; ?>
614 1046 scales: {
615 - xAxes: [{
616 - <?php echo (get_post_meta($pid, 'guaven_sqlcharts_nostacked', true) == 1) ? '':'stacked: true,';?>
1047 + x: {
1048 + <?php if ($time_points !== false) echo guaven_sqlcharts_time_axis_scale(); ?>
1049 + <?php echo guaven_sqlcharts_axis_title($pid, $horizontal ? 'y' : 'x'); ?>
1050 + stacked: <?php echo esc_js($stacked); ?>,
1051 + beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_x', true) == 1) ? 'true':'false'; ?>
1052 + },
1053 + y: {
1054 + <?php echo guaven_sqlcharts_axis_title($pid, $horizontal ? 'x' : 'y'); ?>
1055 + stacked: <?php echo esc_js($stacked); ?>,
1056 + beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_y', true) == 1) ? 'true':'false'; ?>,
617 1057 ticks: {
618 - beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_x', true) == 1) ? 'true':'false'; ?>
619 - }
620 - }],
621 - yAxes: [{
622 - <?php echo (get_post_meta($pid, 'guaven_sqlcharts_nostacked', true) == 1) ? '':'stacked: true,';?>
623 - ticks: {
624 1058 <?php if(get_post_meta($pid, 'guaven_sqlcharts_round_y_values', true) == 1) echo 'precision: 0,'; ?>
625 - beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_y', true) == 1) ? 'true':'false'; ?>
626 1059 }
627 - }]
1060 + }
628 1061 }
1062 + <?php
1063 + guaven_sqlcharts_maybe_additional_parameters($pid, guaven_sqlcharts_time_axis_plugins($time_points));
1064 + ?>
629 1065 };
630 1066 var myBarChart = new Chart(ctx, {
631 - type: '<?php
632 - echo $type;
633 -?>',
1067 + type: 'bar',
634 1068 data: data,
635 1069 options: options
636 1070 });
637 1071 <?php
@@ -636,51 +1070,61 @@
636 1070 });
637 1071 <?php
638 1072 }
639 1073
1074 +function guaven_sqlcharts_merge_labeldata($labels){
1075 + if(count($labels)==1){echo wp_kses(implode(",",$labels[0]),[]);return;}
1076 + $merged=[];
1077 + foreach($labels as $label){
1078 + $merged=array_merge($merged,$label);
1079 + }
1080 + echo wp_kses(implode(",",array_unique($merged)),[]);
1081 +}
640 1082
641 -
642 -function guaven_sqlcharts_linedata($title, $labels, $values, $ylabel, $type = 'false', $pid = null)
1083 +function guaven_sqlcharts_linedata($title, $labels, $values, $ylabel, $type = 'false', $pid = null, $charttype = 'line', $stepped = false)
643 1084 {
1085 + $time_points = ($charttype == 'radar') ? false : guaven_sqlcharts_time_axis_points($pid, $values);
644 1086 ?>
645 1087 var data = {
646 - labels: [<?php
647 - echo implode(",", $labels[0]);
648 -?>],
1088 + <?php if ($time_points === false) { ?>labels: [<?php guaven_sqlcharts_merge_labeldata($labels);?>],<?php } ?>
649 1089 datasets: [
650 1090 <?php
651 - for ($i = 0; $i < count($values); $i++) {
1091 + $values_new=guaven_sqlcharts_key_normalizer($values,$labels,$ylabel)[0];
1092 + $dataset_count=count($values_new);
1093 + $i=-1;
1094 + foreach ($values_new as $key_ak=>$value_ak) {
1095 + $i++;
1096 + $points = $time_points !== false ? $time_points[$key_ak] : $values_new[$key_ak];
1097 + if ($type == 'radarfill') $fill = "'origin'";
1098 + elseif ($type == 'false') $fill = 'false';
1099 + else $fill = ($i == 0 and $dataset_count > 1) ? '"+1"' : '"origin"';
652 1100 ?>
653 1101 {
654 - <?php
655 - if(!empty($GLOBALS["guaven_sqlcharts_atts"]["params"])){
656 - //passing chartJS params via the shortcode
657 - echo esc_js($GLOBALS["guaven_sqlcharts_atts"]["params"]);
658 - }
1102 + <?php
1103 + echo guaven_sqlcharts_dataset_params(); // "params" shortcode attribute (validated)
659 1104 ?>
660 - label: "<?php
661 - echo esc_attr($ylabel[$i]);
662 -?>",
663 - fill: <?php echo $type=="false"?$type:($i==0?'"+1"':'"origin"');
1105 + label: <?php echo guaven_sqlcharts_js_label($ylabel[$key_ak]); ?>,
1106 + fill: <?php echo wp_kses($fill,[]);
664 1107 ?>,
665 - lineTension: 0.1,
1108 + tension: 0.1,
1109 + <?php if ($stepped) echo 'stepped: true,'; ?>
666 1110 backgroundColor: <?php
667 - echo guaven_sqlcharts_colorgenerator(1, 1, 0.2, guaven_sqlcharts_colors($i, $pid));
1111 + echo wp_kses_post(guaven_sqlcharts_colorgenerator(1, 1, 0.2, guaven_sqlcharts_colors($i, $pid)));
668 1112 ?>
669 1113 borderColor: <?php
670 - echo guaven_sqlcharts_colorgenerator(1, 1, 0.2, guaven_sqlcharts_colors($i, $pid));
1114 + echo wp_kses_post(guaven_sqlcharts_colorgenerator(1, 1, 0.2, guaven_sqlcharts_colors($i, $pid)));
671 1115 ?>
672 1116 pointBorderColor: <?php
673 - echo guaven_sqlcharts_colorgenerator(1, 1, 0.2, guaven_sqlcharts_colors($i, $pid));
1117 + echo wp_kses_post(guaven_sqlcharts_colorgenerator(1, 1, 0.2, guaven_sqlcharts_colors($i, $pid)));
674 1118 ?>
675 1119 pointHoverBackgroundColor: <?php
676 - echo guaven_sqlcharts_colorgenerator(1, 1, 0.2, guaven_sqlcharts_colors($i, $pid));
1120 + echo wp_kses_post(guaven_sqlcharts_colorgenerator(1, 1, 0.2, guaven_sqlcharts_colors($i, $pid)));
677 1121 ?>
678 1122 pointHoverBorderColor: <?php
679 - echo guaven_sqlcharts_colorgenerator(1, 1, 0.2, guaven_sqlcharts_colors($i, $pid));
1123 + echo wp_kses_post(guaven_sqlcharts_colorgenerator(1, 1, 0.2, guaven_sqlcharts_colors($i, $pid)));
680 1124 ?>
681 1125 data: [<?php
682 - echo implode(",", $values[$i]);
1126 + echo wp_kses_post(implode(",", $points));
683 1127 ?>],
684 1128 spanGaps: false,
685 1129 },
686 1130 <?php
@@ -688,27 +1132,103 @@
688 1132 ?>
689 1133 ]
690 1134 };
691 1135 var myLineChart = new Chart(ctx, {
692 - type: 'line',
1136 + type: '<?php echo esc_attr($charttype); ?>',
693 1137 data: data,
694 1138 options: {
695 - <?php echo guaven_sqlcharts_toshowlegend($pid);?>
696 1139 responsive: true,
1140 + <?php echo wp_kses(guaven_sqlcharts_mar($pid),[]); ?>
1141 + <?php echo wp_kses(guaven_sqlcharts_value_labels($pid),[]); ?>
1142 + <?php if ($charttype == 'radar') { ?>
697 1143 scales: {
698 - xAxes: [{
1144 + r: {
1145 + beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_y', true) == 1) ? 'true':'false'; ?>
1146 + }
1147 + }
1148 + <?php } else { ?>
1149 + scales: {
1150 + x: {
699 1151 display: true,
1152 + <?php if ($time_points !== false) echo guaven_sqlcharts_time_axis_scale(); ?>
1153 + <?php echo guaven_sqlcharts_axis_title($pid, 'x'); ?>
1154 + beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_x', true) == 1) ? 'true':'false'; ?>
1155 + },
1156 + y: {
1157 + <?php echo guaven_sqlcharts_axis_title($pid, 'y'); ?>
1158 + beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_y', true) == 1) ? 'true':'false'; ?>,
700 1159 ticks: {
701 - beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_x', true) == 1) ? 'true':'false'; ?>
1160 + <?php if(get_post_meta($pid, 'guaven_sqlcharts_round_y_values', true) == 1) echo 'precision: 0,'; ?>
702 1161 }
703 - }],
704 - yAxes: [{
1162 + }
1163 + }
1164 + <?php } ?>
1165 + <?php
1166 + guaven_sqlcharts_maybe_additional_parameters($pid, guaven_sqlcharts_time_axis_plugins($time_points));
1167 + ?>
1168 +
1169 + }
1170 +});
1171 + <?php
1172 +}
1173 +
1174 +function guaven_sqlcharts_scatterdata($title, $labels, $values, $ylabel, $pid = null)
1175 +{
1176 +?>
1177 +var data = {
1178 + datasets: [
1179 + <?php
1180 + $i=-1;
1181 + foreach ($values as $key_ak=>$value_ak) {
1182 + $i++;
1183 + $points=array();
1184 + foreach ($value_ak as $xval=>$yval) {
1185 + $x = is_numeric($xval) ? $xval : '"'.esc_js($xval).'"';
1186 + $y = is_numeric($yval) ? $yval : '"'.esc_js($yval).'"';
1187 + $points[] = '{x:'.$x.',y:'.$y.'}';
1188 + }
1189 +?>
1190 + {
1191 + <?php
1192 + echo guaven_sqlcharts_dataset_params(); // "params" shortcode attribute (validated)
1193 + ?>
1194 + label: <?php echo guaven_sqlcharts_js_label(isset($ylabel[$key_ak])?$ylabel[$key_ak]:''); ?>,
1195 + backgroundColor: <?php
1196 + echo wp_kses_post(guaven_sqlcharts_colorgenerator(1, 1, 0.2, guaven_sqlcharts_colors($i, $pid)));
1197 +?>
1198 + borderColor: <?php
1199 + echo wp_kses_post(guaven_sqlcharts_colorgenerator(1, 1, 0.2, guaven_sqlcharts_colors($i, $pid)));
1200 +?>
1201 + data: [<?php echo wp_kses(implode(",", $points),[]); ?>],
1202 + },
1203 + <?php
1204 + }
1205 +?>
1206 + ]
1207 +};
1208 +var myScatterChart = new Chart(ctx, {
1209 + type: 'scatter',
1210 + data: data,
1211 + options: {
1212 + responsive: true,
1213 + <?php echo wp_kses(guaven_sqlcharts_mar($pid),[]); ?>
1214 + <?php echo wp_kses(guaven_sqlcharts_value_labels($pid),[]); ?>
1215 + scales: {
1216 + x: {
1217 + <?php echo guaven_sqlcharts_axis_title($pid, 'x'); ?>
1218 + beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_x', true) == 1) ? 'true':'false'; ?>
1219 + },
1220 + y: {
1221 + <?php echo guaven_sqlcharts_axis_title($pid, 'y'); ?>
1222 + beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_y', true) == 1) ? 'true':'false'; ?>,
705 1223 ticks: {
706 - <?php if(get_post_meta($pid, 'guaven_sqlcharts_round_y_values', true) == 1) echo 'precision: 0,'; ?>
707 - beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_y', true) == 1) ? 'true':'false'; ?>
1224 + <?php if(get_post_meta($pid, 'guaven_sqlcharts_round_y_values', true) == 1) echo 'precision: 0,'; ?>
708 1225 }
709 - }]
1226 + }
710 1227 }
1228 + <?php
1229 + guaven_sqlcharts_maybe_additional_parameters($pid);
1230 + ?>
711 1231 }
712 1232 });
713 1233 <?php
714 1234 }
@@ -713,43 +1233,66 @@
713 1233 <?php
714 1234 }
715 1235
716 1236
1237 +function guaven_sqlcharts_maybe_additional_parameters($pid, $extra_plugins = ''){
1238 + if(function_exists('guaven_sqlcharts_maybe_additional_parameters_custom')){
1239 + wp_kses(guaven_sqlcharts_maybe_additional_parameters_custom($pid),[]);
1240 + return;
1241 + }
1242 + $guaven_sqlcharts_legend_position=get_post_meta($pid, 'guaven_sqlcharts_legend_position', true);
1243 + if(in_array($guaven_sqlcharts_legend_position,['top','bottom','left','right'])){
1244 + $display='true';$position=$guaven_sqlcharts_legend_position;
1245 + }
1246 + else {
1247 + $display='false';$position='top';
1248 + }
1249 + echo wp_kses( ",plugins: {legend: {display: ".$display.",position:'".$position."'}".($extra_plugins !== '' ? ','.$extra_plugins : '')."}",[]);
1250 +}
1251 +
1252 +
1253 +
1254 +
717 1255 function guaven_sqlcharts_piedata($title, $labels, $values, $ylabel, $pid, $type = 'pie')
718 1256 {
719 1257 ?>
720 1258 var options={
721 - <?php echo guaven_sqlcharts_toshowlegend($pid);?>
722 - responsive: true,
1259 + <?php echo wp_kses(guaven_sqlcharts_value_labels($pid),[]); ?>
1260 + responsive: true
1261 + <?php echo get_post_meta($pid,'guaven_sqlcharts_chartheight',true)!=''||!empty($GLOBALS["guaven_sqlcharts_atts"]['height'])?',maintainAspectRatio: false':''; ?>
1262 + <?php
1263 + guaven_sqlcharts_maybe_additional_parameters($pid);
1264 + ?>
723 1265 };
724 1266 var data = {
725 - labels: [ <?php echo implode(",", $labels[0]);?>],
1267 + labels: [ <?php guaven_sqlcharts_merge_labeldata($labels);?>],
726 1268 datasets: [
727 1269 <?php
728 1270 for ($i = 0; $i < count($values); $i++) {
729 1271 ?>
730 1272 {
731 - <?php
732 - if(!empty($GLOBALS["guaven_sqlcharts_atts"]["params"])){
733 - //passing chartJS params via the shortcode
734 - echo esc_js($GLOBALS["guaven_sqlcharts_atts"]["params"]);
735 - }
1273 + <?php
1274 + echo guaven_sqlcharts_dataset_params(); // "params" shortcode attribute (validated)
736 1275 ?>
737 1276 data: [<?php
738 - echo implode(",", $values[$i]);
1277 + echo wp_kses(implode(",", $values[$i]),[]);
739 1278 ?>],
740 1279 backgroundColor: [
741 1280 <?php
1281 + $ii=0;
742 1282 foreach($values[$i] as $vci=>$valuecolor){
743 - echo guaven_sqlcharts_colorgenerator(1, 0, 0, guaven_sqlcharts_colors($vci, $pid));
744 - }
1283 + echo wp_kses_post(guaven_sqlcharts_colorgenerator(1, 0, -0.1, guaven_sqlcharts_colors($ii, $pid)));
1284 + $ii++;
1285 + }
745 1286 ?>
746 1287 ],
747 1288 hoverBackgroundColor: [
748 1289 <?php
1290 + $ii=0;
749 1291 foreach($values[$i] as $vci=>$valuecolor){
750 - echo guaven_sqlcharts_colorgenerator(1, 0, 0.2, guaven_sqlcharts_colors($vci, $pid));
751 - }
1292 + echo wp_kses_post(guaven_sqlcharts_colorgenerator(1, 0, 0.2, guaven_sqlcharts_colors($ii, $pid)));
1293 + $ii++;
1294 + }
752 1295 ?>
753 1296 ]
754 1297 },
755 1298 <?php
@@ -758,9 +1301,9 @@
758 1301 ]
759 1302 };
760 1303 var myPieChart = new Chart(ctx,{
761 1304 type: '<?php
762 - echo $type;
1305 + echo esc_attr($type);
763 1306 ?>',
764 1307 data: data,
765 1308 options: options
766 1309 });
@@ -804,20 +1347,67 @@
804 1347
805 1348 function guaven_sqlcharts_tablepart($title, $labels, $values, $ylabel,$xlabel){
806 1349 $tabledata='';
807 1350 $fcol=[];$scol=[];
808 - $tablein='<br>';
809 - foreach($values as $valuerow){
1351 + $empty_cell=apply_filters( 'guaven_sqlcharts_table_empty_cell','<td></td>');
1352 + $tablein='';
1353 + foreach($values as $row=>$valuerow){
810 1354 foreach ($valuerow as $key => $value) {
811 - $fcol[$key]='<td>'.str_replace('"',"",$labels[0][$key]).'</td>';
812 - $scol[$key][]='<td>'.$value.'</td>';
813 -
1355 + $putval=$labels[$row][$key]??'';
1356 + $fcol[$key]='<td>'.str_replace('"',"",$putval).'</td>';
1357 + $scol[$key][$row]='<td>'.$value.'</td>';
814 1358 }
1359 + foreach($scol as $scolkey=>$scolvalue){
1360 + for($i=0;$i<count($values);$i++){
1361 + //echo $i;
1362 + if(!isset($scolvalue[$i]))$scol[$scolkey][$i]=$empty_cell;;
1363 + }
1364 + ksort($scol[$scolkey]);
1365 + }
815 1366 }
1367 +
816 1368 foreach($fcol as $key=>$value){
817 1369 $tablein.='<tr>'.$value.implode(" ",$scol[$key]).'</tr>'.PHP_EOL;
818 1370 }
819 - $tabledata.='<table><tr><th>'.$xlabel[0].'</th><th>'.implode("</th><th>",$ylabel).'</th></tr>
820 - '.$tablein.'</table><br>';
821 - return $tabledata;
1371 + $tabledata.='<div class="gvn-tablewrap"><table class="gvn-table"><tr><th>'.$xlabel[0].'</th><th>'.implode("</th><th>",$ylabel).'</th></tr>
1372 + '.$tablein.'</table></div><br>';
822 1373
1374 + echo wp_kses_post($tabledata);
1375 +
823 1376 }
1377 +
1378 +function guaven_sqlcharts_graphtype($post){
1379 + if (strpos(get_post_meta($post->ID, 'guaven_sqlcharts_graphtype', true), "_l") !== false)
1380 + $postfix = '_2';
1381 + else $postfix = '';
1382 + return $postfix;
1383 +}
1384 +
1385 +add_filter('the_content',function($content){
1386 + if(!is_singular('gvn_schart'))return $content;
1387 + global $post;
1388 + $postfix=guaven_sqlcharts_graphtype($post);
1389 + return '[gvn_schart'.$postfix.' id="'.$post->ID.'"'.
1390 + (get_post_meta($post->ID,'guaven_sqlcharts_tablepart',true)!=''?' table="1"':'')
1391 + .']';
1392 +});
1393 +
1394 +function guaven_sqlcharts_key_normalizer($values,$labels,$ylabel){
1395 + $normalize_keys=[];
1396 + $empty_value=apply_filters( 'guaven_sqlcharts_table_empty_value','');
1397 + foreach ($values as $key_ak=>$value_ak) {
1398 + $normalize_keys=array_merge($normalize_keys,array_keys($values[$key_ak]));
1399 + }
1400 + $values_normalized=[];$labels_normalized=[];$ylabel_normalized=[];
1401 + foreach($normalize_keys as $normalized_key){
1402 + foreach ($values as $key_ak=>$value_ak) {
1403 + $values_normalized[$key_ak][$normalized_key]=isset( $values[$key_ak][$normalized_key])? $values[$key_ak][$normalized_key]:"'".$empty_value."'";
1404 + $labels_normalized[$key_ak][$normalized_key]=isset( $labels[$key_ak][$normalized_key])? $labels[$key_ak][$normalized_key]:"''";
1405 + $ylabel_normalized[$key_ak][$normalized_key]=isset( $ylabel[$key_ak][$normalized_key])? $ylabel[$key_ak][$normalized_key]:"";
1406 + }
1407 + }
1408 + return [$values_normalized,$labels_normalized,$ylabel_normalized];
1409 +}
1410 +
1411 +
1412 +add_filter('guaven_sqlcharts_table_empty_cell',function($str){return '<td>#</td>';});
1413 +add_filter('guaven_sqlcharts_table_empty_value',function($str){return 'N/A';});