PluginProbe
SQL Chart Builder / 3.0.3
SQL Chart Builder v3.0.3
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 +116 -30 3.0.13.0.3 View file →
@@ -444,9 +444,10 @@
444 444 "guaven_sqlcharts_begin_with_0_y",
445 445 "guaven_sqlcharts_round_y_values",
446 446 "guaven_sqlcharts_legend_position",
447 447 "guaven_sqlcharts_nostacked",
448 - "guaven_sqlcharts_forcetooltips"
448 + "guaven_sqlcharts_forcetooltips",
449 + "guaven_sqlcharts_timeaxis"
449 450 );
450 451 foreach ($fields as $key => $value) {
451 452 if(isset($_POST[$value]))$newval=esc_attr($_POST[$value]);
452 453 else $newval='';
@@ -491,11 +492,13 @@
491 492 {
492 493 $values = array();
493 494 $labels = array();
494 495 $xarg_s = get_post_meta($id, 'guaven_sqlcharts_xarg_s', true);
495 - $xarg_l = get_post_meta($id, 'guaven_sqlcharts_xarg_l', true);
496 496 $yarg_s = get_post_meta($id, 'guaven_sqlcharts_yarg_s', true);
497 - $yarg_l = get_post_meta($id, 'guaven_sqlcharts_yarg_l', true);
497 + // labels are saved through esc_attr, so "&" is stored as "&"; decode before splitting on ";"
498 + // or the entity's own ";" would be taken as a series separator
499 + $xarg_l = html_entity_decode((string) get_post_meta($id, 'guaven_sqlcharts_xarg_l', true), ENT_QUOTES, 'UTF-8');
500 + $yarg_l = html_entity_decode((string) get_post_meta($id, 'guaven_sqlcharts_yarg_l', true), ENT_QUOTES, 'UTF-8');
498 501 foreach ($fvs as $key => $value) {
499 502 $values[$value->$xarg_s] = $value->$yarg_s;
500 503 $labels[$value->$xarg_s] = '"' . $value->$xarg_s . '"';
501 504 }
@@ -700,19 +703,24 @@
700 703 $blacklister_f = gvn_chart_check_sql_query($sql);
701 704 if ($blacklister_f == 1)return 'You given SQL code contains forbidden commands. Remember that you should only use SELECT queries';
702 705 $tip_g = guaven_sqlcharts_normalize_type(get_post_meta($atts['id'], 'guaven_sqlcharts_graphtype', true));
703 706
707 + // {arg1}..{arg19} come from shortcode attributes: [gvn_schart_2 id="1" arg1="41"].
708 + // Substituted directly (not via wpdb::prepare) so the same tag may appear any number of times,
709 + // e.g. in every query of a ";"-separated comparison chart. Numbers are inserted as-is, anything
710 + // else is escaped and quoted; a tag already wrapped in quotes ('{arg1}') is not double-quoted.
704 711 for($i=1;$i<20;$i++){
705 - if(strpos($sql,"{arg".$i."}")!==false){
706 - $replacearg=!empty($atts["arg".$i])?$atts["arg".$i]:0;
707 - $sql = str_replace("{arg".$i."}", "%s", $sql);
708 - $sql=$wpdb->prepare($sql,$replacearg);
709 - }
710 -
712 + $tag = '{arg'.$i.'}';
713 + if (strpos($sql, $tag) === false) continue;
714 + $replacearg = !empty($atts['arg'.$i]) ? $atts['arg'.$i] : 0;
715 + if (is_numeric($replacearg)) $replacearg = $replacearg + 0;
716 + else $replacearg = "'" . esc_sql($replacearg) . "'";
717 + $sql = str_replace(array("'".$tag."'", '"'.$tag.'"', $tag), $replacearg, $sql);
711 718 }
712 719
713 720 $sql_split = explode(';', $sql);
714 721 $labels_and_values = array();
722 + $labels = $values = $ylabel = $xlabel = array();
715 723 $post_g = get_post($atts['id']);
716 724
717 725 global $sqlcharts_inserted_script;
718 726 ob_start();
@@ -787,17 +795,24 @@
787 795 if (!shortcode_exists('gvn_schart')) {
788 796 add_shortcode('gvn_schart', 'guaven_sqlcharts_local_shortcode');
789 797 }
790 798
799 +// [gvn_schart_2_cached id="1" expire="3600" arg1=".."] – same as gvn_schart_2 but the output is kept in a
800 +// transient. All other attributes (argN, width, height, table, params) are passed through, and each
801 +// distinct set of attributes gets its own cache entry. Append ?force_sql_cache_reload to the URL to bypass.
791 802 add_shortcode("gvn_schart_2_cached",function($atts){
792 803 if(empty($atts["id"]))return;
793 804 $atts["id"]=intval($atts["id"]);
794 805 $is_logged_in=is_user_logged_in()?'':'_guest';
795 806 $expire=!empty($atts["expire"])?intval($atts["expire"]):3600;
796 - $cached=get_transient('cached_sql_charts_'.$atts["id"].$is_logged_in);
807 + $inner_atts=$atts;
808 + unset($inner_atts['expire']);
809 + $key='cached_sql_charts_'.$atts["id"].$is_logged_in;
810 + if (count($inner_atts) > 1) $key .= '_'.md5(serialize($inner_atts));
811 + $cached=get_transient($key);
797 812 if(!empty($cached) and !isset($_GET["force_sql_cache_reload"]) )return $cached;
798 - $tobecached=do_shortcode('[gvn_schart_2 id="'.$atts["id"].'"]');
799 - set_transient('cached_sql_charts_'.$atts["id"].$is_logged_in, $tobecached,$expire);//you can change 3600 yourself
813 + $tobecached=guaven_sqlcharts_local_shortcode($inner_atts);
814 + set_transient($key, $tobecached,$expire);
800 815 return $tobecached;
801 816 });
802 817
803 818 // fixed, colorblind-friendly default palette (Tableau 10) used when no custom colors are set
@@ -827,16 +842,76 @@
827 842 $h = !empty($atts['height']) ? $atts['height'] : get_post_meta($pid, 'guaven_sqlcharts_chartheight', true);
828 843 return $h != '' ? 'maintainAspectRatio: false,' : '';
829 844 }
830 845
846 +// outputs 'showAllTooltips: true,' when "Value labels" is checked; the values are drawn by the
847 +// gvnShowAllValues plugin in asset/front.js (works for every chart type)
848 +function guaven_sqlcharts_value_labels($pid){
849 + return get_post_meta($pid, 'guaven_sqlcharts_forcetooltips', true) != '' ? 'showAllTooltips: true,' : '';
850 +}
851 +
852 +// Chart.js scale title block built from the "X axis label" / "Y axis label" fields.
853 +// $which is 'x' or 'y' (the *field* to use, not the scale). The Y label is only used as an axis
854 +// title for single-series charts; with several ";"-separated series the legend names them instead.
855 +function guaven_sqlcharts_axis_title($pid, $which){
856 + $key = $which == 'x' ? 'guaven_sqlcharts_xarg_l' : 'guaven_sqlcharts_yarg_l';
857 + $text = trim(html_entity_decode((string) get_post_meta($pid, $key, true), ENT_QUOTES, 'UTF-8'));
858 + if ($text === '' or ($which == 'y' and strpos($text, ';') !== false)) return '';
859 + return 'title: {display: true, text: ' . wp_json_encode($text) . '},';
860 +}
861 +
862 +// dataset label as a safe JS string literal (labels saved before 3.0.1 may hold HTML entities)
863 +function guaven_sqlcharts_js_label($label){
864 + return wp_json_encode(html_entity_decode((string) $label, ENT_QUOTES, 'UTF-8'));
865 +}
866 +
867 +// Parses an X value for the "time axis" option. Accepts YYYY, YYYY-MM, YYYY-MM-DD, optionally followed
868 +// by HH:MM or HH:MM:SS. Returns a UTC timestamp in milliseconds, or false when the value is not a date.
869 +function guaven_sqlcharts_parse_date($str){
870 + $str = trim((string) $str);
871 + if (!preg_match('/^(\d{4})(?:-(\d{1,2})(?:-(\d{1,2})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2}))?)?)?)?$/', $str, $m)) return false;
872 + $y = (int) $m[1]; $mo = isset($m[2]) ? (int) $m[2] : 1; $d = isset($m[3]) ? (int) $m[3] : 1;
873 + $h = isset($m[4]) ? (int) $m[4] : 0; $mi = isset($m[5]) ? (int) $m[5] : 0; $sec = isset($m[6]) ? (int) $m[6] : 0;
874 + if (!checkdate($mo, $d, $y) or $h > 23 or $mi > 59 or $sec > 59) return false;
875 + return gmmktime($h, $mi, $sec, $mo, $d, $y) * 1000;
876 +}
877 +
878 +// "Scale X axis by date/time" option. Returns, per dataset, a list of "{x:<ms>,y:<value>}" JS point
879 +// literals when the option is on and every X value is a date; false otherwise (normal category axis).
880 +function guaven_sqlcharts_time_axis_points($pid, $values){
881 + if (get_post_meta($pid, 'guaven_sqlcharts_timeaxis', true) != 1) return false;
882 + $out = array();
883 + $has_point = false;
884 + foreach ($values as $key_ak => $series) {
885 + $out[$key_ak] = array();
886 + foreach ($series as $x => $y) {
887 + $ts = guaven_sqlcharts_parse_date($x);
888 + if ($ts === false) return false;
889 + $out[$key_ak][] = '{x:' . $ts . ',y:' . (is_numeric($y) ? $y + 0 : 'null') . '}';
890 + $has_point = true;
891 + }
892 + }
893 + return $has_point ? $out : false;
894 +}
895 +
896 +// X scale options for time-axis mode; gvnSqlChartsTimeTick (asset/front.js) formats the ticks as dates
897 +function guaven_sqlcharts_time_axis_scale(){
898 + return "type: 'linear', offset: true, ticks: {callback: gvnSqlChartsTimeTick, maxRotation: 45},";
899 +}
900 +// extra entry for the Chart.js "plugins" object in time-axis mode (tooltip title shown as a date)
901 +function guaven_sqlcharts_time_axis_plugins($time_points){
902 + return $time_points !== false ? 'tooltip: {callbacks: {title: gvnSqlChartsTimeTooltipTitle}}' : '';
903 +}
904 +
831 905 function guaven_sqlcharts_bardata($title, $labels, $values, $ylabel, $type = 'bar', $pid = null)
832 906 {
833 907 $horizontal = ($type == 'horizontalBar');
834 908 $forcestack = ($type == 'stackedBar');
835 909 $stacked = ($forcestack or get_post_meta($pid, 'guaven_sqlcharts_nostacked', true) != 1) ? 'true' : 'false';
910 + $time_points = $horizontal ? false : guaven_sqlcharts_time_axis_points($pid, $values);
836 911 ?>
837 912 var data = {
838 - labels: [<?php guaven_sqlcharts_merge_labeldata($labels);?>],
913 + <?php if ($time_points === false) { ?>labels: [<?php guaven_sqlcharts_merge_labeldata($labels);?>],<?php } ?>
839 914 datasets: [
840 915 <?php
841 916 $values_new=guaven_sqlcharts_key_normalizer($values,$labels,$ylabel)[0];
842 917 $i=-1;
@@ -841,8 +916,9 @@
841 916 $values_new=guaven_sqlcharts_key_normalizer($values,$labels,$ylabel)[0];
842 917 $i=-1;
843 918 foreach ($values_new as $key_ak=>$value_ak) {
844 919 $i++;
920 + $points = $time_points !== false ? $time_points[$key_ak] : $values_new[$key_ak];
845 921 ?>
846 922 {
847 923 <?php
848 924 if(!empty($GLOBALS["guaven_sqlcharts_atts"]["params"])){
@@ -849,24 +925,23 @@
849 925 //passing chartJS params via the shortcode
850 926 echo wp_kses($GLOBALS["guaven_sqlcharts_atts"]["params"],[]);
851 927 }
852 928 ?>
853 - label: "<?php
854 - echo wp_kses($ylabel[$key_ak],[]);
855 -?>",
929 + label: <?php echo guaven_sqlcharts_js_label($ylabel[$key_ak]); ?>,
856 930 backgroundColor: [
857 931 <?php
858 - echo wp_kses(guaven_sqlcharts_colorgenerator(count($values_new[$key_ak]), 0, 0, guaven_sqlcharts_colors($i, $pid)),[]);
932 + echo wp_kses(guaven_sqlcharts_colorgenerator(count($points), 0, 0, guaven_sqlcharts_colors($i, $pid)),[]);
859 933 ?>
860 934 ],
861 935 borderColor: [
862 936 <?php
863 - echo wp_kses(guaven_sqlcharts_colorgenerator(count($values_new[$key_ak]), 0, 0.2, guaven_sqlcharts_colors($i, $pid)),[]);
937 + echo wp_kses(guaven_sqlcharts_colorgenerator(count($points), 0, 0.2, guaven_sqlcharts_colors($i, $pid)),[]);
864 938 ?>
865 939 ],
866 940 borderWidth: 1,
941 + <?php if ($time_points !== false) echo 'maxBarThickness: 48,'; ?>
867 942 data: [<?php
868 - echo wp_kses(implode(",", $values_new[$key_ak]),[]);
943 + echo wp_kses(implode(",", $points),[]);
869 944 ?>],
870 945 },
871 946 <?php
872 947 }
@@ -875,15 +950,19 @@
875 950 };
876 951 var options={
877 952 responsive: true,
878 953 <?php echo wp_kses(guaven_sqlcharts_mar($pid),[]); ?>
954 + <?php echo wp_kses(guaven_sqlcharts_value_labels($pid),[]); ?>
879 955 <?php if ($horizontal) echo "indexAxis: 'y',"; ?>
880 956 scales: {
881 957 x: {
958 + <?php if ($time_points !== false) echo guaven_sqlcharts_time_axis_scale(); ?>
959 + <?php echo guaven_sqlcharts_axis_title($pid, $horizontal ? 'y' : 'x'); ?>
882 960 stacked: <?php echo esc_js($stacked); ?>,
883 961 beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_x', true) == 1) ? 'true':'false'; ?>
884 962 },
885 963 y: {
964 + <?php echo guaven_sqlcharts_axis_title($pid, $horizontal ? 'x' : 'y'); ?>
886 965 stacked: <?php echo esc_js($stacked); ?>,
887 966 beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_y', true) == 1) ? 'true':'false'; ?>,
888 967 ticks: {
889 968 <?php if(get_post_meta($pid, 'guaven_sqlcharts_round_y_values', true) == 1) echo 'precision: 0,'; ?>
@@ -890,9 +969,9 @@
890 969 }
891 970 }
892 971 }
893 972 <?php
894 - guaven_sqlcharts_maybe_additional_parameters($pid);
973 + guaven_sqlcharts_maybe_additional_parameters($pid, guaven_sqlcharts_time_axis_plugins($time_points));
895 974 ?>
896 975 };
897 976 var myBarChart = new Chart(ctx, {
898 977 type: 'bar',
@@ -912,11 +991,12 @@
912 991 }
913 992
914 993 function guaven_sqlcharts_linedata($title, $labels, $values, $ylabel, $type = 'false', $pid = null, $charttype = 'line', $stepped = false)
915 994 {
995 + $time_points = ($charttype == 'radar') ? false : guaven_sqlcharts_time_axis_points($pid, $values);
916 996 ?>
917 997 var data = {
918 - labels: [<?php guaven_sqlcharts_merge_labeldata($labels);?>],
998 + <?php if ($time_points === false) { ?>labels: [<?php guaven_sqlcharts_merge_labeldata($labels);?>],<?php } ?>
919 999 datasets: [
920 1000 <?php
921 1001 $values_new=guaven_sqlcharts_key_normalizer($values,$labels,$ylabel)[0];
922 1002 $dataset_count=count($values_new);
@@ -922,8 +1002,9 @@
922 1002 $dataset_count=count($values_new);
923 1003 $i=-1;
924 1004 foreach ($values_new as $key_ak=>$value_ak) {
925 1005 $i++;
1006 + $points = $time_points !== false ? $time_points[$key_ak] : $values_new[$key_ak];
926 1007 if ($type == 'radarfill') $fill = "'origin'";
927 1008 elseif ($type == 'false') $fill = 'false';
928 1009 else $fill = ($i == 0 and $dataset_count > 1) ? '"+1"' : '"origin"';
929 1010 ?>
@@ -933,11 +1014,9 @@
933 1014 //passing chartJS params via the shortcode
934 1015 echo wp_kses($GLOBALS["guaven_sqlcharts_atts"]["params"],[]);
935 1016 }
936 1017 ?>
937 - label: "<?php
938 - echo esc_attr($ylabel[$key_ak]);
939 -?>",
1018 + label: <?php echo guaven_sqlcharts_js_label($ylabel[$key_ak]); ?>,
940 1019 fill: <?php echo wp_kses($fill,[]);
941 1020 ?>,
942 1021 tension: 0.1,
943 1022 <?php if ($stepped) echo 'stepped: true,'; ?>
@@ -956,9 +1035,9 @@
956 1035 pointHoverBorderColor: <?php
957 1036 echo wp_kses_post(guaven_sqlcharts_colorgenerator(1, 1, 0.2, guaven_sqlcharts_colors($i, $pid)));
958 1037 ?>
959 1038 data: [<?php
960 - echo wp_kses_post(implode(",", $values_new[$key_ak]));
1039 + echo wp_kses_post(implode(",", $points));
961 1040 ?>],
962 1041 spanGaps: false,
963 1042 },
964 1043 <?php
@@ -971,8 +1050,9 @@
971 1050 data: data,
972 1051 options: {
973 1052 responsive: true,
974 1053 <?php echo wp_kses(guaven_sqlcharts_mar($pid),[]); ?>
1054 + <?php echo wp_kses(guaven_sqlcharts_value_labels($pid),[]); ?>
975 1055 <?php if ($charttype == 'radar') { ?>
976 1056 scales: {
977 1057 r: {
978 1058 beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_y', true) == 1) ? 'true':'false'; ?>
@@ -981,11 +1061,14 @@
981 1061 <?php } else { ?>
982 1062 scales: {
983 1063 x: {
984 1064 display: true,
1065 + <?php if ($time_points !== false) echo guaven_sqlcharts_time_axis_scale(); ?>
1066 + <?php echo guaven_sqlcharts_axis_title($pid, 'x'); ?>
985 1067 beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_x', true) == 1) ? 'true':'false'; ?>
986 1068 },
987 1069 y: {
1070 + <?php echo guaven_sqlcharts_axis_title($pid, 'y'); ?>
988 1071 beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_y', true) == 1) ? 'true':'false'; ?>,
989 1072 ticks: {
990 1073 <?php if(get_post_meta($pid, 'guaven_sqlcharts_round_y_values', true) == 1) echo 'precision: 0,'; ?>
991 1074 }
@@ -992,9 +1075,9 @@
992 1075 }
993 1076 }
994 1077 <?php } ?>
995 1078 <?php
996 - guaven_sqlcharts_maybe_additional_parameters($pid);
1079 + guaven_sqlcharts_maybe_additional_parameters($pid, guaven_sqlcharts_time_axis_plugins($time_points));
997 1080 ?>
998 1081
999 1082 }
1000 1083 });
@@ -1023,9 +1106,9 @@
1023 1106 //passing chartJS params via the shortcode
1024 1107 echo wp_kses($GLOBALS["guaven_sqlcharts_atts"]["params"],[]);
1025 1108 }
1026 1109 ?>
1027 - label: "<?php echo esc_attr(isset($ylabel[$key_ak])?$ylabel[$key_ak]:''); ?>",
1110 + label: <?php echo guaven_sqlcharts_js_label(isset($ylabel[$key_ak])?$ylabel[$key_ak]:''); ?>,
1028 1111 backgroundColor: <?php
1029 1112 echo wp_kses_post(guaven_sqlcharts_colorgenerator(1, 1, 0.2, guaven_sqlcharts_colors($i, $pid)));
1030 1113 ?>
1031 1114 borderColor: <?php
@@ -1043,13 +1126,16 @@
1043 1126 data: data,
1044 1127 options: {
1045 1128 responsive: true,
1046 1129 <?php echo wp_kses(guaven_sqlcharts_mar($pid),[]); ?>
1130 + <?php echo wp_kses(guaven_sqlcharts_value_labels($pid),[]); ?>
1047 1131 scales: {
1048 1132 x: {
1133 + <?php echo guaven_sqlcharts_axis_title($pid, 'x'); ?>
1049 1134 beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_x', true) == 1) ? 'true':'false'; ?>
1050 1135 },
1051 1136 y: {
1137 + <?php echo guaven_sqlcharts_axis_title($pid, 'y'); ?>
1052 1138 beginAtZero: <?php echo (get_post_meta($pid, 'guaven_sqlcharts_begin_with_0_y', true) == 1) ? 'true':'false'; ?>,
1053 1139 ticks: {
1054 1140 <?php if(get_post_meta($pid, 'guaven_sqlcharts_round_y_values', true) == 1) echo 'precision: 0,'; ?>
1055 1141 }
@@ -1063,9 +1149,9 @@
1063 1149 <?php
1064 1150 }
1065 1151
1066 1152
1067 -function guaven_sqlcharts_maybe_additional_parameters($pid){
1153 +function guaven_sqlcharts_maybe_additional_parameters($pid, $extra_plugins = ''){
1068 1154 if(function_exists('guaven_sqlcharts_maybe_additional_parameters_custom')){
1069 1155 wp_kses(guaven_sqlcharts_maybe_additional_parameters_custom($pid),[]);
1070 1156 return;
1071 1157 }
@@ -1075,9 +1161,9 @@
1075 1161 }
1076 1162 else {
1077 1163 $display='false';$position='top';
1078 1164 }
1079 - echo wp_kses( ",plugins: {legend: {display: ".$display.",position:'".$position."'}}",[]);
1165 + echo wp_kses( ",plugins: {legend: {display: ".$display.",position:'".$position."'}".($extra_plugins !== '' ? ','.$extra_plugins : '')."}",[]);
1080 1166 }
1081 1167
1082 1168
1083 1169
@@ -1085,9 +1171,9 @@
1085 1171 function guaven_sqlcharts_piedata($title, $labels, $values, $ylabel, $pid, $type = 'pie')
1086 1172 {
1087 1173 ?>
1088 1174 var options={
1089 - <?php if(get_post_meta($pid,'guaven_sqlcharts_forcetooltips',true)!='') echo 'showAllTooltips: true,'.PHP_EOL; ?>
1175 + <?php echo wp_kses(guaven_sqlcharts_value_labels($pid),[]); ?>
1090 1176 responsive: true
1091 1177 <?php echo get_post_meta($pid,'guaven_sqlcharts_chartheight',true)!=''||!empty($GLOBALS["guaven_sqlcharts_atts"]['height'])?',maintainAspectRatio: false':''; ?>
1092 1178 <?php
1093 1179 guaven_sqlcharts_maybe_additional_parameters($pid);