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

1,199 lines 34.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 class M_Chart {
4 public $version = '1.11.2';
5 public $slug = 'm-chart';
6 public $plugin_name = 'Chart';
7 public $chart_meta_fields = array(
8 'library' => 'chartjs',
9 'type' => 'line',
10 'parse_in' => 'rows',
11 'labels' => true,
12 'shared' => false,
13 'subtitle' => '',
14 'y_title' => '',
15 'y_units' => '',
16 'y_min' => false,
17 'y_min_value' => 0,
18 'x_title' => '',
19 'x_units' => '',
20 'height' => 400,
21 'legend' => true,
22 'source' => '',
23 'source_url' => '',
24 'data' => array(),
25 'set_names' => array(),
26 );
27 public $get_chart_default_args = array(
28 'show' => 'chart',
29 'width' => 'responsive',
30 'share' => '',
31 );
32 public $parse_options = array(
33 'columns',
34 'rows',
35 );
36 public $options_set;
37 public $plugin_url;
38 public $is_iframe = false;
39 public $instance = 1;
40 public $settings = array(
41 'library' => 'chartjs',
42 'show_library' => 'no',
43 'performance' => 'default',
44 'image_multiplier' => '2',
45 'image_width' => '600',
46 'embeds' => '',
47 'default_theme' => '_default',
48 'locale' => 'en-US',
49 'csv_delimiter' => ',',
50 'lang_settings' => array(
51 'decimalPoint' => '.',
52 'thousandsSep' => ',',
53 'numericSymbols' => array(
54 'K', // Thousands
55 'M', // Millions
56 'B', // Billions
57 'T', // Trillions
58 'P', // Quadrillions
59 'E', // Quintillions
60 ),
61 'numericSymbolMagnitude' => 1000,
62 ),
63 );
64 public $csv_delimiters = array(
65 ',' => 'Comma',
66 "\t" => 'Tab',
67 ' ' => 'Space',
68 ';' => 'Semicolon',
69 );
70 public $library_class;
71
72 private $admin;
73 private $parse;
74 private $block;
75 private $libraries = array(
76 'chartjs' => 'Chart.js',
77 );
78
79 /**
80 * Constructor
81 */
82 public function __construct() {
83 $this->plugin_url = $this->plugin_url();
84
85 add_action( 'init', array( $this, 'init' ) );
86 add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ) );
87 add_action( 'save_post', array( $this, 'save_post' ) );
88 add_action( 'shortcode_ui_before_do_shortcode', array( $this, 'shortcode_ui_before_do_shortcode' ) );
89 // Doing this early as possible because it sets is_iframe which we might need to use for other things
90 add_action( 'template_redirect', array( $this, 'template_redirect' ), 0 );
91 add_action( 'm_chart_update_post_meta', array( $this, 'm_chart_update_post_meta' ), 10, 2 );
92
93 // Doing this before the default so it's already done before anything else
94 add_filter( 'm_chart_get_chart_image_tag', array( $this, 'm_chart_get_chart_image_tag' ), 9, 3 );
95 add_filter( 'the_content', array( $this, 'the_content' ) );
96 add_filter( 'm_chart_image_support', array( $this, 'm_chart_image_support' ), 10, 2 );
97 add_filter( 'm_chart_instant_preview_support', array( $this, 'm_chart_instant_preview_support' ), 10, 2 );
98 add_filter( 'm_chart_library_class', array( $this, 'm_chart_library_class' ), 10, 2 );
99
100 add_shortcode( 'chart', array( $this, 'chart_shortcode' ) );
101 add_shortcode( 'chart-share', array( $this, 'share_shortcode' ) );
102
103 // Initiate the block class
104 $this->block();
105 }
106
107 /**
108 * Admin object accessor
109 */
110 public function admin() {
111 if ( ! $this->admin ) {
112 require_once __DIR__ . '/class-m-chart-admin.php';
113 $this->admin = new M_Chart_Admin();
114 }
115
116 return $this->admin;
117 }
118
119 /**
120 * Block object accessor
121 */
122 public function block() {
123 if ( ! $this->block ) {
124 require_once __DIR__ . '/class-m-chart-block.php';
125 $this->block = new M_Chart_Block();
126 }
127
128 return $this->block;
129 }
130
131 /**
132 * Library object accessor
133 */
134 public function library( $library = false ) {
135 if ( ! $library ) {
136 $library = $this->get_library();
137 }
138
139 return apply_filters( 'm_chart_library_class', $this->library_class, $library );
140 }
141
142 /**
143 * Parse object accessor
144 */
145 public function parse() {
146 if ( ! $this->parse ) {
147 require_once __DIR__ . '/class-m-chart-parse.php';
148 $this->parse = new M_Chart_Parse();
149 }
150
151 return $this->parse;
152 }
153
154 /**
155 * Do init stuff
156 */
157 public function init() {
158 // Register the units taxonomy
159 register_taxonomy(
160 $this->slug . '-units',
161 array( $this->slug ),
162 array(
163 'hierarchical' => true,
164 'labels' => array(
165 'name' => esc_html__( 'Chart Units', 'm-chart' ),
166 'singular_name' => esc_html__( 'Chart Unit', 'm-chart' ),
167 'search_items' => esc_html__( 'Search Chart Units', 'm-chart' ),
168 'all_items' => esc_html__( 'All Chart Units', 'm-chart' ),
169 'parent_item' => esc_html__( 'Parent Chart Unit', 'm-chart' ),
170 'parent_item_colon' => esc_html__( 'Parent Chart Unit:', 'm-chart' ),
171 'edit_item' => esc_html__( 'Edit Chart Unit', 'm-chart' ),
172 'update_item' => esc_html__( 'Update Chart Unit', 'm-chart' ),
173 'add_new_item' => esc_html__( 'Add New Chart Unit', 'm-chart' ),
174 'new_item_name' => esc_html__( 'Chart Unit Name', 'm-chart' ),
175 'menu_name' => esc_html__( 'Chart Units', 'm-chart' ),
176 ),
177 'show_ui' => true,
178 'query_var' => true,
179 'rewrite' => array(
180 'slug' => $this->slug . '-units',
181 ),
182 )
183 );
184
185 // Register the library taxonomy
186 register_taxonomy(
187 $this->slug . '-library',
188 array( $this->slug ),
189 array(
190 'hierarchical' => false,
191 'show_ui' => false,
192 'query_var' => true,
193 'rewrite' => array(
194 'slug' => $this->slug . '-library',
195 ),
196 )
197 );
198
199 // Register the charts custom post type
200 register_post_type(
201 $this->slug,
202 array(
203 'labels' => array(
204 'name' => esc_html__( 'Charts', 'm-chart' ),
205 'singular_name' => esc_html__( 'Chart', 'm-chart' ),
206 'add_new' => esc_html__( 'Add Chart', 'm-chart' ),
207 'add_new_item' => esc_html__( 'Add Chart', 'm-chart' ),
208 'edit' => esc_html__( 'Edit', 'm-chart' ),
209 'edit_item' => esc_html__( 'Edit Chart', 'm-chart' ),
210 'new_item' => esc_html__( 'New Chart', 'm-chart' ),
211 'view' => esc_html__( 'View', 'm-chart' ),
212 'view_item' => esc_html__( 'View Chart', 'm-chart' ),
213 'search_items' => esc_html__( 'Search Charts', 'm-chart' ),
214 'not_found' => esc_html__( 'No Charts found', 'm-chart' ),
215 'not_found_in_trash' => esc_html__( 'No Charts found in the Trash', 'm-chart' ),
216 ),
217 'register_meta_box_cb' => is_admin() ? array( $this->admin(), 'meta_boxes' ) : null,
218 'public' => true,
219 'show_in_rest' => true,
220 'hierarchical' => false,
221 'exclude_from_search' => false,
222 'menu_position' => 9,
223 'menu_icon' => 'dashicons-chart-pie',
224 'query_var' => true,
225 'can_export' => true,
226 'has_archive' => 'charts',
227 'description' => esc_html__( 'Manage data sets and display them as charts in WordPress.', 'm-chart' ),
228 'rewrite' => array(
229 'slug' => 'chart',
230 ),
231 'supports' => array(
232 'author',
233 'title',
234 'excerpt',
235 'comments',
236 ),
237 'taxonomies' => array(
238 'category',
239 'post_tag',
240 $this->slug . '-units',
241 ),
242 )
243 );
244
245 // Register the graphing library scripts
246 wp_register_script(
247 'chartjs-helpers',
248 $this->plugin_url . '/components/js/m-chart-chartjs-helpers.min.js',
249 array( 'jquery' ),
250 $this->version
251 );
252
253 wp_register_script(
254 'chartjs',
255 $this->plugin_url . '/components/external/chartjs/chart.js',
256 array( 'jquery', 'chartjs-helpers' ),
257 $this->version
258 );
259
260 wp_register_script(
261 'chartjs-datalabels',
262 $this->plugin_url . '/components/external/chartjs/chartjs-plugin-datalabels.js',
263 array( 'jquery', 'chartjs' ),
264 $this->version
265 );
266
267 // jQuery needs to be in the header since the charts are inline
268 wp_enqueue_script( 'jquery', false, array(), false, false );
269
270 // Add endpoint needed for iframe embed support
271 add_rewrite_endpoint( 'embed', EP_PERMALINK );
272
273 // Check if we need to run any upgrades
274 $current_version = get_site_option( 'm_chart_version' );
275
276 if ( version_compare( $current_version, '1.7', 'lt' ) ) {
277 $this->upgrade_to_1_7();
278 }
279
280 if ( version_compare( $current_version, '1.7.4', 'lt' ) ) {
281 $this->upgrade_to_1_7_4();
282 }
283 }
284
285 /**
286 * Do plugins loaded stuff
287 */
288 public function plugins_loaded() {
289 load_plugin_textdomain( 'm-chart', false, plugin_basename( dirname( __FILE__ ) ) . '/languages/' );
290 }
291
292 /**
293 * VIP's CDN was breaking Highcharts ability to handle embedded SVGs so this should circumvent that
294 * If you wanted to say, watermark your charts, SVGs suddenly become very important
295 *
296 * @param string $path option additional path to be used (e.g. components)
297 *
298 * @return string URL to the plugin directory with path if parameter was passed
299 */
300 public function plugin_url( $path = '' ) {
301 if ( is_admin() ) {
302 $url_base = parse_url( admin_url() );
303 } else {
304 $url_base = parse_url( home_url() );
305 }
306
307 $url_path = parse_url( plugins_url( $path, __DIR__ ) );
308
309 // Check for a port value if one exists we make sure it's honored
310 $port = '';
311
312 if ( isset( $url_base['port'] ) && 80 != $url_base['port'] ) {
313 $port = ':' . $url_base['port'];
314 }
315
316 return $url_base['scheme'] . '://' . $url_base['host'] . $port . preg_replace( '#/$#', '', $url_path['path'] ) . ( empty( $url_path['query'] ) ? '' : '?' . $url_path['query'] );
317 }
318
319 /**
320 * Get chart post meta
321 *
322 * @param int $post_id WP post ID of the post you want post meta from
323 * @param string $field optional field to be returend instead of all post meta
324 *
325 * @return string URL to the plugin directory with path if parameter was passed
326 */
327 public function get_post_meta( $post_id, $field = false ) {
328 $raw_post_meta = get_post_meta( $post_id, $this->slug, true );
329
330 $post_meta = $raw_post_meta;
331
332 $defaults = $this->chart_meta_fields;
333
334 // Make sure the correct library is set in the default chart meta fields
335 if ( ! $post_meta ) {
336 $current_screen = get_current_screen();
337
338 // If we're we're adding a new chart and a library is specified in the get vars we use it
339 if (
340 is_admin()
341 && null != $current_screen
342 && 'post' == $current_screen->base
343 && 'add' == $current_screen->action
344 && isset( $_GET['library'] )
345 && $this->is_valid_library( $_GET['library'] )
346 ) {
347 $defaults['library'] = $_GET['library'];
348 } else {
349 $defaults['library'] = $this->get_library();
350 }
351 }
352
353 $post_meta = wp_parse_args( $post_meta, $defaults );
354
355 // Theme default is based off of an option so we'll handle that here
356 if ( ! isset( $post_meta['theme'] ) ) {
357 $settings = $this->get_settings();
358 $post_meta['theme'] = $settings['default_theme'];
359 }
360
361 // If there's no subtitle set we'll set an empty one
362 if ( ! isset( $post_meta['subtitle'] ) ) {
363 $post_meta['subtitle'] = '';
364 }
365
366 // If there's no y min value set we'll set it to 0
367 if ( ! isset( $post_meta['y_min_value'] ) ) {
368 $post_meta['y_min_value'] = 0;
369 }
370
371 // If the data has the old legacy format we need to update it
372 if ( isset( $post_meta['data'] ) && ! isset( $post_meta['data']['sets'] ) ) {
373 $data = $post_meta['data'];
374 $post_meta['data'] = array();
375 $post_meta['data']['sets'][] = $data;
376 }
377
378 // If there's no set_names value set we'll set it to an empty array
379 if ( ! isset( $post_meta['set_names'] ) ) {
380 $post_meta['set_names'] = array();
381 }
382
383 $post_meta = apply_filters( 'm_chart_get_post_meta', $post_meta, $raw_post_meta, $post_id );
384
385 if ( $field && isset( $post_meta[ $field ] ) ) {
386 return $post_meta[ $field ];
387 } elseif ( $field ) {
388 return null;
389 }
390
391 return $post_meta;
392 }
393
394 /**
395 * Update the post meta based and set unit terms if appropriate
396 *
397 * @param int $post_id WP post ID of the post you want to attach post meta to
398 * @param array $meta an array of the post meta you want to attach to the post
399 */
400 public function update_post_meta( $post_id, $meta ) {
401 // Make sure the meta is formatted correctly and validated
402 $parsed_meta = $this->validate_post_meta( $meta );
403
404 // Set unit terms
405 $terms = array();
406
407 if ( '' != $parsed_meta['y_units'] ) {
408 $terms[] = $parsed_meta['y_units'];
409 }
410
411 if ( '' != $parsed_meta['x_units'] ) {
412 $terms[] = $parsed_meta['x_units'];
413 }
414
415 wp_set_object_terms( $post_id, $terms, $this->slug . '-units' );
416
417 // Set library as a post_tag
418 wp_set_object_terms( $post_id, $parsed_meta['library'], $this->slug . '-library' );
419
420 // Save meta to the post
421 update_post_meta( $post_id, $this->slug, $parsed_meta );
422 do_action( 'm_chart_update_post_meta', $post_id, $parsed_meta, $meta );
423 }
424
425 /**
426 * Parses a $meta array and returns a cleaned and validated array
427 *
428 * @param array $meta an array of the post meta you want to attach to the post
429 *
430 * @return array of cleaned/validated post meta
431 */
432 public function validate_post_meta( $meta ) {
433 // Need to set checkboxes before checking or they can't be deselected
434 $chart_meta['labels'] = false;
435 $chart_meta['y_min'] = false;
436 $chart_meta['legend'] = false;
437
438 // Filter values so we know the data is clean
439 foreach ( $this->chart_meta_fields as $field => $default ) {
440 if ( isset( $meta[ $field ] ) ) {
441 if ( 'source_url' == $field && '' != $meta[ $field ] ) {
442 $chart_meta[ $field ] = esc_url_raw( $meta[ $field ] );
443 } elseif ( 'data' == $field ) {
444 $chart_meta[ $field ]['sets'] = $meta[ $field ];
445 } elseif ( 'set_names' == $field ) {
446 $chart_meta[ $field ] = array_values( $meta[ $field ] );
447 } elseif ( in_array( $field, array( 'labels', 'y_min', 'legend' ) ) ) {
448 $chart_meta[ $field ] = (bool) $meta[ $field ];
449 } elseif ( 'height' == $field ) {
450 $chart_meta[ $field ] = absint( $meta[ $field ] );
451
452 if ( $chart_meta[ $field ] > 1500 ) {
453 $chart_meta[ $field ] = 1500;
454 } elseif ( $chart_meta[ $field ] < 300 ) {
455 $chart_meta[ $field ] = 300;
456 }
457 } elseif ( 'y_min_value' == $field ) {
458 $chart_meta[ $field ] = floatval( $meta[ $field ] );
459 } else {
460 $chart_meta[ $field ] = wp_filter_nohtml_kses( $meta[ $field ] );
461 }
462 } elseif ( ! isset( $chart_meta[ $field ] ) ) {
463 // Fall back on the default value if there wasn't one in the given meta
464 $chart_meta[ $field ] = $default;
465 }
466 }
467
468 // The theme meta it isn't included in the chart_meta_fields class var so we handle it here
469 if ( isset( $meta['theme'] ) && preg_match( '#^[a-zA-Z0-9-_]+$#', $meta['theme'] ) ) {
470 $chart_meta['theme'] = $meta['theme'];
471 }
472
473 // If the data value is not an array we asume it is JSON encoded (i.e. from Handsontable)
474 if ( ! is_array( $chart_meta['data']['sets'] ) && '' != $chart_meta['data']['sets'] ) {
475 $chart_meta['data']['sets'] = json_decode( stripslashes( $chart_meta['data']['sets'] ) );
476 }
477
478 // Validate the data array
479 foreach ( $chart_meta['data']['sets'] as $key => $data ) {
480 $chart_meta['data'][ $key ] = $this->validate_data( $data );
481 }
482
483 // Allow plugins to validate their own custom meta
484 $chart_meta = apply_filters( 'm_chart_validate_post_meta', $chart_meta, $meta );
485
486 return $chart_meta;
487 }
488
489 /**
490 * Runs all of the raw data array values through wp_filter_nohtml_kses
491 *
492 * @param array $data an array of data as passed by the user
493 *
494 * @return array of validated data
495 */
496 function validate_data( $data ) {
497 if ( ! is_array( $data ) ) {
498 return wp_filter_nohtml_kses( $data );
499 }
500
501 foreach ( $data as $key => $value ) {
502 if ( is_array( $value ) ) {
503 $data[ $key ] = $this->validate_data( $value );
504 } else {
505 $value = $value ?? '';
506 $data[ $key ] = wp_filter_nohtml_kses( $value );
507 }
508 }
509
510 return $data;
511 }
512
513
514 /**
515 * Hook to save_post action and save chart related post meta
516 *
517 * @param int $post_id WP post ID of the post
518 */
519 public function save_post( $post_id ) {
520 // We do this in the main class because otherwise it won't get hooked soon enough
521 $this->admin()->save_post( $post_id );
522 }
523
524 /**
525 * Returns a chart
526 *
527 * @param int $post_id WP post ID of the chart you want
528 * @param array $args an array of args
529 *
530 * @return string HTML and Javascript needed to display a chart (or if appropriate an HTML image tag)
531 */
532 public function get_chart( $post_id = null, $args = array() ) {
533 if ( ! $post_id ) {
534 $post_id = get_the_ID();
535 }
536
537 do_action( 'm_chart_get_chart_start', $post_id, $args );
538
539 // Normalize historic usage of 'img' argument
540 if ( isset( $args['img'] ) && 'yes' == $args['img'] ) {
541 $args['show'] = 'image';
542 unset( $args['img'] );
543 }
544
545 $args = wp_parse_args( $args, $this->get_chart_default_args );
546
547 $library = $this->get_post_meta( $post_id, 'library' );
548
549 // If it's not a valid library we don't proceed
550 if ( ! $this->is_valid_library( $library ) ) {
551 return;
552 }
553
554 // Make sure we isntantiate the library so any library specific filters/setup get run
555 $this->library( $library );
556
557 // If they want the table of data we'll return that
558 if ( 'table' == $args['show'] ) {
559 return $this->build_table( $post_id );
560 }
561
562 // If they want the image version or the request is happening from a feed we return the image tag
563 if ( 'image' == $args['show']
564 || is_feed()
565 || $this->is_amp_endpoint()
566 || apply_filters( 'm_chart_show_image', false, $post_id, $args )
567 ) {
568 $image = $this->get_chart_image( $post_id );
569
570 // Default behavior is to return the full size image but with the width/height values halved
571 // This should result in an image that looks nice on retina or better screens
572 $image['width'] = $image['width'] / 2;
573 $image['height'] = $image['height'] / 2;
574
575 $image = apply_filters( 'm_chart_get_chart_image_tag', $image, $post_id, $args );
576
577 $classes = $this->slug . ' ' . $this->slug . '-' . $post_id;
578
579 if ( $this->is_amp_endpoint() ) {
580 ob_start();
581 ?><amp-img src="<?php echo esc_url( $image['url'] ); ?>" alt="<?php echo esc_attr( $image['name'] ); ?>"
582 width="<?php echo absint( $image['width'] ); ?>" height="<?php echo absint( $image['height'] ); ?>"
583 class="<?php echo esc_attr( $classes ); ?>"></amp-img>
584 <?php
585 return ob_get_clean();
586 } else {
587 ob_start();
588 ?>
589 <img src="<?php echo esc_url( $image['url'] ); ?>" alt="<?php echo esc_attr( $image['name'] ); ?>"
590 width="<?php echo absint( $image['width'] ); ?>" height="<?php echo absint( $image['height'] ); ?>"
591 alt="<?php echo esc_attr( get_the_title( $post_id ) ); ?>" class="<?php echo esc_attr( $classes ); ?>" />
592 <?php
593 return ob_get_clean();
594 }
595 }
596
597 $settings = $this->get_settings();
598
599 if (
600 ! is_admin()
601 && 'enabled' == $settings['embeds']
602 && ! $this->is_iframe
603 ) {
604 return $this->get_chart_iframe( $post_id, $args );
605 }
606
607 // If we haven't enqueued the right library yet lets do it
608 if ( ! wp_script_is( $library, 'enqueued' ) ) {
609 wp_enqueue_script( $library );
610 }
611
612 $template = __DIR__ . '/templates/' . $library . '-chart.php';
613
614 ob_start();
615 do_action( 'm_chart_get_chart_begin', $post_id, $args );
616 require apply_filters( 'm_chart_chart_template', $template, $library, $post_id );
617 do_action( 'm_chart_get_chart_end', $post_id, $args );
618 $this->instance++;
619 return ob_get_clean();
620 }
621
622 /**
623 * Returns a charts data as an HTML table
624 *
625 * @param int $post_id WP post ID of the chart you want
626 *
627 * @return string HTML table
628 */
629 public function build_table( $post_id ) {
630 $post = get_post( $post_id );
631 $post_meta = $this->get_post_meta( $post_id );
632 $library = m_chart()->get_post_meta( $post->ID, 'library' );
633
634 $table = '';
635
636 $multiple = count( $post_meta['data']['sets'] ) > 1 ? true : false;
637
638 foreach ( $post_meta['data']['sets'] as $set => $data ) {
639 $classes = $this->slug . '-table ' . $this->slug . '-table-' . $post_id . '-' . $set;
640
641 m_chart()->parse()->parse_data( $data, $post_meta['parse_in'] );
642
643 $template = __DIR__ . '/templates/table.php';
644
645 ob_start();
646 require apply_filters( 'm_chart_table_template', $template, $library, $post->ID );
647 $table .= ob_get_clean();
648 }
649
650 return $table;
651 }
652
653 /**
654 * Return an array of image values for a chart
655 *
656 * @param int $post_id WP post ID of the chart you want an image for
657 *
658 * @return array an array of image values url, width, height, etc...
659 */
660 public function get_chart_image( $post_id ) {
661 $settings = $this->get_settings();
662
663 // If we aren't generating images we'll return false
664 if ( 'default' != $settings['performance'] ) {
665 return false;
666 }
667
668 if ( ! $thumbnail_id = get_post_meta( $post_id, '_thumbnail_id', true ) ) {
669 return false;
670 }
671
672 if ( ! $thumbnail = wp_get_attachment_image_src( $thumbnail_id, 'full' ) ) {
673 return false;
674 }
675
676 return array(
677 'url' => $thumbnail[0],
678 'file' => basename( $thumbnail[0] ),
679 'width' => $thumbnail[1],
680 'height' => $thumbnail[2],
681 'name' => get_the_title( $thumbnail_id ),
682 );
683 }
684
685 /**
686 * Filter the m_chart_get_chart_image_tag hook and return a plaecholder if appropriate
687 *
688 * @param array|bool an array of image values or false if no image could be found
689 * @param int $post_id WP post ID of the chart you want an image for
690 *
691 * @return array an array of image values url, width, height, etc...
692 */
693 public function m_chart_get_chart_image_tag( $img, $post_id ) {
694 if ( $img ) {
695 return $img;
696 }
697
698 $url = $this->plugin_url . '/components/images/chart-placeholder.png';
699
700 return array(
701 'url' => $url,
702 'file' => basename( $url ),
703 'width' => 640,
704 'height' => 480,
705 'name' => get_the_title( $post_id ),
706 );
707 }
708
709 /**
710 * Filter the the_content hook and return chart code if this is a chart
711 *
712 * @param string $content content from the current post
713 *
714 * @return string original content or chart code
715 */
716 public function the_content( $content ) {
717 // Make sure we're dealing with a chart
718 if ( get_post_type() != $this->slug ) {
719 // We aren't dealing with a chart so we'll just stop here
720 return $content;
721 }
722
723 // Call the get_chart method and let it do it's thing
724 return $this->get_chart();
725 }
726
727 /**
728 * Hook to the m_chart_image_support filter and indicate that Chart.js supports images
729 *
730 * @param string $supports_images yes/no whether the library supports image generation
731 * @param string $library the library in question
732 *
733 * @return string yes/no whether the library supports images
734 */
735 public function m_chart_image_support( $supports_images, $library ) {
736 if ( $library != $this->chart_meta_fields['library'] ) {
737 return $supports_images;
738 }
739
740 return 'yes';
741 }
742
743 /**
744 * Hook to the m_chart_instant_preview_support filter and indicate that Chart.js supports instant previews
745 *
746 * @param string $supports_images yes/no whether the library supports instant previews
747 * @param string $library the library in question
748 *
749 * @return string yes/no whether the library supports instant previews
750 */
751 public function m_chart_instant_preview_support( $supports_instant_preview, $library ) {
752 if ( $library != $this->chart_meta_fields['library'] ) {
753 return $supports_instant_preview;
754 }
755
756 return 'yes';
757 }
758
759 /**
760 * Hook to the m_chart_library_class filter and return the library class if appropriate
761 *
762 * @param string $library_class the library class
763 * @param string $library the library in question
764 *
765 * @return class the library class for this library
766 */
767 public function m_chart_library_class( $library_class, $library ) {
768 // If Chart.js wasn't requested we'll stop here ()
769 if ( $library != $this->chart_meta_fields['library'] ) {
770 return $library_class;
771 }
772
773 if ( ! $this->library_class instanceof M_Chart_Chartjs ) {
774 require_once __DIR__ . '/class-m-chart-chartjs.php';
775 $this->library_class = new M_Chart_Chartjs();
776 }
777
778 return $this->library_class;
779 }
780
781 /**
782 * Return an iframe for a given chart
783 *
784 * @param int $post_id WP post ID of the chart you want
785 * @param array $args an array of args
786 *
787 * @return string HTML needed to display a chart via an iframe
788 */
789 public function get_chart_iframe( $post_id, $args = array() ) {
790 $post_meta = $this->get_post_meta( $post_id );
791
792 $src_url = add_query_arg( $args, get_permalink( $post_id ) . 'embed/' );
793
794 ob_start();
795 ?>
796 <iframe id="m-chart-container-<?php echo absint( $post_id ); ?>-<?php echo absint( $this->instance ); ?>"
797 class="m-chart-iframe" width="100%" height="<?php echo absint( $post_meta['height'] + 1 ); ?>"
798 src="<?php echo esc_url_raw( $src_url ); ?>" frameborder="0"></iframe>
799 <?php
800 if ( 'show' == $args['share'] ) {
801 unset( $args['share'] );
802 require apply_filters( 'm_chart_share_template', __DIR__ . '/templates/share.php' );
803 }
804 $this->instance++;
805 return ob_get_clean();
806 }
807
808 /**
809 * Return a chart via the [chart id="x"] short code
810 *
811 * @param array $args an array of arguments passed by the WP short code API
812 *
813 * @return string the chart requested in Javascript or HTML form
814 */
815 public function chart_shortcode( $args ) {
816 $default_args = $this->get_chart_default_args;
817 $default_args['id'] = '';
818
819 $args = shortcode_atts( $default_args, $args );
820
821 $post_id = $args['id'];
822 unset( $args['id'] );
823
824 // Did we get a chart ID?
825 if ( ! is_numeric( $post_id ) ) {
826 return;
827 }
828
829 // Make sure the chart actually exists and that it's a chart so we don't fatal later
830 if ( $this->slug != get_post_type( $post_id ) ) {
831 return;
832 }
833
834 // Is it published?
835 if ( 'publish' != get_post_status( $post_id ) ) {
836 return;
837 }
838
839 return $this->get_chart( $post_id, $args );
840 }
841
842 /**
843 * Helper function that prevents issues with stripslashes and unicode characters
844 * stripslashes will strip off the slash before unicode characters which is sucky, this prevents that
845 *
846 * @param string $string a string that may have unicode characters as well as unecessary escaping
847 *
848 * @return string a string with any unecessary escaping removed
849 */
850 public function unicode_aware_stripslashes( $string ) {
851 return stripslashes( preg_replace( '#\\\u[a-fA-F0-9]{4}#', '\\\\$0', $string ) );
852 }
853
854 /**
855 * Helper function that returns the chart unit terms
856 *
857 * @return array an array of generated and/or compiled unit terms
858 */
859 public function get_unit_terms() {
860 $terms = get_terms( $this->slug . '-units', array( 'hide_empty' => false ) );
861
862 if ( empty( $terms ) ) {
863 $terms = $this->generate_unit_terms();
864 }
865
866 return $this->compile_unit_terms( $terms );
867 }
868
869 /**
870 * Helper function that returns the chart unit terms
871 *
872 * @param array an array of unit terms
873 *
874 * @return array an array of compiled unit terms
875 */
876 public function compile_unit_terms( $terms ) {
877 $compiled_terms = array();
878 $parents = array();
879
880 foreach ( $terms as $unit ) {
881 if ( 0 == $unit->parent ) {
882 $parents[ $unit->term_id ] = $unit->name;
883 }
884 }
885
886 foreach ( $terms as $unit ) {
887 if ( 0 != $unit->parent && isset( $parents[ $unit->parent ] ) ) {
888 $compiled_terms[ $parents[ $unit->parent ] ][] = $unit;
889 }
890 }
891
892 ksort( $compiled_terms );
893
894 return $compiled_terms;
895 }
896
897 /**
898 * Helper function that populates the unit terms taxonomy with a default set of terms
899 *
900 * @return array an array of the newly generated unit terms
901 */
902 public function generate_unit_terms() {
903 // Load the default terms array
904 $default_terms = require __DIR__ . '/array-default-unit-terms.php';
905
906 $terms = array();
907
908 foreach ( $default_terms as $parent_term => $child_terms ) {
909 $parent = (object) wp_insert_term( $parent_term, $this->slug . '-units' );
910 $terms[] = get_term( $parent->term_id, $this->slug . '-units' );
911
912 foreach ( $child_terms as $child_term ) {
913 $term = (object) wp_insert_term( $child_term, $this->slug . '-units', array( 'parent' => $parent->term_id ) );
914 $terms[] = get_term( $term->term_id, $this->slug . '-units' );
915 }
916 }
917
918 return $terms;
919 }
920
921 /**
922 * Looks for the embed endpoint and serves up the requested chart if appropriate
923 */
924 public function template_redirect() {
925 global $wp_query;
926
927 // Make sure this is a chart with the embed endpoint in the URL
928 if ( ! isset( $wp_query->query['post_type'] ) || ! isset( $wp_query->query['embed'] ) || 'm-chart' != $wp_query->query['post_type'] ) {
929 return;
930 }
931
932 $post = get_post();
933
934 if ( ! $post ) {
935 wp_die(
936 esc_html__( 'The chart could not be found', 'm-chart' ),
937 esc_html__( 'Chart not found', 'm-chart' ),
938 array( 'response' => 404 )
939 );
940 }
941
942 $settings = $this->get_settings();
943
944 if ( 'enabled' != $settings['embeds'] ) {
945 wp_die(
946 esc_html__( 'Embeds of this type are not enabled', 'm-chart' ),
947 esc_html__( 'Embeds disabled', 'm-chart' ),
948 array( 'response' => 403 )
949 );
950 exit;
951 }
952
953 $this->is_iframe = true;
954
955 $scripts = array(
956 'jquery',
957 $this->get_post_meta( $post->ID, 'library' ),
958 );
959
960 unset( $_GET['action'], $_GET['share'] );
961
962 // This prevents issues when embedding with outside sites
963 header_remove( 'X-Frame-Options' );
964
965 status_header( 200 );
966
967 require __DIR__ . '/templates/iframe.php';
968 exit;
969 }
970
971 /**
972 * Hook to the m_chart_update_post_meta action and call the required library specific function
973 *
974 * @param int $post_id WP post ID of the post you want chart args for
975 * @param array $parsed_meta the parsed chart meta passed by the action hook
976 */
977 public function m_chart_update_post_meta( $post_id, $parsed_meta ) {
978 $this->library( $parsed_meta['library'] )->m_chart_update_post_meta( $post_id, $parsed_meta );
979 }
980
981 /**
982 * If the passed library is valid return TRUE otherwise FALSE
983 *
984 * @param string library string (i.e. 'highcharts')
985 *
986 * @return bool
987 */
988 public function is_valid_library( $library ) {
989 $libraries = $this->get_libraries();
990
991 if ( ! isset( $libraries[ $library ] ) ) {
992 return false;
993 }
994
995 return true;
996 }
997
998 /**
999 * If current page is an AMP page returns true
1000 *
1001 * @return bool
1002 */
1003 public function is_amp_endpoint() {
1004 if ( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() ) {
1005 return true;
1006 }
1007
1008 return false;
1009 }
1010
1011 /**
1012 * Return the M Chart settings
1013 *
1014 * @return array current settings
1015 */
1016 public function get_settings( $setting = false ) {
1017 // Allow third party libraries to modify the default settings
1018 $default_settings = apply_filters( 'm_chart_default_settings', $this->settings );
1019
1020 $settings = (array) get_option( $this->slug, $default_settings );
1021 $settings = wp_parse_args( $settings, $default_settings );
1022
1023 // Make sure the lang_settings aren't missing anything we'll be expecting later on
1024 $settings['lang_settings'] = wp_parse_args( $settings['lang_settings'], $this->settings['lang_settings'] );
1025
1026 // Make sure the set library is still valid
1027 if ( ! $this->is_valid_library( $settings['library'] ) ) {
1028 $settings['library'] = 'chartjs';
1029 }
1030
1031 $settings = apply_filters( 'm_chart_get_settings', $settings );
1032
1033 if ( $setting && isset( $settings[ $setting ] ) ) {
1034 return $settings[ $setting ];
1035 } elseif ( $setting ) {
1036 return null;
1037 }
1038
1039 return $settings;
1040 }
1041
1042 /**
1043 * Return an array of available charting libraries
1044 *
1045 * @return array current settings
1046 */
1047 public function get_libraries() {
1048 return apply_filters( 'm_chart_get_libraries', $this->libraries );
1049 }
1050
1051 /**
1052 * Return the current library
1053 *
1054 * @return string current library
1055 */
1056 public function get_library() {
1057 if ( isset( $_GET['library'] ) && $this->is_valid_library( $_GET['library'] ) ) {
1058 return $_GET['library'];
1059 }
1060
1061 return $this->get_settings( 'library' );
1062 }
1063
1064 /**
1065 * Return the locale array
1066 *
1067 * @return array locales as used by Intl.NumberFormat
1068 */
1069 public function get_locales() {
1070 return require __DIR__ . '/array-locale-codes.php';
1071 }
1072
1073 /**
1074 * Return a recursively merged array out of the two given
1075 *
1076 * @param array a multi dimensional array that will be merged into
1077 * @param array a multi dimensional array that will be merged recursively into the first one
1078 *
1079 * @return array the merged array
1080 */
1081 public function array_merge_recursive( &$a, $b ) {
1082 foreach ( $b as $child => $value ) {
1083 if ( isset( $a[ $child ] ) ) {
1084 // New value exists so we'll need to move a level down
1085 if ( is_array( $a[ $child ] ) && is_array( $value ) ) {
1086 $this->array_merge_recursive( $a[ $child ], $value );
1087 } else {
1088 // New value is not an array so we override the old value with the new one
1089 $a[ $child ] = $value;
1090 }
1091 } else {
1092 // New value doesn't exist so we can just add it
1093 $a[ $child ] = $value;
1094 }
1095 }
1096
1097 return $a;
1098 }
1099
1100 /**
1101 * Do things needed for version 1.7
1102 */
1103 public function upgrade_to_1_7() {
1104 // Get all charts
1105 $charts = get_posts(
1106 array(
1107 'post_type' => m_chart()->slug,
1108 'posts_per_page' => -1,
1109 'post_status' => 'any',
1110 'tax_query' => array(
1111 array(
1112 'taxonomy' => 'post_tag',
1113 'field' => 'slug',
1114 'terms' => 'highcharts',
1115 'operator' => 'NOT IN',
1116 ),
1117 ),
1118 )
1119 );
1120
1121 // Add highcharts post_tag to all charts
1122 foreach ( $charts as $chart ) {
1123 wp_set_object_terms( $chart->ID, 'highcharts', 'post_tag' );
1124 }
1125
1126 // Update version
1127 update_site_option( 'm_chart_version', '1.7' );
1128 }
1129
1130 /**
1131 * Do things needed for version 1.7.4
1132 */
1133 public function upgrade_to_1_7_4() {
1134 // Get all charts tagged with highcharts
1135 $highcharts_charts = get_posts(
1136 array(
1137 'post_type' => m_chart()->slug,
1138 'posts_per_page' => -1,
1139 'post_status' => 'any',
1140 'tax_query' => array(
1141 array(
1142 'taxonomy' => 'post_tag',
1143 'field' => 'slug',
1144 'terms' => 'highcharts',
1145 'operator' => 'IN',
1146 ),
1147 ),
1148 )
1149 );
1150
1151 foreach ( $highcharts_charts as $chart ) {
1152 // Add highcharts as a term to the m-chart-library taxonomy
1153 wp_set_object_terms( $chart->ID, 'highcharts', m_chart()->slug . '-library' );
1154 // Remove highcharts as a term from the post_tag taxonomy
1155 wp_remove_object_terms( $chart->ID, 'highcharts', 'post_tag' );
1156 }
1157
1158 // Get all charts tagged with chartjs
1159 $chartjs_charts = get_posts(
1160 array(
1161 'post_type' => m_chart()->slug,
1162 'posts_per_page' => -1,
1163 'post_status' => 'any',
1164 'tax_query' => array(
1165 array(
1166 'taxonomy' => 'post_tag',
1167 'field' => 'slug',
1168 'terms' => 'chartjs',
1169 'operator' => 'IN',
1170 ),
1171 ),
1172 )
1173 );
1174
1175 foreach ( $chartjs_charts as $chart ) {
1176 // Add chartjs as a term to the m-chart-library taxonomy
1177 wp_set_object_terms( $chart->ID, 'chartjs', m_chart()->slug . '-library' );
1178 // Remove chartjs as a term from the post_tag taxonomy
1179 wp_remove_object_terms( $chart->ID, 'chartjs', 'post_tag' );
1180 }
1181
1182 // Update version
1183 update_site_option( 'm_chart_version', '1.7.4' );
1184 }
1185 }
1186
1187 /**
1188 * Plugin object accessor
1189 */
1190 function m_chart() {
1191 global $m_chart;
1192
1193 if ( ! $m_chart instanceof M_Chart ) {
1194 $m_chart = new M_Chart();
1195 }
1196
1197 return $m_chart;
1198 }
1199