PluginProbe
M Chart / 2.2.2
M Chart v2.2.2
2.3.2 2.3.1 2.3 2.2.2 2.2.1 2.2 trunk 1.0 1.1 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.10 1.10.1 1.11 1.11.1 1.11.2 1.12 1.2 1.2.1 1.3 1.3.1 1.3.2 All 53 releases
m-chart / components / class-m-chart.php

class-m-chart.php in M Chart 2.2.2, at components/class-m-chart.php

1,457 lines 45.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit;
5 }
6
7 class M_Chart {
8 public $version = '2.2.2';
9 public $slug = 'm-chart';
10 public $plugin_name = 'Chart';
11 public $chart_meta_fields = [
12 'library' => 'chartjs',
13 'type' => 'line',
14 'parse_in' => 'rows',
15 'labels' => true,
16 'shared' => false,
17 'subtitle' => '',
18 'y_title' => '',
19 'y_units' => '',
20 'y_min' => false,
21 'y_min_value' => 0,
22 'x_title' => '',
23 'x_units' => '',
24 'height' => 600,
25 'legend' => true,
26 'source' => '',
27 'source_url' => '',
28 'data' => [],
29 'set_names' => [],
30 'data_point_colors' => false,
31 'mean_point' => true,
32 'sample_points' => false,
33 'constrain_y_axis' => false,
34 'include_source' => false,
35 ];
36 public $get_chart_default_args = [
37 'show' => 'chart',
38 'width' => 'responsive',
39 'share' => '',
40 ];
41 public $parse_options = [
42 'columns',
43 'rows',
44 ];
45 public $options_set;
46 public $plugin_url;
47 public $is_iframe = false;
48 public $instance = 1;
49 public $iframe_csp_nonce = '';
50 public $settings = [
51 'library' => 'chartjs',
52 'show_library' => 'no',
53 'performance' => 'default',
54 'image_multiplier' => '2',
55 'image_width' => '900',
56 'default_height' => '600',
57 'embeds' => '',
58 'defer_rendering' => 'enabled',
59 'default_theme' => '_default',
60 'locale' => 'en-US',
61 'csv_delimiter' => ',',
62 ];
63 public $csv_delimiters = [
64 ',' => 'Comma',
65 "\t" => 'Tab',
66 ' ' => 'Space',
67 ';' => 'Semicolon',
68 ];
69 public $library_class;
70 public $icon = 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjY0IDY0IDI0IDI0IiB3aWR0aD0iMjRweCIgaGVpZ2h0PSIyNHB4Ij4KICA8cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik0gNzYuNjU3IDY1LjUgTCA3Ni42NTcgNzUuMzQ0IEwgODYuNSA3NS4zNDQgQyA4Ni41IDY5LjkwOSA4Mi4wOTEgNjUuNSA3Ni42NTcgNjUuNSBaIE0gNzQuNjg4IDc2LjMyOCBMIDc0LjY4OCA2Ni44MzMgQyA2OS41NTcgNjcuMTc0IDY1LjUgNzEuNDM5IDY1LjUgNzYuNjU3IEMgNjUuNSA4Mi4wOTEgNjkuOTA5IDg2LjUgNzUuMzQzIDg2LjUgQyA4MC41NjIgODYuNSA4NC44MjggODIuNDQzIDg1LjE2NyA3Ny4zMTMgTCA3NC42ODggNzcuMzEzIEwgNzQuNjg4IDc2LjMyOCBaIi8+Cjwvc3ZnPg==';
71 public $logo = 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyBpZD0idXVpZC03OGNkNDE3YS0zN2NjLTQ0NTAtODU2ZC00MzBlMTliYjhmOTEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDQzNyAzNzAuMDU2Ij4KICA8ZyBpZD0idXVpZC1hMDM3MTk0ZS1hN2QzLTQ4NTktOGEwZi0wMmIzMWI0YmQ3MzEiIHRyYW5zZm9ybT0ibWF0cml4KDEsIDAsIDAsIDEsIDM4LjQwMzk5OSwgNTUuOTM3MDA0KSI+CiAgICA8cmVjdCB4PSIyODEuNDAxIiB5PSIxNDguNTExIiB3aWR0aD0iODguNzEiIGhlaWdodD0iMTQxLjQ1IiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyIvPgogICAgPHJlY3QgeD0iLTkuOTE5IiB5PSIyNjQuODIxIiB3aWR0aD0iODguNzEiIGhlaWdodD0iMjUuMTMiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7Ii8+CiAgICA8cmVjdCB4PSItOS45MTkiIHk9Ii0zMS43NzkiIHdpZHRoPSI4OC43MSIgaGVpZ2h0PSIyODQuMjEiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7Ii8+CiAgICA8cmVjdCB4PSIyODEuNDAxIiB5PSItMzEuNzc5IiB3aWR0aD0iODguNzEiIGhlaWdodD0iMTY3Ljg3IiBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAxOyIvPgogICAgPHJlY3QgeD0iMTg2LjI4MSIgeT0iMTk0LjU1MSIgd2lkdGg9IjgyLjc5IiBoZWlnaHQ9Ijk1LjQiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7Ii8+CiAgICA8cmVjdCB4PSI5MS4wODEiIHk9IjIzNS40MTEiIHdpZHRoPSI4Mi45NCIgaGVpZ2h0PSI1NC41NSIgc3R5bGU9InN0cm9rZS13aWR0aDogMTsiLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iMjgxLjQwMSAtMzEuNzc5IDE4MC4xMDEgNjUuMTExIDE4MC4xMDEgMTc2LjcwMSAyODEuNDAxIDc5LjcxMSAyODEuNDAxIC0zMS43NzkiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7Ii8+CiAgICA8cG9seWdvbiBwb2ludHM9Ijc4LjgwMSA3OS43MTEgMTgwLjEwMSAxNzYuNzAxIDE4MC4xMDEgNjUuMTExIDc4LjgwMSAtMzEuNzc5IDc4LjgwMSA3OS43MTEiIHN0eWxlPSJzdHJva2Utd2lkdGg6IDE7Ii8+CiAgPC9nPgo8L3N2Zz4=';
72
73 private $admin;
74 private $parse;
75 private $block;
76 private $fs;
77 private $resolved_settings;
78 private $libraries = [
79 'chartjs' => 'Chart.js',
80 ];
81
82 /**
83 * Constructor
84 */
85 public function __construct() {
86 // Initiate Freemius first so it loads as early as possible
87 // Must run synchronously here at file load so Freemius detects m-chart.php as the plugin file
88 $this->freemius();
89
90 $this->plugin_url = $this->plugin_url();
91
92 add_action( 'init', [ $this, 'init' ] );
93 add_action( 'plugins_loaded', [ $this, 'plugins_loaded' ] );
94 add_action( 'save_post', [ $this, 'save_post' ] );
95 // Doing this early as possible because it sets is_iframe which we might need to use for other things
96 add_action( 'template_redirect', [ $this, 'template_redirect' ], 0 );
97 add_action( 'm_chart_update_post_meta', [ $this, 'm_chart_update_post_meta' ], 10, 2 );
98
99 // Doing this before the default so it's already done before anything else
100 add_filter( 'm_chart_get_chart_image_tag', [ $this, 'm_chart_get_chart_image_tag' ], 9, 3 );
101 add_filter( 'the_content', [ $this, 'the_content' ] );
102 add_filter( 'm_chart_image_support', [ $this, 'm_chart_image_support' ], 10, 2 );
103 add_filter( 'm_chart_instant_preview_support', [ $this, 'm_chart_instant_preview_support' ], 10, 2 );
104 add_filter( 'm_chart_library_class', [ $this, 'm_chart_library_class' ], 10, 2 );
105
106 add_shortcode( 'm-chart', [ $this, 'chart_shortcode' ] );
107 // Backwards-compatible alias for content created before the rename
108 add_shortcode( 'chart', [ $this, 'chart_shortcode' ] );
109
110 // Initiate the block class
111 $this->block();
112 }
113
114 /**
115 * Initiate and return the Freemius SDK instance
116 *
117 * Lazily builds the instance on first call and caches it
118 * The SDK lives in components/external/freemius rather than the usual plugin root /freemius
119 * M Chart core (m-chart) is the free version so is_premium is false
120 * M Chart Pro (m-chart-pro) is a separate plugin sold under the same Freemius product via parallel activation
121 *
122 * @return Freemius the Freemius SDK instance for M Chart
123 */
124 public function freemius() {
125 if ( isset( $this->fs ) ) {
126 return $this->fs;
127 }
128
129 require_once __DIR__ . '/external/freemius/start.php';
130
131 $this->fs = fs_dynamic_init( [
132 'id' => '30258',
133 'slug' => 'm-chart',
134 'premium_slug' => 'm-chart-pro',
135 'type' => 'plugin',
136 'public_key' => 'pk_b1eafc082abd31243874041aaaaab',
137 'is_premium' => false,
138 'has_premium_version' => true,
139 'has_addons' => false,
140 'has_paid_plans' => true,
141 'is_org_compliant' => true,
142 'parallel_activation' => [
143 'enabled' => true,
144 'premium_version_basename' => 'm-chart-pro/m-chart-pro.php',
145 ],
146 'menu' => [
147 // Anchor on the Charts CPT itself so the Freemius pages are plugin-level (m-chart-account, m-chart-pricing)
148 // The CPT slug contains ".php?" so get_slug() falls back to the m-chart affix rather than an m-chart-settings- prefix
149 'slug' => 'edit.php?post_type=m-chart',
150 // Surface the Freemius Account page as a submenu link under the Charts menu
151 'account' => true,
152 'contact' => false,
153 'support' => false,
154 ],
155 ] );
156
157 $this->freemius_filters();
158
159 do_action( 'm_chart_fs_loaded' );
160
161 return $this->fs;
162 }
163
164 /**
165 * Register Freemius filters that brand the pricing and opt-in screens
166 *
167 * pricing/css_path points Freemius at our stylesheet which is enqueued on the pricing page after its own CSS
168 * pricing/show_annual_in_monthly shows the real annual price rather than the monthly equivalent of the annual plan
169 *
170 * The css_path and plugin_icon paths run through Freemius's fs_asset_url which maps a path to a URL by stripping WP_PLUGIN_DIR
171 * __DIR__ resolves to the plugin's real path which differs from WP_PLUGIN_DIR when the plugin is symlinked
172 * plugin_basename normalizes it back to the plugins-dir-relative path so the URL resolves in every install
173 *
174 * The opt-in / connect screen has no css_path filter so we inline our stylesheet on the connect/before action instead
175 * It prints after Freemius's own connect.css so equal-specificity overrides win on source order
176 */
177 private function freemius_filters() {
178 $this->fs->add_filter( 'pricing/css_path', function () {
179 return WP_PLUGIN_DIR . '/' . plugin_basename( __DIR__ . '/css/m-chart-freemius-pricing.css' );
180 } );
181
182 // Re-point the pricing page contact links at M Chart's own support/contact pages
183 // Freemius wraps the pricing output in this filter so the inline script is scoped to that screen
184 $this->fs->add_filter( 'templates/pricing.php', function ( $html ) {
185 $script_path = __DIR__ . '/js/m-chart-freemius-pricing.js';
186
187 if ( ! file_exists( $script_path ) ) {
188 return $html;
189 }
190
191 return $html
192 . "<script id=\"m-chart-freemius-pricing\">\n"
193 . file_get_contents( $script_path ) // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- local plugin asset
194 . "</script>\n";
195 } );
196
197 $this->fs->add_filter( 'plugin_icon', function () {
198 return WP_PLUGIN_DIR . '/' . plugin_basename( __DIR__ . '/images/m-chart-logo-square-rounded.svg' );
199 } );
200
201 $this->fs->add_filter( 'pricing/show_annual_in_monthly', '__return_false' );
202
203 $this->fs->add_action( 'connect/before', function () {
204 $css_path = __DIR__ . '/css/m-chart-freemius-connect.css';
205
206 if ( ! file_exists( $css_path ) ) {
207 return;
208 }
209
210 echo "<style id=\"m-chart-freemius-connect\">\n";
211 echo file_get_contents( $css_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- local plugin asset
212 echo "</style>\n";
213 } );
214
215 // Swap Freemius's casual interjection headings for professional wording
216 // These are admin-notice titles keyed by id and the trailing "!" / "..." is appended by the SDK so it carries over
217 // Values are left unescaped since Freemius escapes them at the output site
218 // Deferred to init since freemius() runs at construct and the __() calls would load the text domain before WordPress is ready
219 add_action( 'init', function () {
220 fs_override_i18n( [
221 'yee-haw' => __( 'Success', 'm-chart' ),
222 'woot' => __( 'Success', 'm-chart' ),
223 'right-on' => __( 'Success', 'm-chart' ),
224 'oops' => __( 'Error', 'm-chart' ),
225 'hmm' => __( 'Heads up', 'm-chart' ),
226 'hey' => __( 'Hello', 'm-chart' ),
227 ], $this->slug );
228 } );
229 }
230
231 /**
232 * Admin object accessor
233 */
234 public function admin() {
235 if ( ! $this->admin ) {
236 require_once __DIR__ . '/class-m-chart-admin.php';
237 $this->admin = new M_Chart_Admin();
238 }
239
240 return $this->admin;
241 }
242
243 /**
244 * Block object accessor
245 */
246 public function block() {
247 if ( ! $this->block ) {
248 require_once __DIR__ . '/class-m-chart-block.php';
249 $this->block = new M_Chart_Block();
250 }
251
252 return $this->block;
253 }
254
255 /**
256 * Library object accessor
257 */
258 public function library( $library = false ) {
259 if ( ! $library ) {
260 $library = $this->get_library();
261 }
262
263 return apply_filters( 'm_chart_library_class', $this->library_class, $library );
264 }
265
266 /**
267 * Parse object accessor
268 */
269 public function parse() {
270 if ( ! $this->parse ) {
271 require_once __DIR__ . '/class-m-chart-parsed-data-point.php';
272 require_once __DIR__ . '/class-m-chart-parse.php';
273 $this->parse = new M_Chart_Parse();
274 }
275
276 return $this->parse;
277 }
278
279 /**
280 * Do init stuff
281 */
282 public function init() {
283 // Register the units taxonomy
284 register_taxonomy(
285 $this->slug . '-units',
286 [ $this->slug ],
287 [
288 'hierarchical' => true,
289 'labels' => [
290 'name' => esc_html__( 'Chart Units', 'm-chart' ),
291 'singular_name' => esc_html__( 'Chart Unit', 'm-chart' ),
292 'search_items' => esc_html__( 'Search Chart Units', 'm-chart' ),
293 'all_items' => esc_html__( 'All Chart Units', 'm-chart' ),
294 'parent_item' => esc_html__( 'Parent Chart Unit', 'm-chart' ),
295 'parent_item_colon' => esc_html__( 'Parent Chart Unit:', 'm-chart' ),
296 'edit_item' => esc_html__( 'Edit Chart Unit', 'm-chart' ),
297 'update_item' => esc_html__( 'Update Chart Unit', 'm-chart' ),
298 'add_new_item' => esc_html__( 'Add New Chart Unit', 'm-chart' ),
299 'new_item_name' => esc_html__( 'Chart Unit Name', 'm-chart' ),
300 'menu_name' => esc_html__( 'Chart Units', 'm-chart' ),
301 ],
302 'show_ui' => true,
303 'query_var' => true,
304 'rewrite' => [
305 'slug' => $this->slug . '-units',
306 ],
307 ]
308 );
309
310 // Register the library taxonomy
311 register_taxonomy(
312 $this->slug . '-library',
313 [ $this->slug ],
314 [
315 'hierarchical' => false,
316 'show_ui' => false,
317 'query_var' => true,
318 'rewrite' => [
319 'slug' => $this->slug . '-library',
320 ],
321 ]
322 );
323
324 // Register the charts custom post type
325 register_post_type(
326 $this->slug,
327 [
328 'labels' => [
329 'name' => esc_html__( 'Charts', 'm-chart' ),
330 // Top-level admin menu label defaults to 'name' so we set it explicitly to brand the menu as M Chart
331 'menu_name' => esc_html__( 'M Chart', 'm-chart' ),
332 // First submenu item label defaults to menu_name so we set it back to Charts (parent reads M Chart, list reads Charts)
333 'all_items' => esc_html__( 'Charts', 'm-chart' ),
334 'singular_name' => esc_html__( 'Chart', 'm-chart' ),
335 'add_new' => esc_html__( 'Add Chart', 'm-chart' ),
336 'add_new_item' => esc_html__( 'Add Chart', 'm-chart' ),
337 'edit' => esc_html__( 'Edit', 'm-chart' ),
338 'edit_item' => esc_html__( 'Edit Chart', 'm-chart' ),
339 'new_item' => esc_html__( 'New Chart', 'm-chart' ),
340 'view' => esc_html__( 'View', 'm-chart' ),
341 'view_item' => esc_html__( 'View Chart', 'm-chart' ),
342 'search_items' => esc_html__( 'Search Charts', 'm-chart' ),
343 'not_found' => esc_html__( 'No Charts found', 'm-chart' ),
344 'not_found_in_trash' => esc_html__( 'No Charts found in the Trash', 'm-chart' ),
345 ],
346 'register_meta_box_cb' => is_admin() ? [ $this->admin(), 'meta_boxes' ] : null,
347 'public' => true,
348 'show_in_rest' => true,
349 'hierarchical' => false,
350 'exclude_from_search' => false,
351 'menu_position' => 9,
352 'menu_icon' => 'data:image/svg+xml;base64,' . $this->logo,
353 'query_var' => true,
354 'can_export' => true,
355 'has_archive' => 'charts',
356 'description' => esc_html__( 'Manage data sets and display them as charts in WordPress.', 'm-chart' ),
357 'rewrite' => [
358 'slug' => 'chart',
359 ],
360 'supports' => [
361 'author',
362 'title',
363 'excerpt',
364 'comments',
365 ],
366 'taxonomies' => [
367 'category',
368 'post_tag',
369 $this->slug . '-units',
370 ],
371 ]
372 );
373
374 // Register the graphing library scripts
375 wp_register_script(
376 'chartjs',
377 $this->plugin_url . '/components/external/chartjs/chart.js',
378 [],
379 $this->version
380 );
381
382 wp_register_script(
383 'chartjs-helper',
384 $this->plugin_url . '/components/js/m-chart-chartjs-helper.min.js',
385 [ 'chartjs' ],
386 $this->version
387 );
388
389 wp_register_script(
390 'chartjs-datalabels',
391 $this->plugin_url . '/components/external/chartjs/chartjs-plugin-datalabels.js',
392 [ 'chartjs' ],
393 $this->version
394 );
395
396 wp_register_script(
397 'chartjs-treemap',
398 $this->plugin_url . '/components/external/chartjs/chartjs-chart-treemap.min.js',
399 [ 'chartjs' ],
400 $this->version
401 );
402
403 wp_register_script(
404 'chartjs-boxplot',
405 $this->plugin_url . '/components/external/chartjs/chartjs-chart-boxplot.min.js',
406 [ 'chartjs' ],
407 $this->version
408 );
409
410 // Add endpoint needed for iframe embed support
411 add_rewrite_endpoint( 'embed', EP_PERMALINK );
412
413 // Check if we need to run any upgrades
414 $current_version = get_site_option( 'm_chart_version' );
415
416 if ( version_compare( $current_version, '1.7', 'lt' ) ) {
417 $this->upgrade_to_1_7();
418 }
419
420 if ( version_compare( $current_version, '1.7.4', 'lt' ) ) {
421 $this->upgrade_to_1_7_4();
422 }
423 }
424
425 /**
426 * Do plugins loaded stuff
427 */
428 public function plugins_loaded() {
429 load_plugin_textdomain( 'm-chart', false, plugin_basename( dirname( __FILE__ ) ) . '/languages/' );
430 }
431
432 /**
433 * WP VIP's CDN was breaking the chart library's ability to handle embedded SVGs so this should circumvent that
434 * If you wanted to say, watermark your charts, SVGs suddenly become very important
435 *
436 * @param string $path option additional path to be used (e.g. components)
437 *
438 * @return string URL to the plugin directory with path if parameter was passed
439 */
440 public function plugin_url( $path = '' ) {
441 if ( is_admin() ) {
442 $url_base = parse_url( admin_url() );
443 } else {
444 $url_base = parse_url( home_url() );
445 }
446
447 $url_path = parse_url( plugins_url( $path, __DIR__ ) );
448
449 // Check for a port value if one exists we make sure it's honored
450 $port = '';
451
452 if ( isset( $url_base['port'] ) && 80 != $url_base['port'] ) {
453 $port = ':' . $url_base['port'];
454 }
455
456 $scheme = $url_base['scheme'] ?? 'https';
457 $host = $url_base['host'] ?? '';
458 $path = $url_path['path'] ?? '';
459 $query = $url_path['query'] ?? '';
460
461 return $scheme . '://' . $host . $port . preg_replace( '#/$#', '', $path ) . ( '' === $query ? '' : '?' . $query );
462 }
463
464 /**
465 * Get chart post meta
466 *
467 * @param int $post_id WP post ID of the post you want post meta from
468 * @param string $field optional field to be returend instead of all post meta
469 *
470 * @return array $post_meta the meta for a give post ID
471 */
472 public function get_post_meta( $post_id, $field = false ) {
473 $raw_post_meta = get_post_meta( $post_id, $this->slug, true );
474
475 $post_meta = $raw_post_meta;
476
477 $defaults = $this->chart_meta_fields;
478
479 // Make sure the correct library is set in the default chart meta fields
480 if ( ! $post_meta ) {
481 // get_current_screen() is only defined in admin context
482 // This guards the call so REST and front-end contexts don't fatal
483 $current_screen = is_admin() && function_exists( 'get_current_screen' )
484 ? get_current_screen()
485 : null;
486
487 // If we're we're adding a new chart and a library is specified in the get vars we use it
488 if (
489 is_admin()
490 && null != $current_screen
491 && 'post' == $current_screen->base
492 && 'add' == $current_screen->action
493 && isset( $_GET['library'] )
494 && $this->is_valid_library( $_GET['library'] )
495 ) {
496 $defaults['library'] = $_GET['library'];
497 } else {
498 $defaults['library'] = $this->get_library();
499 }
500 }
501
502 // Default chart height comes from the plugin settings so new charts honor the site default
503 // Saved charts already have a height so wp_parse_args leaves theirs alone
504 $defaults['height'] = (int) $this->get_settings( 'default_height' );
505
506 $post_meta = wp_parse_args( $post_meta, $defaults );
507
508 // Theme default is based off of an option so we'll handle that here
509 if ( ! isset( $post_meta['theme'] ) ) {
510 $settings = $this->get_settings();
511 $post_meta['theme'] = $settings['default_theme'];
512 }
513
514 // If there's no subtitle set we'll set an empty one
515 if ( ! isset( $post_meta['subtitle'] ) ) {
516 $post_meta['subtitle'] = '';
517 }
518
519 // If there's no y min value set we'll set it to 0
520 if ( ! isset( $post_meta['y_min_value'] ) ) {
521 $post_meta['y_min_value'] = 0;
522 }
523
524 // If the data has the old legacy format we need to update it
525 if ( isset( $post_meta['data'] ) && ! isset( $post_meta['data']['sets'] ) ) {
526 $data = $post_meta['data'];
527 $post_meta['data'] = [];
528 $post_meta['data']['sets'][] = $data;
529 }
530
531 // If there's no set_names value set we'll set it to an empty array
532 if ( ! isset( $post_meta['set_names'] ) ) {
533 $post_meta['set_names'] = [];
534 }
535
536 $post_meta = apply_filters( 'm_chart_get_post_meta', $post_meta, $raw_post_meta, $post_id );
537
538 if ( $field && isset( $post_meta[ $field ] ) ) {
539 return $post_meta[ $field ];
540 } elseif ( $field ) {
541 return null;
542 }
543
544 return $post_meta;
545 }
546
547 /**
548 * Update the post meta and set unit terms if appropriate
549 *
550 * @param int $post_id WP post ID of the post you want to attach post meta to
551 * @param array $meta an array of the post meta you want to attach to the post
552 */
553 public function update_post_meta( $post_id, $meta ) {
554 // Make sure the meta is formatted correctly and validated
555 $parsed_meta = $this->validate_post_meta( $meta );
556
557 // Set unit terms
558 $terms = [];
559
560 if ( '' != $parsed_meta['y_units'] ) {
561 $terms[] = $parsed_meta['y_units'];
562 }
563
564 if ( '' != $parsed_meta['x_units'] ) {
565 $terms[] = $parsed_meta['x_units'];
566 }
567
568 wp_set_object_terms( $post_id, $terms, $this->slug . '-units' );
569
570 // Set library as a post_tag
571 wp_set_object_terms( $post_id, $parsed_meta['library'], $this->slug . '-library' );
572
573 // Save meta to the post
574 update_post_meta( $post_id, $this->slug, $parsed_meta );
575 do_action( 'm_chart_update_post_meta', $post_id, $parsed_meta, $meta );
576 }
577
578 /**
579 * Parses a $meta array and returns a cleaned and validated array
580 *
581 * @param array $meta an array of the post meta you want to attach to the post
582 *
583 * @return array of cleaned/validated post meta
584 */
585 public function validate_post_meta( $meta ) {
586 // Need to set checkboxes before checking or they can't be deselected
587 $chart_meta['labels'] = false;
588 $chart_meta['y_min'] = false;
589 $chart_meta['legend'] = false;
590 $chart_meta['data_point_colors'] = false;
591 $chart_meta['mean_point'] = false;
592 $chart_meta['sample_points'] = false;
593 $chart_meta['constrain_y_axis'] = false;
594 $chart_meta['include_source'] = false;
595
596 // Filter values so we know the data is clean
597 foreach ( $this->chart_meta_fields as $field => $default ) {
598 if ( isset( $meta[ $field ] ) ) {
599 if ( 'source_url' == $field && '' != $meta[ $field ] ) {
600 $chart_meta[ $field ] = esc_url_raw( $meta[ $field ] );
601 } elseif ( 'data' == $field ) {
602 $chart_meta[ $field ]['sets'] = $meta[ $field ];
603 } elseif ( 'set_names' == $field ) {
604 $chart_meta[ $field ] = array_values( $meta[ $field ] );
605 } elseif ( in_array( $field, [ 'labels', 'y_min', 'legend', 'data_point_colors', 'mean_point', 'sample_points', 'constrain_y_axis', 'include_source' ] ) ) {
606 $chart_meta[ $field ] = (bool) $meta[ $field ];
607 } elseif ( 'height' == $field ) {
608 $chart_meta[ $field ] = absint( $meta[ $field ] );
609
610 if ( $chart_meta[ $field ] > 1500 ) {
611 $chart_meta[ $field ] = 1500;
612 } elseif ( $chart_meta[ $field ] < 300 ) {
613 $chart_meta[ $field ] = 300;
614 }
615 } elseif ( 'y_min_value' == $field ) {
616 $chart_meta[ $field ] = floatval( $meta[ $field ] );
617 } else {
618 $chart_meta[ $field ] = wp_filter_nohtml_kses( $meta[ $field ] );
619 }
620 } elseif ( ! isset( $chart_meta[ $field ] ) ) {
621 // Fall back on the default value if there wasn't one in the given meta
622 // Height honors the default_height setting so the save path agrees with get_post_meta()
623 if ( 'height' === $field ) {
624 $chart_meta[ $field ] = (int) $this->get_settings( 'default_height' );
625 } else {
626 $chart_meta[ $field ] = $default;
627 }
628 }
629 }
630
631 // The theme meta it isn't included in the chart_meta_fields class var so we handle it here
632 if ( isset( $meta['theme'] ) && preg_match( '#^[a-zA-Z0-9-_]+$#', $meta['theme'] ) ) {
633 $chart_meta['theme'] = $meta['theme'];
634 }
635
636 // If the data value is not an array we asume it is JSON encoded (i.e. from Jspreadsheet CE)
637 if ( ! is_array( $chart_meta['data']['sets'] ) && '' != $chart_meta['data']['sets'] ) {
638 $decoded = json_decode( stripslashes( $chart_meta['data']['sets'] ) );
639 $chart_meta['data']['sets'] = is_array( $decoded ) ? $decoded : [];
640 }
641
642 // Validate the data array
643 foreach ( $chart_meta['data']['sets'] as $key => $data ) {
644 $chart_meta['data'][ $key ] = $this->validate_data( $data );
645 }
646
647 // Allow plugins to validate their own custom meta
648 $chart_meta = apply_filters( 'm_chart_validate_post_meta', $chart_meta, $meta );
649
650 return $chart_meta;
651 }
652
653 /**
654 * Runs all of the raw data array values through wp_filter_nohtml_kses
655 *
656 * @param array $data an array of data as passed by the user
657 *
658 * @return array of validated data
659 */
660 function validate_data( $data ) {
661 if ( ! is_array( $data ) ) {
662 return wp_filter_nohtml_kses( $data );
663 }
664
665 foreach ( $data as $key => $value ) {
666 if ( is_array( $value ) ) {
667 $data[ $key ] = $this->validate_data( $value );
668 } else {
669 $value = $value ?? '';
670 $data[ $key ] = wp_filter_nohtml_kses( $value );
671 }
672 }
673
674 return $data;
675 }
676
677
678 /**
679 * Hook to save_post action and save chart related post meta
680 *
681 * @param int $post_id WP post ID of the post
682 */
683 public function save_post( $post_id ) {
684 // We do this in the main class because otherwise it won't get hooked soon enough
685 $this->admin()->save_post( $post_id );
686 }
687
688 /**
689 * Returns a chart
690 *
691 * @param int $post_id WP post ID of the chart you want
692 * @param array $args an array of args
693 *
694 * @return string HTML and Javascript needed to display a chart (or if appropriate an HTML image tag)
695 */
696 public function get_chart( $post_id = null, $args = [] ) {
697 if ( ! $post_id ) {
698 $post_id = get_the_ID();
699 }
700
701 do_action( 'm_chart_get_chart_start', $post_id, $args );
702
703 // Normalize historic usage of 'img' argument
704 if ( isset( $args['img'] ) && 'yes' == $args['img'] ) {
705 $args['show'] = 'image';
706 unset( $args['img'] );
707 }
708
709 $args = wp_parse_args( $args, $this->get_chart_default_args );
710
711 $library = $this->get_post_meta( $post_id, 'library' );
712
713 // If it's not a valid library we don't proceed
714 if ( ! $this->is_valid_library( $library ) ) {
715 return;
716 }
717
718 // Make sure we isntantiate the library so any library specific filters/setup get run
719 $this->library( $library );
720
721 // If they want the table of data we'll return that
722 if ( 'table' == $args['show'] ) {
723 return $this->build_table( $post_id );
724 }
725
726 // If they want the image version or the request is happening from a feed we return the image tag
727 if (
728 'image' == $args['show']
729 || is_feed()
730 || $this->is_amp_endpoint()
731 || apply_filters( 'm_chart_show_image', false, $post_id, $args )
732 ) {
733 $image = $this->get_chart_image( $post_id );
734
735 // Default behavior is to return the full size image but with the width/height values halved
736 // This should result in an image that looks nice on retina or better screens
737 $image['width'] = $image['width'] / 2;
738 $image['height'] = $image['height'] / 2;
739
740 $image = apply_filters( 'm_chart_get_chart_image_tag', $image, $post_id, $args );
741
742 $classes = $this->slug . ' ' . $this->slug . '-' . $post_id;
743
744 if ( $this->is_amp_endpoint() ) {
745 ob_start();
746 ?>
747 <figure class="m-chart-image-figure">
748 <amp-img src="<?php echo esc_url( $image['url'] ); ?>"
749 width="<?php echo absint( $image['width'] ); ?>" height="<?php echo absint( $image['height'] ); ?>"
750 alt="<?php echo esc_attr( get_the_title( $post_id ) ); ?>"
751 class="<?php echo esc_attr( $classes ); ?>"></amp-img>
752 <?php if ( has_action( 'm_chart_screen_reader_text' ) ) : ?>
753 <?php wp_enqueue_style( 'm-chart-frontend', $this->plugin_url . '/components/css/m-chart-frontend.css', [], $this->version ); ?>
754 <div class="screen-reader-text">
755 <?php do_action( 'm_chart_screen_reader_text', $post_id, $args ); ?>
756 </div>
757 <?php endif; ?>
758 </figure>
759 <?php
760 return ob_get_clean();
761 } else {
762 ob_start();
763 ?>
764 <figure class="m-chart-image-figure">
765 <img src="<?php echo esc_url( $image['url'] ); ?>"
766 width="<?php echo absint( $image['width'] ); ?>" height="<?php echo absint( $image['height'] ); ?>"
767 alt="<?php echo esc_attr( get_the_title( $post_id ) ); ?>"
768 class="<?php echo esc_attr( $classes ); ?>" />
769 <?php if ( has_action( 'm_chart_screen_reader_text' ) ) : ?>
770 <?php wp_enqueue_style( 'm-chart-frontend', $this->plugin_url . '/components/css/m-chart-frontend.css', [], $this->version ); ?>
771 <div class="screen-reader-text">
772 <?php do_action( 'm_chart_screen_reader_text', $post_id, $args ); ?>
773 </div>
774 <?php endif; ?>
775 </figure>
776 <?php
777 return ob_get_clean();
778 }
779 }
780
781 $settings = $this->get_settings();
782
783 if (
784 ! is_admin()
785 && 'enabled' == $settings['embeds']
786 && ! $this->is_iframe
787 ) {
788 return $this->get_chart_iframe( $post_id, $args );
789 }
790
791 // If we haven't enqueued the right library yet lets do it
792 if ( ! wp_script_is( $library, 'enqueued' ) ) {
793 wp_enqueue_script( $library );
794 wp_enqueue_script( 'chartjs-helper' );
795 }
796
797 // Ship the visually hidden rule the screen reader data table relies on
798 // so it never depends on the active theme defining .screen-reader-text
799 wp_enqueue_style( 'm-chart-frontend', $this->plugin_url . '/components/css/m-chart-frontend.css', [], $this->version );
800
801 $template = __DIR__ . '/templates/' . $library . '-chart.php';
802
803 ob_start();
804 do_action( 'm_chart_get_chart_begin', $post_id, $args );
805 require apply_filters( 'm_chart_chart_template', $template, $library, $post_id );
806 do_action( 'm_chart_get_chart_end', $post_id, $args );
807 $this->instance++;
808 return ob_get_clean();
809 }
810
811 /**
812 * Returns a charts data as an HTML table
813 *
814 * @param int $post_id WP post ID of the chart you want
815 *
816 * @return string HTML table
817 */
818 public function build_table( $post_id ) {
819 $post = get_post( $post_id );
820 $post_meta = $this->get_post_meta( $post_id );
821 $library = m_chart()->get_post_meta( $post->ID, 'library' );
822
823 $table = '';
824
825 $multiple = count( $post_meta['data']['sets'] ) > 1 ? true : false;
826
827 foreach ( $post_meta['data']['sets'] as $set => $data ) {
828 $classes = $this->slug . '-table ' . $this->slug . '-table-' . $post_id . '-' . $set;
829
830 m_chart()->parse()->parse_data( $data, $post_meta['parse_in'] );
831
832 $template = __DIR__ . '/templates/table.php';
833
834 ob_start();
835 require apply_filters( 'm_chart_table_template', $template, $library, $post->ID );
836 $table .= ob_get_clean();
837 }
838
839 return $table;
840 }
841
842 /**
843 * Return an array of image values for a chart
844 *
845 * @param int $post_id WP post ID of the chart you want an image for
846 *
847 * @return array an array of image values url, width, height, etc...
848 */
849 public function get_chart_image( $post_id ) {
850 $settings = $this->get_settings();
851
852 // If we aren't generating images we'll return false
853 if ( 'default' != $settings['performance'] ) {
854 return false;
855 }
856
857 if ( ! $thumbnail_id = get_post_meta( $post_id, '_thumbnail_id', true ) ) {
858 return false;
859 }
860
861 if ( ! $thumbnail = wp_get_attachment_image_src( $thumbnail_id, 'full' ) ) {
862 return false;
863 }
864
865 return [
866 'url' => $thumbnail[0],
867 'file' => basename( $thumbnail[0] ),
868 'width' => $thumbnail[1],
869 'height' => $thumbnail[2],
870 'name' => get_the_title( $thumbnail_id ),
871 ];
872 }
873
874 /**
875 * Filter the m_chart_get_chart_image_tag hook and return a plaecholder if appropriate
876 *
877 * @param array|bool an array of image values or false if no image could be found
878 * @param int $post_id WP post ID of the chart you want an image for
879 *
880 * @return array an array of image values url, width, height, etc...
881 */
882 public function m_chart_get_chart_image_tag( $img, $post_id ) {
883 if ( $img ) {
884 return $img;
885 }
886
887 $url = $this->plugin_url . '/components/images/chart-placeholder.png';
888
889 return [
890 'url' => $url,
891 'file' => basename( $url ),
892 'width' => 640,
893 'height' => 480,
894 'name' => get_the_title( $post_id ),
895 ];
896 }
897
898 /**
899 * Filter the the_content hook and return chart code if this is a chart
900 *
901 * @param string $content content from the current post
902 *
903 * @return string original content or chart code
904 */
905 public function the_content( $content ) {
906 // Make sure we're dealing with a chart
907 if ( get_post_type() != $this->slug ) {
908 // We aren't dealing with a chart so we'll just stop here
909 return $content;
910 }
911
912 // Call the get_chart method and let it do it's thing
913 return $this->get_chart();
914 }
915
916 /**
917 * Hook to the m_chart_image_support filter and indicate that Chart.js supports images
918 *
919 * @param string $supports_images yes/no whether the library supports image generation
920 * @param string $library the library in question
921 *
922 * @return string yes/no whether the library supports images
923 */
924 public function m_chart_image_support( $supports_images, $library ) {
925 if ( $library != $this->chart_meta_fields['library'] ) {
926 return $supports_images;
927 }
928
929 return 'yes';
930 }
931
932 /**
933 * Hook to the m_chart_instant_preview_support filter and indicate that Chart.js supports instant previews
934 *
935 * @param string $supports_images yes/no whether the library supports instant previews
936 * @param string $library the library in question
937 *
938 * @return string yes/no whether the library supports instant previews
939 */
940 public function m_chart_instant_preview_support( $supports_instant_preview, $library ) {
941 if ( $library != $this->chart_meta_fields['library'] ) {
942 return $supports_instant_preview;
943 }
944
945 return 'yes';
946 }
947
948 /**
949 * Hook to the m_chart_library_class filter and return the library class if appropriate
950 *
951 * @param string $library_class the library class
952 * @param string $library the library in question
953 *
954 * @return class the library class for this library
955 */
956 public function m_chart_library_class( $library_class, $library ) {
957 // If Chart.js wasn't requested we'll stop here ()
958 if ( $library != $this->chart_meta_fields['library'] ) {
959 return $library_class;
960 }
961
962 if ( ! $this->library_class instanceof M_Chart_Chartjs ) {
963 require_once __DIR__ . '/class-m-chart-chartjs.php';
964 $this->library_class = new M_Chart_Chartjs();
965 }
966
967 return $this->library_class;
968 }
969
970 /**
971 * Return an iframe for a given chart
972 *
973 * @param int $post_id WP post ID of the chart you want
974 * @param array $args an array of args
975 *
976 * @return string HTML needed to display a chart via an iframe
977 */
978 public function get_chart_iframe( $post_id, $args = [] ) {
979 $post_meta = $this->get_post_meta( $post_id );
980
981 $src_url = add_query_arg( $args, get_permalink( $post_id ) . 'embed/' );
982
983 $defer_lazy = 'enabled' === $this->get_settings( 'defer_rendering' );
984
985 ob_start();
986 ?>
987 <iframe id="m-chart-container-<?php echo absint( $post_id ); ?>-<?php echo absint( $this->instance ); ?>"
988 class="m-chart-iframe" width="100%" height="<?php echo absint( $post_meta['height'] + 1 ); ?>"
989 title="<?php echo esc_attr( get_the_title( $post_id ) ); ?>"
990 src="<?php echo esc_url( $src_url ); ?>"<?php echo $defer_lazy ? ' loading="lazy"' : ''; ?>></iframe>
991 <?php
992 if ( 'show' == $args['share'] ) {
993 unset( $args['share'] );
994 require apply_filters( 'm_chart_share_template', __DIR__ . '/templates/share.php' );
995 }
996 $this->instance++;
997 return ob_get_clean();
998 }
999
1000 /**
1001 * Return a chart via the [m-chart id="x"] short code
1002 *
1003 * Also registered under the legacy [chart] name for backwards compatibility
1004 *
1005 * @param array $args an array of arguments passed by the WP short code API
1006 *
1007 * @return string the chart requested in Javascript or HTML form
1008 */
1009 public function chart_shortcode( $args ) {
1010 $default_args = $this->get_chart_default_args;
1011 $default_args['id'] = '';
1012
1013 $args = shortcode_atts( $default_args, $args );
1014
1015 $post_id = $args['id'];
1016 unset( $args['id'] );
1017
1018 // Did we get what looks like a chart ID?
1019 if ( ! is_numeric( $post_id ) ) {
1020 return;
1021 }
1022
1023 // Make sure the chart actually exists and that it's a chart so we don't fatal later
1024 if ( $this->slug != get_post_type( $post_id ) ) {
1025 return;
1026 }
1027
1028 // Is it published?
1029 if ( 'publish' != get_post_status( $post_id ) ) {
1030 return;
1031 }
1032
1033 return $this->get_chart( $post_id, $args );
1034 }
1035
1036 /**
1037 * Helper function that prevents issues with stripslashes and unicode characters
1038 * stripslashes will strip off the slash before unicode characters which is sucky, this prevents that
1039 *
1040 * @param string $string a string that may have unicode characters as well as unecessary escaping
1041 *
1042 * @return string a string with any unecessary escaping removed
1043 */
1044 public function unicode_aware_stripslashes( $string ) {
1045 return stripslashes( preg_replace( '#\\\u[a-fA-F0-9]{4}#', '\\\\$0', $string ) );
1046 }
1047
1048 /**
1049 * Helper function that returns the chart unit terms
1050 *
1051 * @return array an array of generated and/or compiled unit terms
1052 */
1053 public function get_unit_terms() {
1054 $terms = get_terms( $this->slug . '-units', [ 'hide_empty' => false ] );
1055
1056 if ( empty( $terms ) ) {
1057 $terms = $this->generate_unit_terms();
1058 }
1059
1060 return $this->compile_unit_terms( $terms );
1061 }
1062
1063 /**
1064 * Helper function that compiles the chart unit terms for use
1065 *
1066 * @param array an array of unit terms
1067 *
1068 * @return array an array of compiled unit terms
1069 */
1070 public function compile_unit_terms( $terms ) {
1071 $compiled_terms = [];
1072 $parents = [];
1073
1074 foreach ( $terms as $unit ) {
1075 if ( 0 == $unit->parent ) {
1076 $parents[ $unit->term_id ] = $unit->name;
1077 }
1078 }
1079
1080 foreach ( $terms as $unit ) {
1081 if ( 0 != $unit->parent && isset( $parents[ $unit->parent ] ) ) {
1082 $compiled_terms[ $parents[ $unit->parent ] ][] = $unit;
1083 }
1084 }
1085
1086 ksort( $compiled_terms );
1087
1088 return $compiled_terms;
1089 }
1090
1091 /**
1092 * Helper function that populates the unit terms taxonomy with a default set of terms
1093 *
1094 * @return array an array of the newly generated unit terms
1095 */
1096 public function generate_unit_terms() {
1097 // Load the default terms array
1098 $default_terms = require __DIR__ . '/array-default-unit-terms.php';
1099
1100 $terms = [];
1101
1102 foreach ( $default_terms as $parent_term => $child_terms ) {
1103 $parent = (object) wp_insert_term( $parent_term, $this->slug . '-units' );
1104 $terms[] = get_term( $parent->term_id, $this->slug . '-units' );
1105
1106 foreach ( $child_terms as $child_term ) {
1107 $term = (object) wp_insert_term( $child_term, $this->slug . '-units', [ 'parent' => $parent->term_id ] );
1108 $terms[] = get_term( $term->term_id, $this->slug . '-units' );
1109 }
1110 }
1111
1112 return $terms;
1113 }
1114
1115 /**
1116 * Looks for the embed endpoint and serves up the requested chart if appropriate
1117 */
1118 public function template_redirect() {
1119 global $wp_query;
1120
1121 // Make sure this is a chart with the embed endpoint in the URL
1122 if ( ! isset( $wp_query->query['post_type'] ) || ! isset( $wp_query->query['embed'] ) || 'm-chart' != $wp_query->query['post_type'] ) {
1123 return;
1124 }
1125
1126 $post = get_post();
1127
1128 if ( ! $post ) {
1129 wp_die(
1130 esc_html__( 'The chart could not be found', 'm-chart' ),
1131 esc_html__( 'Chart not found', 'm-chart' ),
1132 [ 'response' => 404 ]
1133 );
1134 }
1135
1136 $settings = $this->get_settings();
1137
1138 if ( 'enabled' != $settings['embeds'] ) {
1139 wp_die(
1140 esc_html__( 'Embeds of this type are not enabled', 'm-chart' ),
1141 esc_html__( 'Embeds disabled', 'm-chart' ),
1142 [ 'response' => 403 ]
1143 );
1144 exit;
1145 }
1146
1147 $this->is_iframe = true;
1148
1149 $scripts = [
1150 'jquery',
1151 $this->get_post_meta( $post->ID, 'library' ),
1152 ];
1153
1154 unset( $_GET['action'], $_GET['share'] );
1155
1156 // This prevents issues when embedding with outside sites
1157 header_remove( 'X-Frame-Options' );
1158
1159 status_header( 200 );
1160
1161 require __DIR__ . '/templates/iframe.php';
1162 exit;
1163 }
1164
1165 /**
1166 * Hook to the m_chart_update_post_meta action and call the required library specific function
1167 *
1168 * @param int $post_id WP post ID of the post you want chart args for
1169 * @param array $parsed_meta the parsed chart meta passed by the action hook
1170 */
1171 public function m_chart_update_post_meta( $post_id, $parsed_meta ) {
1172 $this->library( $parsed_meta['library'] )->m_chart_update_post_meta( $post_id, $parsed_meta );
1173 }
1174
1175 /**
1176 * If the passed library is valid return TRUE otherwise FALSE
1177 *
1178 * @param string library string (i.e. 'highcharts')
1179 *
1180 * @return bool
1181 */
1182 public function is_valid_library( $library ) {
1183 $libraries = $this->get_libraries();
1184
1185 if ( ! isset( $libraries[ $library ] ) ) {
1186 return false;
1187 }
1188
1189 return true;
1190 }
1191
1192 /**
1193 * If current page is an AMP page returns true
1194 *
1195 * @return bool
1196 */
1197 public function is_amp_endpoint() {
1198 if ( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() ) {
1199 return true;
1200 }
1201
1202 return false;
1203 }
1204
1205 /**
1206 * Return the M Chart settings
1207 *
1208 * @return array current settings
1209 */
1210 public function get_settings( $setting = false ) {
1211 // Memoize the resolved settings, get_post_meta() calls this on every invocation
1212 // and the option + filter pipeline doesn't need to re-run within a request
1213 // Invalidated via reset_settings() when the settings are saved
1214 if ( isset( $this->resolved_settings ) ) {
1215 $settings = $this->resolved_settings;
1216 } else {
1217 // Allow third party libraries to modify the default settings
1218 $default_settings = apply_filters( 'm_chart_default_settings', $this->settings );
1219
1220 $settings = (array) get_option( $this->slug, $default_settings );
1221 $settings = wp_parse_args( $settings, $default_settings );
1222
1223 // Make sure the set library is still valid
1224 if ( ! $this->is_valid_library( $settings['library'] ) ) {
1225 $settings['library'] = 'chartjs';
1226 }
1227
1228 $settings = apply_filters( 'm_chart_get_settings', $settings );
1229
1230 $this->resolved_settings = $settings;
1231 }
1232
1233 if ( $setting && isset( $settings[ $setting ] ) ) {
1234 return $settings[ $setting ];
1235 } elseif ( $setting ) {
1236 return null;
1237 }
1238
1239 return $settings;
1240 }
1241
1242 /**
1243 * Drop the memoized settings so the next get_settings() call re-resolves them
1244 *
1245 * Called after the settings option is updated
1246 */
1247 public function reset_settings() {
1248 $this->resolved_settings = null;
1249 }
1250
1251 /**
1252 * Return an array of available charting libraries
1253 *
1254 * @return array current settings
1255 */
1256 public function get_libraries() {
1257 return apply_filters( 'm_chart_get_libraries', $this->libraries );
1258 }
1259
1260 /**
1261 * Return the chart types that support multiple data sets (multi-sheet tab bar)
1262 *
1263 * This is the single authoritative source for this list
1264 * The value is passed to window.m_chart_admin.multi_sheet_types so the React UI always stays in sync with PHP
1265 * Filtered via 'm_chart_multi_sheet_types' so library plugins can add or remove types
1266 *
1267 * @return array
1268 */
1269 public function get_multi_sheet_types() {
1270 return apply_filters( 'm_chart_multi_sheet_types', [
1271 'scatter',
1272 'bubble',
1273 'radar',
1274 'radar-area',
1275 'boxplot',
1276 'violin',
1277 ] );
1278 }
1279
1280 /**
1281 * Return the current library
1282 *
1283 * @return string current library
1284 */
1285 public function get_library() {
1286 if ( isset( $_GET['library'] ) && $this->is_valid_library( $_GET['library'] ) ) {
1287 return $_GET['library'];
1288 }
1289
1290 return $this->get_settings( 'library' );
1291 }
1292
1293 /**
1294 * Return the locale array
1295 *
1296 * When PHP's Intl extension is available, labels are generated in each locale's native language using Locale::getDisplayName()
1297 * Falls back to the English labels in array-locale-codes.php otherwise
1298 *
1299 * @return array locales as used by Intl.NumberFormat
1300 */
1301 public function get_locales() {
1302 $locales = require __DIR__ . '/array-locale-codes.php';
1303
1304 if ( ! class_exists( 'Locale' ) ) {
1305 return $locales;
1306 }
1307
1308 foreach ( $locales as $code => $english_label ) {
1309 $native = Locale::getDisplayName( $code, $code );
1310
1311 if ( $native ) {
1312 $locales[ $code ] = esc_html( $native );
1313 }
1314 }
1315
1316 return $locales;
1317 }
1318
1319 /**
1320 * Return a recursively merged array out of the two given
1321 *
1322 * @param array a multi dimensional array that will be merged into
1323 * @param array a multi dimensional array that will be merged recursively into the first one
1324 *
1325 * @return array the merged array
1326 */
1327 public function array_merge_recursive( &$a, $b ) {
1328 foreach ( $b as $child => $value ) {
1329 if ( isset( $a[ $child ] ) ) {
1330 // New value exists so we'll need to move a level down
1331 if ( is_array( $a[ $child ] ) && is_array( $value ) ) {
1332 $this->array_merge_recursive( $a[ $child ], $value );
1333 } else {
1334 // New value is not an array so we override the old value with the new one
1335 $a[ $child ] = $value;
1336 }
1337 } else {
1338 // New value doesn't exist so we can just add it
1339 $a[ $child ] = $value;
1340 }
1341 }
1342
1343 return $a;
1344 }
1345
1346 /**
1347 * Do things needed for version 1.7
1348 */
1349 public function upgrade_to_1_7() {
1350 // Get all charts
1351 $charts = get_posts(
1352 [
1353 'post_type' => m_chart()->slug,
1354 'posts_per_page' => -1,
1355 'post_status' => 'any',
1356 'tax_query' => [
1357 [
1358 'taxonomy' => 'post_tag',
1359 'field' => 'slug',
1360 'terms' => 'highcharts',
1361 'operator' => 'NOT IN',
1362 ],
1363 ],
1364 ]
1365 );
1366
1367 // Add highcharts post_tag to all charts
1368 foreach ( $charts as $chart ) {
1369 wp_set_object_terms( $chart->ID, 'highcharts', 'post_tag' );
1370 }
1371
1372 // Update version
1373 update_site_option( 'm_chart_version', '1.7' );
1374 }
1375
1376 /**
1377 * Do things needed for version 1.7.4
1378 */
1379 public function upgrade_to_1_7_4() {
1380 // Get all charts tagged with highcharts
1381 $highcharts_charts = get_posts(
1382 [
1383 'post_type' => m_chart()->slug,
1384 'posts_per_page' => -1,
1385 'post_status' => 'any',
1386 'tax_query' => [
1387 [
1388 'taxonomy' => 'post_tag',
1389 'field' => 'slug',
1390 'terms' => 'highcharts',
1391 'operator' => 'IN',
1392 ],
1393 ],
1394 ]
1395 );
1396
1397 foreach ( $highcharts_charts as $chart ) {
1398 // Add highcharts as a term to the m-chart-library taxonomy
1399 wp_set_object_terms( $chart->ID, 'highcharts', m_chart()->slug . '-library' );
1400 // Remove highcharts as a term from the post_tag taxonomy
1401 wp_remove_object_terms( $chart->ID, 'highcharts', 'post_tag' );
1402 }
1403
1404 // Get all charts tagged with chartjs
1405 $chartjs_charts = get_posts(
1406 [
1407 'post_type' => m_chart()->slug,
1408 'posts_per_page' => -1,
1409 'post_status' => 'any',
1410 'tax_query' => [
1411 [
1412 'taxonomy' => 'post_tag',
1413 'field' => 'slug',
1414 'terms' => 'chartjs',
1415 'operator' => 'IN',
1416 ],
1417 ],
1418 ]
1419 );
1420
1421 foreach ( $chartjs_charts as $chart ) {
1422 // Add chartjs as a term to the m-chart-library taxonomy
1423 wp_set_object_terms( $chart->ID, 'chartjs', m_chart()->slug . '-library' );
1424 // Remove chartjs as a term from the post_tag taxonomy
1425 wp_remove_object_terms( $chart->ID, 'chartjs', 'post_tag' );
1426 }
1427
1428 // Update version
1429 update_site_option( 'm_chart_version', '1.7.4' );
1430 }
1431 }
1432
1433 /**
1434 * Plugin object accessor
1435 */
1436 function m_chart() {
1437 global $m_chart;
1438
1439 if ( ! $m_chart instanceof M_Chart ) {
1440 $m_chart = new M_Chart();
1441 }
1442
1443 return $m_chart;
1444 }
1445
1446 /**
1447 * Freemius SDK accessor
1448 *
1449 * Thin global wrapper that delegates to the core class so Freemius ecosystem code and conventions keep working
1450 * The actual configuration lives in M_Chart::freemius()
1451 *
1452 * @return Freemius the Freemius SDK instance for M Chart
1453 */
1454 function m_chart_fs() {
1455 return m_chart()->freemius();
1456 }
1457