PluginProbe
M Chart / 2.2
M Chart v2.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, at components/class-m-chart.php

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