PluginProbe
SQL Chart Builder / 3.0.5
SQL Chart Builder v3.0.5
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 +242 -58 3.0.03.0.5 View file →
@@ -110,8 +110,9 @@
110 110 add_action('admin_notices', 'guaven_sqlcharts_onboarding_notice');
111 111
112 112 function guaven_sqlcharts_onboarding_notice_dismissed(){
113 113 check_ajax_referer('notice_dismissed', 'nonce');
114 + if (!current_user_can('manage_options')) return;
114 115
115 116 if(empty($_POST['type']))return;
116 117 switch ($_POST['type']){
117 118 case 'onboarding_notice':
@@ -224,9 +225,31 @@
224 225 'item_updated' => __('Chart updated.','guaven_sqlcharts'),
225 226 ),
226 227
227 228 'public' => true,
229 + 'show_in_rest' => false,
228 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 + ),
229 252 'supports' => array(
230 253 'title',
231 254 'postmeta'
232 255 ),
@@ -235,8 +258,14 @@
235 258
236 259 guaven_sqlcharts_load_defaults();
237 260 }
238 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 +
239 268 // "Add title" placeholder on the chart edit screen
240 269 add_filter('enter_title_here', function ($title, $post) {
241 270 if (!empty($post) and $post->post_type == 'gvn_schart') return __('Chart name', 'guaven_sqlcharts');
242 271 return $title;
@@ -423,8 +452,11 @@
423 452 {
424 453 if (!isset($_POST['meta_box_nonce_field']) or !wp_verify_nonce($_POST['meta_box_nonce_field'], 'meta_box_nonce_action')) {
425 454 return $post->ID;
426 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 + }
427 459 $fields = array(
428 460 "guaven_sqlcharts_chartheight",
429 461 "guaven_sqlcharts_chartwidth",
430 462 "guaven_sqlcharts_graphtype",
@@ -444,9 +476,10 @@
444 476 "guaven_sqlcharts_begin_with_0_y",
445 477 "guaven_sqlcharts_round_y_values",
446 478 "guaven_sqlcharts_legend_position",
447 479 "guaven_sqlcharts_nostacked",
448 - "guaven_sqlcharts_forcetooltips"
480 + "guaven_sqlcharts_forcetooltips",
481 + "guaven_sqlcharts_timeaxis"
449 482 );
450 483 foreach ($fields as $key => $value) {
451 484 if(isset($_POST[$value]))$newval=esc_attr($_POST[$value]);
452 485 else $newval='';
@@ -456,20 +489,80 @@
456 489 if(!empty($_POST["guaven_sqlcharts_dbpass"])){
457 490 $encpass=guaven_sqlcharts_encrypt_decrypt('encrypt',$_POST["guaven_sqlcharts_dbpass"]);
458 491 update_post_meta($post->ID, 'guaven_sqlcharts_dbpass', ['encrypted',$encpass]);
459 492 }
460 - 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);
461 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 +}
462 511 add_action('save_post', 'guaven_sqlcharts_save_metabox_area', 1, 2);
463 512 // save the custom fields
464 513
465 514
466 515
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)
519 +{
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 +}
542 +
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.
467 545 function gvn_chart_check_sql_query($sql)
468 546 {
469 - // case-insensitive, word-boundary check: only read-only SELECT queries are allowed
470 - $pattern = '/\b(delete|update|insert|replace|drop|truncate|alter|create|rename|grant|revoke|call|handler|load\s+data|load_file|outfile|dumpfile)\b/i';
471 - return preg_match($pattern, $sql) ? 1 : 0;
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;
563 + }
564 + return 0;
472 565 }
473 566
474 567 function guaven_get_labels_and_values($id, $fvs)
475 568 {
@@ -475,11 +568,13 @@
475 568 {
476 569 $values = array();
477 570 $labels = array();
478 571 $xarg_s = get_post_meta($id, 'guaven_sqlcharts_xarg_s', true);
479 - $xarg_l = get_post_meta($id, 'guaven_sqlcharts_xarg_l', true);
480 572 $yarg_s = get_post_meta($id, 'guaven_sqlcharts_yarg_s', true);
481 - $yarg_l = get_post_meta($id, 'guaven_sqlcharts_yarg_l', true);
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');
482 577 foreach ($fvs as $key => $value) {
483 578 $values[$value->$xarg_s] = $value->$yarg_s;
484 579 $labels[$value->$xarg_s] = '"' . $value->$xarg_s . '"';
485 580 }
@@ -561,9 +656,9 @@
561 656 if (count($varfield_arr)<3) continue;
562 657 $varfield_arr=array_map("trim",$varfield_arr);
563 658 if (!empty($_GET[$varfield_arr[0]])) {
564 659 // User-supplied input: no () bypass allowed — sanitize strictly
565 - $varreplacement = sanitize_text_field(wp_unslash($_GET[$varfield_arr[0]]));
660 + $varreplacement = str_replace(';', '', sanitize_text_field(wp_unslash($_GET[$varfield_arr[0]])));
566 661 if (is_numeric($varreplacement)) {
567 662 $varreplacement = $varreplacement + 0;
568 663 } else {
569 664 $varreplacement = '"' . esc_sql($varreplacement) . '"';
@@ -657,8 +752,10 @@
657 752
658 753 function guaven_sqlcharts_local_shortcode($atts) {
659 754 if(empty($atts['id']))return 'ID is missing.';
660 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.';
661 758 $remote_host=get_post_meta($atts['id'], 'guaven_sqlcharts_dbhost', true);
662 759 if ($remote_host!=''){
663 760 $remote_db=get_post_meta($atts['id'], 'guaven_sqlcharts_dbname', true);
664 761 $remote_login=get_post_meta($atts['id'], 'guaven_sqlcharts_dblogin', true);
@@ -673,31 +770,36 @@
673 770 }
674 771
675 772 $GLOBALS["guaven_sqlcharts_atts"]=$atts;
676 773
677 - $sql = html_entity_decode(get_post_meta($atts['id'], 'guaven_sqlcharts_code', true));
774 + $sql = guaven_sqlcharts_get_code($atts['id']);
678 775 if(empty($sql))return 'SQL query is missing.';
679 - $sql=gvn_chart_put_variables($sql,$atts['id']);
680 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 + }
681 790
791 + $sql=gvn_chart_put_variables($sql,$atts['id']);
682 792 $sql=apply_filters('guaven_sqlcharts_rendered_sql',$sql,$atts);
683 793
794 + // command check on the final SQL, after every shortcode argument and filter value is in place
684 795 $blacklister_f = gvn_chart_check_sql_query($sql);
685 796 if ($blacklister_f == 1)return 'You given SQL code contains forbidden commands. Remember that you should only use SELECT queries';
686 797 $tip_g = guaven_sqlcharts_normalize_type(get_post_meta($atts['id'], 'guaven_sqlcharts_graphtype', true));
687 798
688 - for($i=1;$i<20;$i++){
689 - if(strpos($sql,"{arg".$i."}")!==false){
690 - $replacearg=!empty($atts["arg".$i])?$atts["arg".$i]:0;
691 - $sql = str_replace("{arg".$i."}", "%s", $sql);
692 - $sql=$wpdb->prepare($sql,$replacearg);
693 - }
694 -
695 - }
696 -
697 799 $sql_split = explode(';', $sql);
698 800 $labels_and_values = array();
699 - $post_g = get_post($atts['id']);
801 + $labels = $values = $ylabel = $xlabel = array();
700 802
701 803 global $sqlcharts_inserted_script;
702 804 ob_start();
703 805 for ($i = 0; $i < count($sql_split); $i++) {
@@ -771,17 +873,30 @@
771 873 if (!shortcode_exists('gvn_schart')) {
772 874 add_shortcode('gvn_schart', 'guaven_sqlcharts_local_shortcode');
773 875 }
774 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.
775 880 add_shortcode("gvn_schart_2_cached",function($atts){
776 881 if(empty($atts["id"]))return;
777 882 $atts["id"]=intval($atts["id"]);
778 - $is_logged_in=is_user_logged_in()?'':'_guest';
779 883 $expire=!empty($atts["expire"])?intval($atts["expire"]):3600;
780 - $cached=get_transient('cached_sql_charts_'.$atts["id"].$is_logged_in);
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);
781 896 if(!empty($cached) and !isset($_GET["force_sql_cache_reload"]) )return $cached;
782 - $tobecached=do_shortcode('[gvn_schart_2 id="'.$atts["id"].'"]');
783 - set_transient('cached_sql_charts_'.$atts["id"].$is_logged_in, $tobecached,$expire);//you can change 3600 yourself
897 + $tobecached=guaven_sqlcharts_local_shortcode($inner_atts);
898 + set_transient($key, $tobecached,$expire);
784 899 return $tobecached;
785 900 });
786 901
787 902 // fixed, colorblind-friendly default palette (Tableau 10) used when no custom colors are set
@@ -811,16 +926,86 @@
811 926 $h = !empty($atts['height']) ? $atts['height'] : get_post_meta($pid, 'guaven_sqlcharts_chartheight', true);
812 927 return $h != '' ? 'maintainAspectRatio: false,' : '';
813 928 }
814 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 + }
985 + }
986 + return $has_point ? $out : false;
987 +}
988 +
989 +// X scale options for time-axis mode (globals from asset/front.js): gvnSqlChartsTimeTicks replaces the evenly
990 +// spaced ticks Chart.js generates on a linear scale with the actual data dates, gvnSqlChartsTimeTick formats them
991 +function guaven_sqlcharts_time_axis_scale(){
992 + return "type: 'linear', offset: true, afterBuildTicks: gvnSqlChartsTimeTicks, ticks: {callback: gvnSqlChartsTimeTick, maxRotation: 45, autoSkip: true},";
993 +}
994 +// extra entry for the Chart.js "plugins" object in time-axis mode (tooltip title shown as a date)
995 +function guaven_sqlcharts_time_axis_plugins($time_points){
996 + return $time_points !== false ? 'tooltip: {callbacks: {title: gvnSqlChartsTimeTooltipTitle}}' : '';
997 +}
998 +
815 999 function guaven_sqlcharts_bardata($title, $labels, $values, $ylabel, $type = 'bar', $pid = null)
816 1000 {
817 1001 $horizontal = ($type == 'horizontalBar');
818 1002 $forcestack = ($type == 'stackedBar');
819 1003 $stacked = ($forcestack or get_post_meta($pid, 'guaven_sqlcharts_nostacked', true) != 1) ? 'true' : 'false';
1004 + $time_points = $horizontal ? false : guaven_sqlcharts_time_axis_points($pid, $values);
820 1005 ?>
821 1006 var data = {
822 - labels: [<?php guaven_sqlcharts_merge_labeldata($labels);?>],
1007 + <?php if ($time_points === false) { ?>labels: [<?php guaven_sqlcharts_merge_labeldata($labels);?>],<?php } ?>
823 1008 datasets: [
824 1009 <?php
825 1010 $values_new=guaven_sqlcharts_key_normalizer($values,$labels,$ylabel)[0];
826 1011 $i=-1;
@@ -825,32 +1010,29 @@
825 1010 $values_new=guaven_sqlcharts_key_normalizer($values,$labels,$ylabel)[0];
826 1011 $i=-1;
827 1012 foreach ($values_new as $key_ak=>$value_ak) {
828 1013 $i++;
1014 + $points = $time_points !== false ? $time_points[$key_ak] : $values_new[$key_ak];
829 1015 ?>
830 1016 {
831 1017 <?php
832 - if(!empty($GLOBALS["guaven_sqlcharts_atts"]["params"])){
833 - //passing chartJS params via the shortcode
834 - echo wp_kses($GLOBALS["guaven_sqlcharts_atts"]["params"],[]);
835 - }
1018 + echo guaven_sqlcharts_dataset_params(); // "params" shortcode attribute (validated)
836 1019 ?>
837 - label: "<?php
838 - echo wp_kses($ylabel[$key_ak],[]);
839 -?>",
1020 + label: <?php echo guaven_sqlcharts_js_label($ylabel[$key_ak]); ?>,
840 1021 backgroundColor: [
841 1022 <?php
842 - echo wp_kses(guaven_sqlcharts_colorgenerator(count($values_new[$key_ak]), 0, 0, guaven_sqlcharts_colors($i, $pid)),[]);
1023 + echo wp_kses(guaven_sqlcharts_colorgenerator(count($points), 0, 0, guaven_sqlcharts_colors($i, $pid)),[]);
843 1024 ?>
844 1025 ],
845 1026 borderColor: [
846 1027 <?php
847 - echo wp_kses(guaven_sqlcharts_colorgenerator(count($values_new[$key_ak]), 0, 0.2, guaven_sqlcharts_colors($i, $pid)),[]);
1028 + echo wp_kses(guaven_sqlcharts_colorgenerator(count($points), 0, 0.2, guaven_sqlcharts_colors($i, $pid)),[]);
848 1029 ?>
849 1030 ],
850 1031 borderWidth: 1,
1032 + <?php if ($time_points !== false) echo 'barThickness: 24,'; // fixed width: on a time axis Chart.js would otherwise size bars from the closest pair of dates ?>
851 1033 data: [<?php
852 - echo wp_kses(implode(",", $values_new[$key_ak]),[]);
1034 + echo wp_kses(implode(",", $points),[]);
853 1035 ?>],
854 1036 },
855 1037 <?php
856 1038 }
@@ -859,15 +1041,19 @@
859 1041 };
860 1042 var options={
861 1043 responsive: true,
862 1044 <?php echo wp_kses(guaven_sqlcharts_mar($pid),[]); ?>
1045 + <?php echo wp_kses(guaven_sqlcharts_value_labels($pid),[]); ?>
863 1046 <?php if ($horizontal) echo "indexAxis: 'y',"; ?>
864 1047 scales: {
865 1048 x: {
1049 + <?php if ($time_points !== false) echo guaven_sqlcharts_time_axis_scale(); ?>
1050 + <?php echo guaven_sqlcharts_axis_title($pid, $horizontal ? 'y' : 'x'); ?>
866 1051 stacked: <?php echo esc_js($stacked); ?>,
867 1052 beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_x', true) == 1) ? 'true':'false'; ?>
868 1053 },
869 1054 y: {
1055 + <?php echo guaven_sqlcharts_axis_title($pid, $horizontal ? 'x' : 'y'); ?>
870 1056 stacked: <?php echo esc_js($stacked); ?>,
871 1057 beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_y', true) == 1) ? 'true':'false'; ?>,
872 1058 ticks: {
873 1059 <?php if(get_post_meta($pid, 'guaven_sqlcharts_round_y_values', true) == 1) echo 'precision: 0,'; ?>
@@ -874,9 +1060,9 @@
874 1060 }
875 1061 }
876 1062 }
877 1063 <?php
878 - guaven_sqlcharts_maybe_additional_parameters($pid);
1064 + guaven_sqlcharts_maybe_additional_parameters($pid, guaven_sqlcharts_time_axis_plugins($time_points));
879 1065 ?>
880 1066 };
881 1067 var myBarChart = new Chart(ctx, {
882 1068 type: 'bar',
@@ -896,11 +1082,12 @@
896 1082 }
897 1083
898 1084 function guaven_sqlcharts_linedata($title, $labels, $values, $ylabel, $type = 'false', $pid = null, $charttype = 'line', $stepped = false)
899 1085 {
1086 + $time_points = ($charttype == 'radar') ? false : guaven_sqlcharts_time_axis_points($pid, $values);
900 1087 ?>
901 1088 var data = {
902 - labels: [<?php guaven_sqlcharts_merge_labeldata($labels);?>],
1089 + <?php if ($time_points === false) { ?>labels: [<?php guaven_sqlcharts_merge_labeldata($labels);?>],<?php } ?>
903 1090 datasets: [
904 1091 <?php
905 1092 $values_new=guaven_sqlcharts_key_normalizer($values,$labels,$ylabel)[0];
906 1093 $dataset_count=count($values_new);
@@ -906,8 +1093,9 @@
906 1093 $dataset_count=count($values_new);
907 1094 $i=-1;
908 1095 foreach ($values_new as $key_ak=>$value_ak) {
909 1096 $i++;
1097 + $points = $time_points !== false ? $time_points[$key_ak] : $values_new[$key_ak];
910 1098 if ($type == 'radarfill') $fill = "'origin'";
911 1099 elseif ($type == 'false') $fill = 'false';
912 1100 else $fill = ($i == 0 and $dataset_count > 1) ? '"+1"' : '"origin"';
913 1101 ?>
@@ -912,16 +1100,11 @@
912 1100 else $fill = ($i == 0 and $dataset_count > 1) ? '"+1"' : '"origin"';
913 1101 ?>
914 1102 {
915 1103 <?php
916 - if(!empty($GLOBALS["guaven_sqlcharts_atts"]["params"])){
917 - //passing chartJS params via the shortcode
918 - echo wp_kses($GLOBALS["guaven_sqlcharts_atts"]["params"],[]);
919 - }
1104 + echo guaven_sqlcharts_dataset_params(); // "params" shortcode attribute (validated)
920 1105 ?>
921 - label: "<?php
922 - echo esc_attr($ylabel[$key_ak]);
923 -?>",
1106 + label: <?php echo guaven_sqlcharts_js_label($ylabel[$key_ak]); ?>,
924 1107 fill: <?php echo wp_kses($fill,[]);
925 1108 ?>,
926 1109 tension: 0.1,
927 1110 <?php if ($stepped) echo 'stepped: true,'; ?>
@@ -940,9 +1123,9 @@
940 1123 pointHoverBorderColor: <?php
941 1124 echo wp_kses_post(guaven_sqlcharts_colorgenerator(1, 1, 0.2, guaven_sqlcharts_colors($i, $pid)));
942 1125 ?>
943 1126 data: [<?php
944 - echo wp_kses_post(implode(",", $values_new[$key_ak]));
1127 + echo wp_kses_post(implode(",", $points));
945 1128 ?>],
946 1129 spanGaps: false,
947 1130 },
948 1131 <?php
@@ -955,8 +1138,9 @@
955 1138 data: data,
956 1139 options: {
957 1140 responsive: true,
958 1141 <?php echo wp_kses(guaven_sqlcharts_mar($pid),[]); ?>
1142 + <?php echo wp_kses(guaven_sqlcharts_value_labels($pid),[]); ?>
959 1143 <?php if ($charttype == 'radar') { ?>
960 1144 scales: {
961 1145 r: {
962 1146 beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_y', true) == 1) ? 'true':'false'; ?>
@@ -965,11 +1149,14 @@
965 1149 <?php } else { ?>
966 1150 scales: {
967 1151 x: {
968 1152 display: true,
1153 + <?php if ($time_points !== false) echo guaven_sqlcharts_time_axis_scale(); ?>
1154 + <?php echo guaven_sqlcharts_axis_title($pid, 'x'); ?>
969 1155 beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_x', true) == 1) ? 'true':'false'; ?>
970 1156 },
971 1157 y: {
1158 + <?php echo guaven_sqlcharts_axis_title($pid, 'y'); ?>
972 1159 beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_y', true) == 1) ? 'true':'false'; ?>,
973 1160 ticks: {
974 1161 <?php if(get_post_meta($pid, 'guaven_sqlcharts_round_y_values', true) == 1) echo 'precision: 0,'; ?>
975 1162 }
@@ -976,9 +1163,9 @@
976 1163 }
977 1164 }
978 1165 <?php } ?>
979 1166 <?php
980 - guaven_sqlcharts_maybe_additional_parameters($pid);
1167 + guaven_sqlcharts_maybe_additional_parameters($pid, guaven_sqlcharts_time_axis_plugins($time_points));
981 1168 ?>
982 1169
983 1170 }
984 1171 });
@@ -1002,14 +1189,11 @@
1002 1189 }
1003 1190 ?>
1004 1191 {
1005 1192 <?php
1006 - if(!empty($GLOBALS["guaven_sqlcharts_atts"]["params"])){
1007 - //passing chartJS params via the shortcode
1008 - echo wp_kses($GLOBALS["guaven_sqlcharts_atts"]["params"],[]);
1009 - }
1193 + echo guaven_sqlcharts_dataset_params(); // "params" shortcode attribute (validated)
1010 1194 ?>
1011 - label: "<?php echo esc_attr(isset($ylabel[$key_ak])?$ylabel[$key_ak]:''); ?>",
1195 + label: <?php echo guaven_sqlcharts_js_label(isset($ylabel[$key_ak])?$ylabel[$key_ak]:''); ?>,
1012 1196 backgroundColor: <?php
1013 1197 echo wp_kses_post(guaven_sqlcharts_colorgenerator(1, 1, 0.2, guaven_sqlcharts_colors($i, $pid)));
1014 1198 ?>
1015 1199 borderColor: <?php
@@ -1027,13 +1211,16 @@
1027 1211 data: data,
1028 1212 options: {
1029 1213 responsive: true,
1030 1214 <?php echo wp_kses(guaven_sqlcharts_mar($pid),[]); ?>
1215 + <?php echo wp_kses(guaven_sqlcharts_value_labels($pid),[]); ?>
1031 1216 scales: {
1032 1217 x: {
1218 + <?php echo guaven_sqlcharts_axis_title($pid, 'x'); ?>
1033 1219 beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_x', true) == 1) ? 'true':'false'; ?>
1034 1220 },
1035 1221 y: {
1222 + <?php echo guaven_sqlcharts_axis_title($pid, 'y'); ?>
1036 1223 beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_y', true) == 1) ? 'true':'false'; ?>,
1037 1224 ticks: {
1038 1225 <?php if(get_post_meta($pid, 'guaven_sqlcharts_round_y_values', true) == 1) echo 'precision: 0,'; ?>
1039 1226 }
@@ -1047,9 +1234,9 @@
1047 1234 <?php
1048 1235 }
1049 1236
1050 1237
1051 -function guaven_sqlcharts_maybe_additional_parameters($pid){
1238 +function guaven_sqlcharts_maybe_additional_parameters($pid, $extra_plugins = ''){
1052 1239 if(function_exists('guaven_sqlcharts_maybe_additional_parameters_custom')){
1053 1240 wp_kses(guaven_sqlcharts_maybe_additional_parameters_custom($pid),[]);
1054 1241 return;
1055 1242 }
@@ -1059,9 +1246,9 @@
1059 1246 }
1060 1247 else {
1061 1248 $display='false';$position='top';
1062 1249 }
1063 - echo wp_kses( ",plugins: {legend: {display: ".$display.",position:'".$position."'}}",[]);
1250 + echo wp_kses( ",plugins: {legend: {display: ".$display.",position:'".$position."'}".($extra_plugins !== '' ? ','.$extra_plugins : '')."}",[]);
1064 1251 }
1065 1252
1066 1253
1067 1254
@@ -1069,9 +1256,9 @@
1069 1256 function guaven_sqlcharts_piedata($title, $labels, $values, $ylabel, $pid, $type = 'pie')
1070 1257 {
1071 1258 ?>
1072 1259 var options={
1073 - <?php if(get_post_meta($pid,'guaven_sqlcharts_forcetooltips',true)!='') echo 'showAllTooltips: true,'.PHP_EOL; ?>
1260 + <?php echo wp_kses(guaven_sqlcharts_value_labels($pid),[]); ?>
1074 1261 responsive: true
1075 1262 <?php echo get_post_meta($pid,'guaven_sqlcharts_chartheight',true)!=''||!empty($GLOBALS["guaven_sqlcharts_atts"]['height'])?',maintainAspectRatio: false':''; ?>
1076 1263 <?php
1077 1264 guaven_sqlcharts_maybe_additional_parameters($pid);
@@ -1084,12 +1271,9 @@
1084 1271 for ($i = 0; $i < count($values); $i++) {
1085 1272 ?>
1086 1273 {
1087 1274 <?php
1088 - if(!empty($GLOBALS["guaven_sqlcharts_atts"]["params"])){
1089 - //passing chartJS params via the shortcode
1090 - echo wp_kses($GLOBALS["guaven_sqlcharts_atts"]["params"],[]);
1091 - }
1275 + echo guaven_sqlcharts_dataset_params(); // "params" shortcode attribute (validated)
1092 1276 ?>
1093 1277 data: [<?php
1094 1278 echo wp_kses(implode(",", $values[$i]),[]);
1095 1279 ?>],