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

630 lines 17.9 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 there's no subtitle set we'll set an empty one
229 if ( ! isset( $post_meta['subtitle'] ) ) {
230 $post_meta['subtitle'] = '';
231 }
232
233 if ( $field && isset( $post_meta[ $field ] ) ) {
234 return $post_meta[ $field ];
235 }
236 elseif ( $field ) {
237 return null;
238 }
239
240 return $post_meta;
241 }
242
243 /**
244 * Update the post meta based and set unit terms if appropriate
245 *
246 * @param int $post_id WP post ID of the post you want to attach post meta to
247 * @param array $meta an array of the post meta you want to attach to the post
248 */
249 public function update_post_meta( $post_id, $meta ) {
250 // Make sure the meta is formatted correctly and validated
251 $parsed_meta = $this->validate_post_meta( $meta );
252
253 // Set unit terms
254 $terms = array();
255
256 if ( '' != $parsed_meta['y_units'] ) {
257 $terms[] = $parsed_meta['y_units'];
258 }
259
260 if ( '' != $parsed_meta['x_units'] ) {
261 $terms[] = $parsed_meta['x_units'];
262 }
263
264 wp_set_object_terms( $post_id, $terms, $this->slug . '-units' );
265
266 // Save meta to the post
267 update_post_meta( $post_id, $this->slug, $parsed_meta );
268 do_action( 'm_chart_update_post_meta', $post_id, $parsed_meta );
269 }
270
271 /**
272 * Parses a $meta array and returns a cleaned and validated array
273 *
274 * @param array $meta an array of the post meta you want to attach to the post
275 *
276 * @return array of cleaned/validated post meta
277 */
278 public function validate_post_meta( $meta ) {
279 // Need to set checkboxes before checking or they can't be deselected
280 $chart_meta['labels'] = false;
281 $chart_meta['y_min'] = false;
282 $chart_meta['legend'] = false;
283
284 // Filter values so we know the data is clean
285 foreach ( $this->chart_meta_fields as $field => $default ) {
286 if ( isset( $meta[ $field ] ) ) {
287 if ( 'source_url' == $field && '' != $meta[ $field ] ) {
288 $chart_meta[ $field ] = esc_url_raw( $meta[ $field ] );
289 } elseif ( 'data' == $field ) {
290 $chart_meta[ $field ] = $meta[ $field ];
291 } elseif ( in_array( $field, array( 'labels', 'y_min', 'legend' ) ) ) {
292 $chart_meta[ $field ] = (bool) $meta[ $field ];
293 } elseif ( 'height' == $field ) {
294 $chart_meta[ $field ] = absint( $meta[ $field ] );
295
296 if ( $chart_meta[ $field ] > 900 ) {
297 $chart_meta[ $field ] = 900;
298 } else if ( $chart_meta[ $field ] < 300 ) {
299 $chart_meta[ $field ] = 300;
300 }
301 } else {
302 $chart_meta[ $field ] = wp_filter_nohtml_kses( $meta[ $field ] );
303 }
304 }
305 elseif ( ! isset( $chart_meta[ $field ] ) ) {
306 // Fall back on the default value if there wasn't one in the given meta
307 $chart_meta[ $field ] = $default;
308 }
309 }
310
311 // The theme meta it isn't included in the chart_meta_fields class var so we handle it here
312 if ( isset( $meta['theme'] ) && preg_match('#^[a-zA-Z0-9-_]+$#', $meta['theme'] ) ) {
313 $chart_meta['theme'] = $meta['theme'];
314 }
315
316 // If the data value is not an array we asume it is JSON encoded (i.e. from Handsontable)
317 if ( ! is_array( $chart_meta['data'] ) && '' != $chart_meta['data'] ) {
318 $chart_meta['data'] = json_decode( stripslashes( $chart_meta['data'] ) );
319 }
320
321 // Validate the data array
322 $chart_meta['data'] = $this->validate_data( $chart_meta['data'] );
323
324 return $chart_meta;
325 }
326
327 /**
328 * Runs all of the raw data array values through wp_filter_nohtml_kses
329 *
330 * @param array $data an array of data as passed by the user
331 *
332 * @return array of validated data
333 */
334 function validate_data( $data ) {
335 if ( ! is_array( $data ) ) {
336 return wp_filter_nohtml_kses( $data );
337 }
338
339 foreach ( $data as $key => $value ) {
340 if ( is_array( $value ) ) {
341 $data[ $key ] = $this->validate_data( $value );
342 }
343 else {
344 $data[ $key ] = wp_filter_nohtml_kses( $value );
345 }
346 }
347
348 return $data;
349 }
350
351
352 /**
353 * Hook to save_post action and save chart related post meta
354 *
355 * @param int $post_id WP post ID of the post
356 */
357 public function save_post( $post_id ) {
358 // We do this in the main class because otherwise it won't get hooked soon enough
359 $this->admin()->save_post( $post_id );
360 }
361
362 /**
363 * Returns a chart
364 *
365 * @param int $post_id WP post ID of the chart you want
366 * @param array $args an array of args
367 *
368 * @return string HTML and Javascript needed to display a chart (or if appropriate an HTML image tag)
369 */
370 public function get_chart( $post_id, $args = array() ) {
371 $args = wp_parse_args( $args, $this->get_chart_default_args );
372
373 // If they want the image version or the request is happening from a feed we return the image tag
374 if ( 'yes' == $args['img'] || is_feed() || $this->is_shortcake ) {
375 $image = $this->get_chart_image( $post_id );
376
377 // Default behavior is to return the full size image but with the width/height values halved
378 // This should result in an image that looks nice on retina or better screens
379 $image['width'] = $image['width'] / 2;
380 $image['height'] = $image['height'] / 2;
381
382 $image = apply_filters( 'm_chart_get_chart_image_tag', $image, $post_id, $args );
383
384 ob_start();
385 ?>
386 <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'] ); ?>" />
387 <?php
388 return ob_get_clean();
389 }
390
391 $library = $this->get_post_meta( $post_id, 'library' );
392
393 // If it's not a valid library we don't proceed
394 if ( ! $this->is_valid_library( $library ) ) {
395 return;
396 }
397
398 // If we haven't enqueued the right library yet lets do it
399 if ( ! wp_script_is( $library, 'enqueued' ) ) {
400 wp_enqueue_script( $library );
401 }
402
403 ob_start();
404 require __DIR__ . '/templates/' . $library . '-chart.php';
405 return ob_get_clean();
406 }
407
408 /**
409 * Return an array of image values for a chart
410 *
411 * @param int $post_id WP post ID of the chart you want an image for
412 *
413 * @return array an array of image values url, width, height, etc...
414 */
415 public function get_chart_image( $post_id ) {
416 $settings = $this->get_settings();
417
418 // If we aren't generating images we'll return false
419 if ( 'default' != $settings['performance'] ) {
420 return false;
421 }
422
423 if ( ! $thumbnail_id = get_post_meta( $post_id, '_thumbnail_id', true ) ) {
424 return false;
425 }
426
427 if ( ! $thumbnail = wp_get_attachment_image_src( $thumbnail_id, 'full' ) ) {
428 return false;
429 }
430
431 return array(
432 'url' => $thumbnail[0],
433 'file' => basename( $thumbnail[0] ),
434 'width' => $thumbnail[1],
435 'height' => $thumbnail[2],
436 'name' => get_the_title( $thumbnail_id ),
437 );
438 }
439
440 /**
441 * Filter the m_chart_get_chart_image_tag and return a plaecholder if appropriate
442 *
443 * @param array|bool an array of image values or false if no image could be found
444 * @param int $post_id WP post ID of the chart you want an image for
445 *
446 * @return array an array of image values url, width, height, etc...
447 */
448 public function m_chart_get_chart_image_tag( $img, $post_id ) {
449 if ( ! $img ) {
450 return $img;
451 }
452
453 $url = $this->plugin_url . '/components/images/chart-placeholder.png';
454
455 return array(
456 'url' => $url,
457 'file' => basename( $url ),
458 'width' => 640,
459 'height' => 480,
460 'name' => get_the_title( $post_id ),
461 );
462 }
463
464 /**
465 * Return a chart via the [chart id="x"] short code
466 *
467 * @param array $args an array of arguments passed by the WP short code API
468 *
469 * @return string the chart requested in Javascript or HTML form
470 */
471 public function chart_shortcode( $args ) {
472 $default_args = $this->get_chart_default_args;
473 $default_args['id'] = '';
474
475 $args = shortcode_atts( $default_args, $args );
476
477 $post_id = $args['id'];
478 unset( $args['id'] );
479
480 // Did we get a chart ID?
481 if ( ! is_numeric( $post_id ) ) {
482 return;
483 }
484
485 // Make sure the chart actually exists and that it's a chart so we don't fatal later
486 if ( $this->slug != get_post_type( $post_id ) ) {
487 return;
488 }
489
490 // Is it published?
491 if ( 'publish' != get_post_status( $post_id ) ) {
492 return;
493 }
494
495 return $this->get_chart( $post_id, $args );
496 }
497
498 /**
499 * Helper function that prevents issues with stripslashes and unicode characters
500 * stripslashes will strip off the slash before unicode characters which is sucky, this prevents that
501 *
502 * @param string $string a string that may have unicode characters as well as unecessary escaping
503 *
504 * @return string a string with any unecessary escaping removed
505 */
506 public function unicode_aware_stripslashes( $string ) {
507 return stripslashes( preg_replace( '#\\\u[a-fA-F0-9]{4}#', '\\\\$0', $string ) );
508 }
509
510 /**
511 * Helper function that returns the chart unit terms
512 *
513 * @return array an array of generated and/or compiled unit terms
514 */
515 public function get_unit_terms() {
516 $terms = get_terms( $this->slug . '-units', array( 'hide_empty' => false ) );
517
518 if ( empty( $terms ) ) {
519 $terms = $this->generate_unit_terms();
520 }
521
522 return $this->compile_unit_terms( $terms );
523 }
524
525 /**
526 * Helper function that returns the chart unit terms
527 *
528 * @param array an array of unit terms
529 *
530 * @return array an array of compiled unit terms
531 */
532 public function compile_unit_terms( $terms ) {
533 $compiled_terms = array();
534 $parents = array();
535
536 foreach ( $terms as $unit ) {
537 if ( 0 == $unit->parent ) {
538 $parents[ $unit->term_id ] = $unit->name;
539 }
540 }
541
542 foreach ( $terms as $unit ) {
543 if ( 0 != $unit->parent && isset( $parents[ $unit->parent ] ) ) {
544 $compiled_terms[ $parents[ $unit->parent ] ][] = $unit;
545 }
546 }
547
548 ksort( $compiled_terms );
549
550 return $compiled_terms;
551 }
552
553 /**
554 * Helper function that populates the unit terms taxonomy with a default set of terms
555 *
556 * @return array an array of the newly generated unit terms
557 */
558 public function generate_unit_terms() {
559 // Load the default terms array
560 $default_terms = require __DIR__ .'/array-default-unit-terms.php';
561
562 $terms = array();
563
564 foreach ( $default_terms as $parent_term => $child_terms ) {
565 $parent = (object) wp_insert_term( $parent_term, $this->slug . '-units' );
566 $terms[] = get_term( $parent->term_id, $this->slug . '-units' );
567
568 foreach ( $child_terms as $child_term ) {
569 $term = (object) wp_insert_term( $child_term, $this->slug . '-units', array( 'parent' => $parent->term_id ) );
570 $terms[] = get_term( $term->term_id, $this->slug . '-units' );
571 }
572 }
573
574 return $terms;
575 }
576
577 /**
578 * Detect Shortcake usage and set is_shortcake to true
579 *
580 * @param string The shortcode being rendered
581 */
582 public function shortcode_ui_before_do_shortcode( $shortcode ) {
583 if ( ! preg_match( '#\[chart*.*?\]#', $shortcode ) ) {
584 return;
585 }
586
587 $this->is_shortcake = true;
588 }
589
590 /**
591 * If the passed library is valid return TRUE otherwise FALSE
592 *
593 * @param string library string (i.e. 'highcharts')
594 *
595 * @return bool
596 */
597 public function is_valid_library( $library ) {
598 if ( ! in_array( $library, $this->valid_libraries ) ) {
599 return false;
600 }
601
602 return true;
603 }
604
605 /**
606 * Return the M Chart settings as an object
607 * *
608 * @return array current settings
609 */
610 public function get_settings() {
611 $settings = (array) get_option( $this->slug, $this->settings );
612 $settings = wp_parse_args( $settings, $this->settings );
613
614 return $settings;
615 }
616 }
617
618 /**
619 * Plugin object accessor
620 */
621 function m_chart() {
622 global $m_chart;
623
624 if ( ! $m_chart ) {
625 $m_chart = new M_Chart;
626 }
627
628 return $m_chart;
629 }
630