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

625 lines 17.8 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 $dev = true;
5 public $slug = 'm-chart';
6 public $plugin_name = 'Chart';
7 public $chart_meta_fields = array(
8 'library' => 'highcharts',
9 'type' => 'line',
10 'parse_in' => 'rows',
11 'labels' => true,
12 'subtitle' => '',
13 'y_title' => '',
14 'y_units' => '',
15 'y_min' => false,
16 'x_title' => '',
17 'x_units' => '',
18 'height' => 400,
19 'legend' => true,
20 'source' => '',
21 'source_url' => '',
22 'data' => array(),
23 );
24 public $get_chart_default_args = array(
25 'img' => 'no',
26 'width' => 'responsive',
27 );
28 public $parse_options = array(
29 'columns',
30 'rows',
31 );
32 public $options_set;
33 public $plugin_url;
34 public $is_shortcake = false;
35 public $settings = array(
36 'performance' => 'default',
37 'default_theme' => '_default',
38 );
39
40 private $admin;
41 private $highcharts;
42 private $parse;
43 private $valid_libraries = array(
44 'highcharts',
45 );
46
47 /**
48 * Constructor
49 */
50 public function __construct() {
51 $this->plugin_url = $this->plugin_url();
52
53 add_action( 'init', array( $this, 'init' ) );
54 add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ) );
55 add_action( 'save_post', array( $this, 'save_post' ) );
56 add_action( 'shortcode_ui_before_do_shortcode', array( $this, 'shortcode_ui_before_do_shortcode' ) );
57
58 // Doing this before the default so it's already done before anything else
59 add_filter( 'm_chart_get_chart_image_tag', array( $this, 'm_chart_get_chart_image_tag' ), 9, 3 );
60
61 add_shortcode( 'chart', array( $this, 'chart_shortcode' ) );
62 }
63
64 /**
65 * Admin object accessor
66 */
67 public function admin() {
68 if ( ! $this->admin ) {
69 require_once __DIR__ . '/class-m-chart-admin.php';
70 $this->admin = new M_Chart_Admin;
71 }
72
73 return $this->admin;
74 }
75
76 /**
77 * Highcharts object accessor
78 */
79 public function highcharts() {
80 if ( ! $this->highcharts ) {
81 require_once __DIR__ . '/class-m-chart-highcharts.php';
82 $this->highcharts = new M_Chart_Highcharts;
83 }
84
85 return $this->highcharts;
86 }
87
88 /**
89 * Parse object accessor
90 */
91 public function parse() {
92 if ( ! $this->parse ) {
93 require_once __DIR__ . '/class-m-chart-parse.php';
94 $this->parse = new M_Chart_Parse;
95 }
96
97 return $this->parse;
98 }
99
100 /**
101 * Do init stuff
102 */
103 public function init() {
104 // Register the units taxonomy
105 register_taxonomy(
106 $this->slug . '-units',
107 array( $this->slug ),
108 array(
109 'hierarchical' => true,
110 'labels' => array(
111 'name' => esc_html__( 'Chart Units', 'm-chart' ),
112 'singular_name' => esc_html__( 'Chart Unit', 'm-chart' ),
113 'search_items' => esc_html__( 'Search Chart Units', 'm-chart' ),
114 'all_items' => esc_html__( 'All Chart Units', 'm-chart' ),
115 'parent_item' => esc_html__( 'Parent Chart Unit', 'm-chart' ),
116 'parent_item_colon' => esc_html__( 'Parent Chart Unit:', 'm-chart' ),
117 'edit_item' => esc_html__( 'Edit Chart Unit', 'm-chart' ),
118 'update_item' => esc_html__( 'Update Chart Unit', 'm-chart' ),
119 'add_new_item' => esc_html__( 'Add New Chart Unit', 'm-chart' ),
120 'new_item_name' => esc_html__( 'Chart Unit Name', 'm-chart' ),
121 'menu_name' => esc_html__( 'Chart Units', 'm-chart' ),
122 ),
123 'show_ui' => true,
124 'query_var' => true,
125 'rewrite' => array(
126 'slug' => $this->slug . '-units',
127 ),
128 )
129 );
130
131 // Get the public taxonomies so the custom post type can use them
132 $taxonomies = get_taxonomies( array( 'public' => true ) );
133
134 // Register the charts custom post type
135 register_post_type(
136 $this->slug,
137 array(
138 'labels' => array(
139 'name' => esc_html__( 'Charts', 'm-chart' ),
140 'singular_name' => esc_html__( 'Chart', 'm-chart' ),
141 'add_new' => esc_html__( 'Add Chart', 'm-chart' ),
142 'add_new_item' => esc_html__( 'Add Chart', 'm-chart' ),
143 'edit' => esc_html__( 'Edit', 'm-chart' ),
144 'edit_item' => esc_html__( 'Edit Chart', 'm-chart' ),
145 'new_item' => esc_html__( 'New Chart', 'm-chart' ),
146 'view' => esc_html__( 'View', 'm-chart' ),
147 'view_item' => esc_html__( 'View Chart', 'm-chart' ),
148 'search_items' => esc_html__( 'Search Charts', 'm-chart' ),
149 'not_found' => esc_html__( 'No Charts found', 'm-chart' ),
150 'not_found_in_trash' => esc_html__( 'No Charts found in the Trash', 'm-chart' ),
151 ),
152 'register_meta_box_cb' => is_admin() ? array( $this->admin(), 'meta_boxes' ) : null,
153 'public' => true,
154 'hierarchical' => false,
155 'exclude_from_search' => false,
156 'menu_position' => 9,
157 'menu_icon' => 'dashicons-chart-pie',
158 'query_var' => true,
159 'can_export' => true,
160 'has_archive' => 'charts',
161 'description' => esc_html__( 'Manage data sets and display them as charts in WordPress.', $this->slug ),
162 'rewrite' => array( 'slug' => 'chart' ),
163 'supports' => array(
164 'author',
165 'title',
166 'excerpt',
167 'comments',
168 ),
169 'taxonomies' => $taxonomies,
170 )
171 );
172
173 // Register the graphing library scripts
174 wp_register_script(
175 'highcharts',
176 $this->plugin_url . '/components/external/highcharts/highcharts.js',
177 array( 'jquery' )
178 );
179 }
180
181 /**
182 * Do plugins loaded stuff
183 */
184 public function plugins_loaded() {
185 load_plugin_textdomain( 'm-chart', false, plugin_basename( dirname( __FILE__ ) ) . '/languages/' );
186 }
187
188 /**
189 * VIP's CDN was breaking Highcharts ability to handle embedded SVGs so this should circumvent that
190 * If you wanted to say, watermark your charts, SVGs suddenly become very important
191 *
192 * @param string $path option additional path to be used (e.g. components)
193 *
194 * @return string URL to the plugin directory with path if parameter was passed
195 */
196 public function plugin_url( $path = '' ) {
197 if ( is_admin() ) {
198 $url_base = parse_url( admin_url() );
199 }
200 else
201 {
202 $url_base = parse_url( home_url() );
203 }
204
205 $url_path = parse_url( plugins_url( $path, __DIR__ ) );
206
207 return $url_base['scheme'] . '://' . $url_base['host'] . preg_replace( '#/$#', '', $url_path['path'] ) . ( empty( $url_path['query'] ) ? '' : '?' . $url_path['query'] );
208 }
209
210 /**
211 * Get chart post meta
212 *
213 * @param int $post_id WP post ID of the post you want post meta from
214 * @param string $field optional field to be returend instead of all post meta
215 *
216 * @return string URL to the plugin directory with path if parameter was passed
217 */
218 public function get_post_meta( $post_id, $field = false ) {
219 $post_meta = get_post_meta( $post_id, $this->slug, true );
220 $post_meta = wp_parse_args( $post_meta, $this->chart_meta_fields );
221
222 // Theme default is based off of an option so we'll handle that here
223 if ( ! isset( $post_meta['theme'] ) ) {
224 $settings = $this->get_settings();
225 $post_meta['theme'] = $settings['default_theme'];
226 }
227
228 if ( $field && isset( $post_meta[ $field ] ) ) {
229 return $post_meta[ $field ];
230 }
231 elseif ( $field ) {
232 return null;
233 }
234
235 return $post_meta;
236 }
237
238 /**
239 * Update the post meta based and set unit terms if appropriate
240 *
241 * @param int $post_id WP post ID of the post you want to attach post meta to
242 * @param array $meta an array of the post meta you want to attach to the post
243 */
244 public function update_post_meta( $post_id, $meta ) {
245 // Make sure the meta is formatted correctly and validated
246 $parsed_meta = $this->validate_post_meta( $meta );
247
248 // Set unit terms
249 $terms = array();
250
251 if ( '' != $parsed_meta['y_units'] ) {
252 $terms[] = $parsed_meta['y_units'];
253 }
254
255 if ( '' != $parsed_meta['x_units'] ) {
256 $terms[] = $parsed_meta['x_units'];
257 }
258
259 wp_set_object_terms( $post_id, $terms, $this->slug . '-units' );
260
261 // Save meta to the post
262 update_post_meta( $post_id, $this->slug, $parsed_meta );
263 do_action( 'm_chart_update_post_meta', $post_id, $parsed_meta );
264 }
265
266 /**
267 * Parses a $meta array and returns a cleaned and validated array
268 *
269 * @param array $meta an array of the post meta you want to attach to the post
270 *
271 * @return array of cleaned/validated post meta
272 */
273 public function validate_post_meta( $meta ) {
274 // Need to set checkboxes before checking or they can't be deselected
275 $chart_meta['labels'] = false;
276 $chart_meta['y_min'] = false;
277 $chart_meta['legend'] = false;
278
279 // Filter values so we know the data is clean
280 foreach ( $this->chart_meta_fields as $field => $default ) {
281 if ( isset( $meta[ $field ] ) ) {
282 if ( 'source_url' == $field && '' != $meta[ $field ] ) {
283 $chart_meta[ $field ] = esc_url_raw( $meta[ $field ] );
284 } elseif ( 'data' == $field ) {
285 $chart_meta[ $field ] = $meta[ $field ];
286 } elseif ( in_array( $field, array( 'labels', 'y_min', 'legend' ) ) ) {
287 $chart_meta[ $field ] = (bool) $meta[ $field ];
288 } elseif ( 'height' == $field ) {
289 $chart_meta[ $field ] = absint( $meta[ $field ] );
290
291 if ( $chart_meta[ $field ] > 900 ) {
292 $chart_meta[ $field ] = 900;
293 } else if ( $chart_meta[ $field ] < 300 ) {
294 $chart_meta[ $field ] = 300;
295 }
296 } else {
297 $chart_meta[ $field ] = wp_filter_nohtml_kses( $meta[ $field ] );
298 }
299 }
300 elseif ( ! isset( $chart_meta[ $field ] ) ) {
301 // Fall back on the default value if there wasn't one in the given meta
302 $chart_meta[ $field ] = $default;
303 }
304 }
305
306 // The theme meta it isn't included in the chart_meta_fields class var so we handle it here
307 if ( isset( $meta['theme'] ) && preg_match('#^[a-zA-Z0-9-_]+$#', $meta['theme'] ) ) {
308 $chart_meta['theme'] = $meta['theme'];
309 }
310
311 // If the data value is not an array we asume it is JSON encoded (i.e. from Handsontable)
312 if ( ! is_array( $chart_meta['data'] ) && '' != $chart_meta['data'] ) {
313 $chart_meta['data'] = json_decode( stripslashes( $chart_meta['data'] ) );
314 }
315
316 // Validate the data array
317 $chart_meta['data'] = $this->validate_data( $chart_meta['data'] );
318
319 return $chart_meta;
320 }
321
322 /**
323 * Runs all of the raw data array values through wp_filter_nohtml_kses
324 *
325 * @param array $data an array of data as passed by the user
326 *
327 * @return array of validated data
328 */
329 function validate_data( $data ) {
330 if ( ! is_array( $data ) ) {
331 return wp_filter_nohtml_kses( $data );
332 }
333
334 foreach ( $data as $key => $value ) {
335 if ( is_array( $value ) ) {
336 $data[ $key ] = $this->validate_data( $value );
337 }
338 else {
339 $data[ $key ] = wp_filter_nohtml_kses( $value );
340 }
341 }
342
343 return $data;
344 }
345
346
347 /**
348 * Hook to save_post action and save chart related post meta
349 *
350 * @param int $post_id WP post ID of the post
351 */
352 public function save_post( $post_id ) {
353 // We do this in the main class because otherwise it won't get hooked soon enough
354 $this->admin()->save_post( $post_id );
355 }
356
357 /**
358 * Returns a chart
359 *
360 * @param int $post_id WP post ID of the chart you want
361 * @param array $args an array of args
362 *
363 * @return string HTML and Javascript needed to display a chart (or if appropriate an HTML image tag)
364 */
365 public function get_chart( $post_id, $args = array() ) {
366 $args = wp_parse_args( $args, $this->get_chart_default_args );
367
368 // If they want the image version or the request is happening from a feed we return the image tag
369 if ( 'yes' == $args['img'] || is_feed() || $this->is_shortcake ) {
370 $image = $this->get_chart_image( $post_id );
371
372 // Default behavior is to return the full size image but with the width/height values halved
373 // This should result in an image that looks nice on retina or better screens
374 $image['width'] = $image['width'] / 2;
375 $image['height'] = $image['height'] / 2;
376
377 $image = apply_filters( 'm_chart_get_chart_image_tag', $image, $post_id, $args );
378
379 ob_start();
380 ?>
381 <img src="<?php echo esc_url( $image['url'] ); ?>" alt="<?php echo esc_attr( $image['name'] ); ?>" width="<?php echo absint( $image['width'] ); ?>" height="<?php echo absint( $image['height'] ); ?>" />
382 <?php
383 return ob_get_clean();
384 }
385
386 $library = $this->get_post_meta( $post_id, 'library' );
387
388 // If it's not a valid library we don't proceed
389 if ( ! $this->is_valid_library( $library ) ) {
390 return;
391 }
392
393 // If we haven't enqueued the right library yet lets do it
394 if ( ! wp_script_is( $library, 'enqueued' ) ) {
395 wp_enqueue_script( $library );
396 }
397
398 ob_start();
399 require __DIR__ . '/templates/' . $library . '-chart.php';
400 return ob_get_clean();
401 }
402
403 /**
404 * Return an array of image values for a chart
405 *
406 * @param int $post_id WP post ID of the chart you want an image for
407 *
408 * @return array an array of image values url, width, height, etc...
409 */
410 public function get_chart_image( $post_id ) {
411 $settings = $this->get_settings();
412
413 // If we aren't generating images we'll return false
414 if ( 'default' != $settings['performance'] ) {
415 return false;
416 }
417
418 if ( ! $thumbnail_id = get_post_meta( $post_id, '_thumbnail_id', true ) ) {
419 return false;
420 }
421
422 if ( ! $thumbnail = wp_get_attachment_image_src( $thumbnail_id, 'full' ) ) {
423 return false;
424 }
425
426 return array(
427 'url' => $thumbnail[0],
428 'file' => basename( $thumbnail[0] ),
429 'width' => $thumbnail[1],
430 'height' => $thumbnail[2],
431 'name' => get_the_title( $thumbnail_id ),
432 );
433 }
434
435 /**
436 * Filter the m_chart_get_chart_image_tag and return a plaecholder if appropriate
437 *
438 * @param array|bool an array of image values or false if no image could be found
439 * @param int $post_id WP post ID of the chart you want an image for
440 *
441 * @return array an array of image values url, width, height, etc...
442 */
443 public function m_chart_get_chart_image_tag( $img, $post_id ) {
444 if ( ! $img ) {
445 return $img;
446 }
447
448 $url = $this->plugin_url . '/components/images/chart-placeholder.png';
449
450 return array(
451 'url' => $url,
452 'file' => basename( $url ),
453 'width' => 640,
454 'height' => 480,
455 'name' => get_the_title( $post_id ),
456 );
457 }
458
459 /**
460 * Return a chart via the [chart id="x"] short code
461 *
462 * @param array $args an array of arguments passed by the WP short code API
463 *
464 * @return string the chart requested in Javascript or HTML form
465 */
466 public function chart_shortcode( $args ) {
467 $default_args = $this->get_chart_default_args;
468 $default_args['id'] = '';
469
470 $args = shortcode_atts( $default_args, $args );
471
472 $post_id = $args['id'];
473 unset( $args['id'] );
474
475 // Did we get a chart ID?
476 if ( ! is_numeric( $post_id ) ) {
477 return;
478 }
479
480 // Make sure the chart actually exists and that it's a chart so we don't fatal later
481 if ( $this->slug != get_post_type( $post_id ) ) {
482 return;
483 }
484
485 // Is it published?
486 if ( 'publish' != get_post_status( $post_id ) ) {
487 return;
488 }
489
490 return $this->get_chart( $post_id, $args );
491 }
492
493 /**
494 * Helper function that prevents issues with stripslashes and unicode characters
495 * stripslashes will strip off the slash before unicode characters which is sucky, this prevents that
496 *
497 * @param string $string a string that may have unicode characters as well as unecessary escaping
498 *
499 * @return string a string with any unecessary escaping removed
500 */
501 public function unicode_aware_stripslashes( $string ) {
502 return stripslashes( preg_replace( '#\\\u[a-fA-F0-9]{4}#', '\\\\$0', $string ) );
503 }
504
505 /**
506 * Helper function that returns the chart unit terms
507 *
508 * @return array an array of generated and/or compiled unit terms
509 */
510 public function get_unit_terms() {
511 $terms = get_terms( $this->slug . '-units', array( 'hide_empty' => false ) );
512
513 if ( empty( $terms ) ) {
514 $terms = $this->generate_unit_terms();
515 }
516
517 return $this->compile_unit_terms( $terms );
518 }
519
520 /**
521 * Helper function that returns the chart unit terms
522 *
523 * @param array an array of unit terms
524 *
525 * @return array an array of compiled unit terms
526 */
527 public function compile_unit_terms( $terms ) {
528 $compiled_terms = array();
529 $parents = array();
530
531 foreach ( $terms as $unit ) {
532 if ( 0 == $unit->parent ) {
533 $parents[ $unit->term_id ] = $unit->name;
534 }
535 }
536
537 foreach ( $terms as $unit ) {
538 if ( 0 != $unit->parent && isset( $parents[ $unit->parent ] ) ) {
539 $compiled_terms[ $parents[ $unit->parent ] ][] = $unit;
540 }
541 }
542
543 ksort( $compiled_terms );
544
545 return $compiled_terms;
546 }
547
548 /**
549 * Helper function that populates the unit terms taxonomy with a default set of terms
550 *
551 * @return array an array of the newly generated unit terms
552 */
553 public function generate_unit_terms() {
554 // Load the default terms array
555 $default_terms = require __DIR__ .'/array-default-unit-terms.php';
556
557 $terms = array();
558
559 foreach ( $default_terms as $parent_term => $child_terms ) {
560 $parent = (object) wp_insert_term( $parent_term, $this->slug . '-units' );
561 $terms[] = get_term( $parent->term_id, $this->slug . '-units' );
562
563 foreach ( $child_terms as $child_term ) {
564 $term = (object) wp_insert_term( $child_term, $this->slug . '-units', array( 'parent' => $parent->term_id ) );
565 $terms[] = get_term( $term->term_id, $this->slug . '-units' );
566 }
567 }
568
569 return $terms;
570 }
571
572 /**
573 * Detect Shortcake usage and set is_shortcake to true
574 *
575 * @param string The shortcode being rendered
576 */
577 public function shortcode_ui_before_do_shortcode( $shortcode ) {
578 if ( ! preg_match( '#\[chart*.*?\]#', $shortcode ) ) {
579 return;
580 }
581
582 $this->is_shortcake = true;
583 }
584
585 /**
586 * If the passed library is valid return TRUE otherwise FALSE
587 *
588 * @param string library string (i.e. 'highcharts')
589 *
590 * @return bool
591 */
592 public function is_valid_library( $library ) {
593 if ( ! in_array( $library, $this->valid_libraries ) ) {
594 return false;
595 }
596
597 return true;
598 }
599
600 /**
601 * Return the M Chart settings as an object
602 * *
603 * @return array current settings
604 */
605 public function get_settings() {
606 $settings = (array) get_option( $this->slug, $this->settings );
607 $settings = wp_parse_args( $settings, $this->settings );
608
609 return $settings;
610 }
611 }
612
613 /**
614 * Plugin object accessor
615 */
616 function m_chart() {
617 global $m_chart;
618
619 if ( ! $m_chart ) {
620 $m_chart = new M_Chart;
621 }
622
623 return $m_chart;
624 }
625