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-admin.php

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

644 lines 18.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_Admin {
4 private $safe_settings = array(
5 'performance' => array(
6 'default',
7 'no-images',
8 'no-preview',
9 ),
10 );
11
12 /**
13 * Constructor
14 */
15 public function __construct() {
16 $this->plugin_url = m_chart()->plugin_url();
17
18 add_action( 'admin_init', array( $this, 'admin_init' ) );
19 add_action( 'admin_menu', array( $this, 'admin_menu' ) );
20 add_action( 'current_screen', array( $this, 'current_screen' ) );
21 add_action( 'admin_footer', array( $this, 'admin_footer' ) );
22 add_action( 'wp_ajax_m_chart_export_csv', array( $this, 'ajax_export_csv' ) );
23 add_action( 'wp_ajax_m_chart_get_chart_args', array( $this, 'ajax_get_chart_args' ) );
24 add_action( 'wp_ajax_m_chart_import_csv', array( $this, 'ajax_import_csv' ) );
25 }
26
27 /**
28 * Register a Shortcake ui if we can and look for save settings submissions
29 */
30 public function admin_init() {
31 $this->save_settings();
32
33 if ( ! function_exists( 'shortcode_ui_register_for_shortcode' ) ) {
34 return;
35 }
36
37 shortcode_ui_register_for_shortcode(
38 'chart',
39 array(
40 'label' => esc_html__( 'Charts', 'm-chart' ),
41 'listItemImage' => 'dashicons-chart-pie',
42 'attrs' => array(
43 array(
44 'label' => esc_html__( 'Chart', 'm-chart' ),
45 'attr' => 'id',
46 'type' => 'post_select',
47 'query' => array(
48 'post_type' => m_chart()->slug,
49 'post_status' => 'publish',
50 ),
51 ),
52 ),
53 )
54 );
55 }
56
57 /**
58 * Add settings admin page
59 */
60 public function admin_menu() {
61 add_submenu_page(
62 'edit.php?post_type=' . m_chart()->slug,
63 esc_html__( 'M Chart Settings', 'm-chart' ),
64 esc_html__( 'Settings', 'm-chart' ),
65 'edit_posts',
66 m_chart()->slug . '-settings',
67 array( $this, 'm_chart_settings' )
68 );
69 }
70
71 /**
72 * Display the M Chart settings admin page
73 */
74 public function m_chart_settings() {
75 $settings = m_chart()->get_settings();
76 require_once __DIR__ . '/templates/m-chart-settings.php';
77 }
78
79 /**
80 * Check for and save M Chart settings
81 */
82 public function save_settings() {
83 // Check the nonce
84 if (
85 ! isset( $_POST[ m_chart()->slug ] )
86 || ! wp_verify_nonce( $_POST[ m_chart()->slug ]['nonce'], m_chart()->slug . '-save-settings' )
87 ) {
88 return;
89 }
90
91 $validated_settings = array();
92 $submitted_settings = $_POST[ m_chart()->slug ];
93
94 foreach ( m_chart()->settings as $setting => $default ) {
95 if ( ! isset( $submitted_settings[ $setting ] ) ) {
96 $validated_settings[ $setting ] = $default;
97 continue;
98 }
99
100 if ( isset( $safe_settings[ $setting ] ) ) {
101 // If we've got an array of valid values lets check against that
102 $safe_setting = $safe_settings[ $setting ];
103
104 if ( in_array( $submitted_settings[ $setting ], $safe_setting, true ) ) {
105 $validated_settings[ $setting ] = $submitted_settings[ $setting ];
106 } else {
107 $validated_settings[ $setting ] = $default;
108 }
109 } else {
110 // Make sure the value is safe before attempting to save it
111 if ( preg_match('#^[a-zA-Z0-9-_]+$#', $submitted_settings[ $setting ] ) ) {
112 $validated_settings[ $setting ] = $submitted_settings[ $setting ];
113 } else {
114 $validated_settings[ $setting ] = $default;
115 }
116 }
117 }
118
119 update_option( m_chart()->slug, $validated_settings );
120
121 add_action( 'admin_notices', array( $this, 'save_success' ) );
122 }
123
124 /**
125 * Display an admin notice that the settings have been saved
126 */
127 public function save_success() {
128 ?>
129 <div class="updated notice">
130 <p><?php esc_html_e( 'Settings saved', 'm-chart' ); ?></p>
131 </div>
132 <?php
133 }
134
135 /**
136 * Load CSS/Javascript necessary for the interface
137 *
138 * @param object the current screen object as passed by the current_screen action hook
139 */
140 public function current_screen( $screen ) {
141 if ( m_chart()->slug != $screen->post_type ) {
142 return;
143 }
144
145 // Only load these if we are on a post page
146 if ( 'post' == $screen->base ) {
147 // Handsontable
148 wp_enqueue_style(
149 'handsontable',
150 $this->plugin_url . '/components/external/handsontable/handsontable.css'
151 );
152
153 wp_enqueue_script(
154 'handsontable',
155 $this->plugin_url . '/components/external/handsontable/handsontable.js',
156 array( 'jquery' )
157 );
158
159 // Highcharts export.js is required for the image generation
160 wp_enqueue_script(
161 'highcharts-exporting',
162 $this->plugin_url . '/components/external/highcharts/exporting.js',
163 array( 'highcharts', 'jquery' )
164 );
165
166 // canvg and rgbcolo do the SVG -> Canvas conversion
167 wp_enqueue_script(
168 'rgbcolor',
169 $this->plugin_url . '/components/external/canvg/rgbcolor.js'
170 );
171
172 wp_enqueue_script(
173 'canvg',
174 $this->plugin_url . '/components/external/canvg/canvg.js',
175 array( 'rgbcolor' )
176 );
177
178 // Admin panel JS
179 wp_enqueue_script(
180 'm-chart-admin',
181 $this->plugin_url . '/components/js/m-chart-admin.js',
182 array( 'highcharts', 'jquery' )
183 );
184
185 $settings = m_chart()->get_settings();
186
187 wp_localize_script(
188 'm-chart-admin',
189 'm_chart_admin',
190 array(
191 'refresh_counter' => 0,
192 'allow_form_submission' => false,
193 'request' => false,
194 'performance' => $settings['performance'],
195 )
196 );
197 }
198
199 // Admin panel CSS
200 wp_enqueue_style(
201 'm-chart-admin',
202 $this->plugin_url . '/components/css/m-chart-admin.css'
203 );
204 }
205
206 /**
207 * Add all of the metaboxes needed for the data and chart editing interface
208 */
209 public function meta_boxes() {
210 global $wp_meta_boxes;
211
212 // Remove excerpt from it's normal spot in the meta_boxes array so we can put it back in after the spreadsheet
213 // Users can move metaboxes, but this helps put things in a reasonable place on the first visit
214 $excerpt = $wp_meta_boxes[ m_chart()->slug ]['normal']['core']['postexcerpt'];
215 unset( $wp_meta_boxes[ m_chart()->slug ]['normal']['core']['postexcerpt'] );
216
217 add_meta_box(
218 m_chart()->slug . '-spreadsheet',
219 esc_html__( 'Data', 'm-chart' ),
220 array( $this, 'spreadsheet_meta_box' ),
221 m_chart()->slug,
222 'normal',
223 'high'
224 );
225
226 add_meta_box(
227 m_chart()->slug,
228 esc_html__( 'Chart', 'm-chart' ),
229 array( $this, 'chart_meta_box' ),
230 m_chart()->slug,
231 'normal',
232 'high'
233 );
234
235 $wp_meta_boxes[ m_chart()->slug ]['normal']['high']['postexcerpt'] = $excerpt;
236
237 add_meta_box(
238 m_chart()->slug . '-csv',
239 esc_html__( 'CSV Import/Export', 'm-chart' ),
240 array( $this, 'csv_meta_box' ),
241 m_chart()->slug,
242 'normal',
243 'high'
244 );
245
246 // We are using our own interface for the units so we can remove the units taxonomy metabox
247 remove_meta_box( m_chart()->slug . '-unitsdiv', m_chart()->slug, 'side' );
248 }
249
250 /**
251 * Displays the spread sheet meta box
252 *
253 * @param object the WP post object as returned by the metabox API
254 */
255 public function spreadsheet_meta_box( $post ) {
256 $post_meta = m_chart()->get_post_meta( $post->ID );
257
258 // Setup default empty sheet data if needed
259 $sheet_data = empty( $post_meta['data'] ) ? array( array( '' ) ) : $post_meta['data'];
260
261 require_once __DIR__ . '/templates/spreadsheet-meta-box.php';
262 }
263
264 /**
265 * Displays the chart meta box
266 *
267 * @param object the WP post object as returned by the metabox API
268 */
269 public function chart_meta_box( $post ) {
270 $chart = m_chart()->get_chart( $post->ID );
271 $post_meta = m_chart()->get_post_meta( $post->ID );
272 $image = m_chart()->get_chart_image( $post->ID );
273
274 require_once __DIR__ . '/templates/chart-meta-box.php';
275 }
276
277 /**
278 * Displays the CSV Import/Export controls
279 *
280 * @param object the WP post object as returned by the metabox API
281 */
282 public function csv_meta_box( $post ) {
283 require_once __DIR__ . '/templates/csv-meta-box.php';
284 }
285
286 /**
287 * Insert CSV Import and Export forms into the footer when editing charts
288 */
289 public function admin_footer() {
290 $screen = get_current_screen();
291
292 if ( 'post' != $screen->base || m_chart()->slug != $screen->post_type ) {
293 return;
294 }
295 ?>
296 <form id="<?php echo esc_attr( $this->get_field_id( 'csv-import-form' ) ); ?>" style="display: none;">
297 <input type="file" name="import_csv_file" id="<?php echo esc_attr( $this->get_field_id( 'csv-file' ) ); ?>" class="hide" />
298 </form>
299 <form action="<?php echo esc_url( admin_url( 'admin-ajax.php?action=m_chart_export_csv' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'csv-export-form' ) ); ?>" style="display: none;" method="post">
300 <input type="hidden" name="post_id" value="" id="<?php echo esc_attr( $this->get_field_id( 'csv-post-id' ) ); ?>" />
301 <input type="hidden" name="data" value="" id="<?php echo esc_attr( $this->get_field_id( 'csv-data' ) ); ?>" />
302 <input type="hidden" name="title" value="" id="<?php echo esc_attr( $this->get_field_id( 'csv-title' ) ); ?>" />
303 </form>
304 <script type="text/javascript">
305 <?php do_action( 'm_chart_admin_footer_javascript' ); ?>
306 </script>
307 <?php
308 }
309
310 /**
311 * Hook to save_post action and save chart related post meta
312 *
313 * @param int the WP post ID of the post being saved
314 */
315 public function save_post( $post_id ) {
316 $post = get_post( $post_id );
317
318 // Check that this isn't an autosave
319 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
320 return;
321 }
322
323 // Check post type
324 if ( ! isset( $post->post_type ) || m_chart()->slug != $post->post_type ) {
325 return;
326 }
327
328 // Don't run on post revisions (almost always happens just before the real post is saved)
329 if ( wp_is_post_revision( $post->ID ) ) {
330 return;
331 }
332
333 // Check the nonce
334 if ( ! wp_verify_nonce( $_POST[ m_chart()->slug ]['nonce'], m_chart()->slug . '-save-post' ) ) {
335 return;
336 }
337
338 unset( $_POST[ m_chart()->slug ]['nonce'] );
339
340 // Check the permissions
341 if ( ! current_user_can( 'edit_post', $post->ID ) ) {
342 return;
343 }
344
345 // If there's an image being passed attach it to the chart post
346 $this->attach_image();
347
348 // Make sure we don't overrwrite existing settings in the case someone hits update too quickly
349 if (
350 isset( $_POST[ m_chart()->slug ]['library'] )
351 // Make sure the library value is clean and valid before trying to use it
352 && m_chart()->is_valid_library( $_POST[ m_chart()->slug ]['library'] )
353 ) {
354 // Load the library in question in case there's a filter/action we'll need
355 if ( 'highcharts' == $_POST[ m_chart()->slug ]['library'] ) {
356 m_chart()->highcharts();
357 }
358
359 // update_post_meta passes the $_POST values directly to validate_post_meta
360 // validate_post_meta returns only valid post meta values and does data validation on each item
361 m_chart()->update_post_meta( $post->ID, $_POST[ m_chart()->slug ] );
362 }
363 }
364
365 /**
366 * Attach a given image to a chart post
367 *
368 * @param int the WP post ID of the post being saved
369 * @param string a base64 encoded string of the image we want to attach
370 */
371 public function attach_image() {
372 $settings = m_chart()->get_settings();
373
374 // If the performance setting isn't turned to default we don't do this
375 if ( 'default' != $settings['performance'] ) {
376 return;
377 }
378
379 if ( ! is_numeric( $_POST['post_ID'] ) ) {
380 return;
381 }
382
383 $post_id = absint( $_POST['post_ID'] );
384
385 if ( ! current_user_can( 'edit_post', $post_id ) ) {
386 return false;
387 }
388
389 if ( ! $post = get_post( $post_id ) ) {
390 return false;
391 }
392
393 if ( '' == $_POST[ m_chart()->slug ]['img'] ) {
394 return false;
395 }
396
397 // Decode the image so we can save it
398 $decoded_img = base64_decode( str_replace( 'data:image/png;base64,', '', $_POST[ m_chart()->slug ]['img'] ) );
399
400 if ( '' == $decoded_img ) {
401 return false;
402 }
403
404 // Check for an existing attached image
405 $attachments = get_posts(
406 array(
407 'post_type' => 'attachment',
408 'posts_per_page' => 1,
409 'post_parent' => $post->ID,
410 'meta_key' => m_chart()->slug . '-image',
411 )
412 );
413
414 // If an existing image was found delete it
415 foreach ( $attachments as $attachment ) {
416 wp_delete_attachment( $attachment->ID, true );
417 }
418
419 // Upload image to WP
420 $file = wp_upload_bits( sanitize_title( $post->post_title . '-' . $post->ID ) . '.png', null, $decoded_img );
421
422 // START acting like media_sideload_image
423 preg_match( '/[^\?]+\.(jpe?g|jpe|gif|png)\b/i', $file['file'], $matches );
424
425 $file_array['name'] = basename( $matches[0] );
426 $file_array['tmp_name'] = $file['file'];
427
428 if ( is_wp_error( $file ) ) {
429 @unlink( $file_array['tmp_name'] );
430 $file_array['tmp_name'] = '';
431 }
432
433 $img_id = media_handle_sideload( $file_array, $post->ID, $post->post_title );
434
435 if ( is_wp_error( $img_id ) ) {
436 @unlink( $file_array['tmp_name'] );
437 return $img_id;
438 }
439 // STOP acting like media_sideload_image
440
441 // Set some meta on the attachment so we know it came from m-chart
442 add_post_meta( $img_id, m_chart()->slug . '-image', $post->ID );
443
444 // Set the attachment as the chart's thumbnail
445 update_post_meta( $post->ID, '_thumbnail_id', $img_id );
446 }
447
448 /**
449 * Parses an incoming CSV file and compiles it into an array
450 *
451 * @return array an array fo the data from the imported CSV file ready for use in the chart meta
452 */
453 public function ajax_import_csv() {
454 $post = get_post( absint( $_POST['post_id'] ) );
455
456 // Check post type
457 if ( ! isset( $post->post_type ) || m_chart()->slug != $post->post_type ) {
458 wp_send_json_error( esc_html__( 'Wrong post type', 'm-chart' ) );
459 }
460
461 // Check the nonce
462 if ( ! wp_verify_nonce( $_POST['nonce'], m_chart()->slug . '-save-post' ) ) {
463 wp_send_json_error( esc_html__( 'Invalid nonce', 'm-chart' ) );
464 }
465
466 // Check the permissions
467 if ( ! current_user_can( 'edit_post', $post->ID ) ) {
468 wp_send_json_error( esc_html__( 'Wrong post type', 'm-chart' ) );
469 }
470
471 // Make sure there's a CSV file
472 if ( empty( $_FILES ) || ! isset( $_FILES['import_csv_file']['name'] ) ) {
473 wp_send_json_error( esc_html__( 'No file to import', 'm-chart' ) );
474 }
475
476 // Make sure the file is a CSV file
477 $file_ext = strtolower( pathinfo( $_FILES['import_csv_file']['name'], PATHINFO_EXTENSION ) );
478
479 if ( 'csv' != $file_ext ) {
480 wp_send_json_error( esc_html__( 'Only CSV files can be imported ', 'm-chart' ) );
481 }
482
483 // Do some validation on the CSV file (mirroring what WP does for this sort of thing)
484 $csv_file = realpath( $_FILES['import_csv_file']['tmp_name'] );
485
486 if ( ! $csv_file ) {
487 wp_send_json_error( esc_html__( 'File path not found', 'm-chart' ) );
488 }
489
490 $csv_data = file_get_contents( $csv_file );
491
492 if ( '' == $csv_data ) {
493 wp_send_json_error( esc_html__( 'CSV file was empty', 'm-chart' ) );
494 }
495
496 // Get parseCSV library so we can use it to convert the CSV to a nice array
497 // Yes, PHP does this natively now but I've run into trouble with malformed CSV that parsCSV handles fine
498 require_once __DIR__ . '/external/parsecsv/parsecsv.lib.php';
499
500 $parse_csv = new parseCSV();
501
502 // The "\n" before and after is to deal with CSV files that don't have line breaks above and below the data
503 // Which then seems to confuse parseCSV occasionally
504 $parse_csv->parse( "\n" . trim( $csv_data ) . "\n" );
505
506 // This deals with Google Doc's crappy CSV exports which don't include columns at the end of a row if they are empty
507 $data_array = $this->fix_csv_data_array( $parse_csv->data );
508
509 wp_send_json_success( $data_array );
510 }
511
512 /**
513 * Helper function makes sure that the data array has matching numbers of array elements for each row
514 * CSV from some sources (Google Docs) doesn't include columns that are empty when they are at the end of a row (Why Google? WHY?)
515 *
516 * @param array an array of data as returned from the parseCSV class
517 *
518 * @param array the array of data with matching array value counts
519 */
520 public function fix_csv_data_array( $data_array ) {
521 $count = 0;
522
523 // Get largest row count
524 foreach ( $data_array as $data ) {
525 $temp_count = count( $data );
526
527 $count = ( $temp_count > $count ) ? $temp_count : $count;
528 }
529
530 // Fix arrays so value counts match
531 foreach ( $data_array as $key => $data ) {
532 $temp_count = count( $data );
533
534 if ( $temp_count < $count ) {
535 $difference = $count - $temp_count;
536
537 for ( $i = 0; $i < $difference; $i++ ) {
538 $data_array[ $key ][] = '';
539 }
540 }
541 }
542
543 return $data_array;
544 }
545
546 /**
547 * Converts data array into CSV outputs it to the browser
548 */
549 public function ajax_export_csv() {
550 // Purposely using $_REQUEST here since this method can work via a GET and POST request
551 // POST requests are used when passing the data value since it's too big to pass via GET
552 if ( ! is_numeric( $_REQUEST['post_id'] ) || ! current_user_can( 'edit_post', absint( $_REQUEST['post_id'] ) ) ) {
553 wp_die( 'Unauthorized access', 'You do not have permission to do that', array( 'response' => 401 ) );
554 }
555
556 $post = get_post( absint( $_REQUEST['post_id'] ) );
557
558 // If the user passed a data value in their request we'll use it after validation
559 if ( isset( $_POST['data'] ) && isset( $_POST['title'] ) ) {
560 $data = m_chart()->validate_data( json_decode( stripslashes( $_POST['data'] ) ) );
561 $file_name = sanitize_title( $_POST['title'] );
562 }
563 else {
564 $data = m_chart()->get_post_meta( $post->ID, 'data' );
565 $file_name = sanitize_title( get_the_title( $post->ID ) );
566 }
567
568 if ( empty( $data ) ) {
569 return;
570 }
571
572 require_once __DIR__ . '/external/parsecsv/parsecsv.lib.php';
573 $parse_csv = new parseCSV();
574
575 $parse_csv->output( $file_name . '.csv', $data );
576 die;
577 }
578
579 /**
580 * Returns JSON encoded chart args from $_POST values sent from the admin panel
581 *
582 * @return string a JSON encoded string containing all of the chart args needed to update an active chart
583 */
584 public function ajax_get_chart_args() {
585 // Check the nonce
586 if ( ! wp_verify_nonce( $_POST['nonce'], m_chart()->slug . '-save-post' ) ) {
587 wp_send_json_error( esc_html__( 'Invalid nonce', 'm-chart' ) );
588 }
589
590 // Does the post exist?
591 if ( ! $post = get_post( absint( $_POST['post_id'] ) ) ) {
592 wp_send_json_error( esc_html__( 'Invalid post', 'm-chart' ) );
593 }
594
595 // Can the user edit this post?
596 if ( ! current_user_can( 'edit_post', $post->ID ) ) {
597 wp_send_json_error( esc_html__( 'Permission error', 'm-chart' ) );
598 }
599
600 // Is this a valid library?
601 if ( ! m_chart()->is_valid_library( $_POST['library'] ) ) {
602 wp_send_json_error( esc_html__( 'Invalid library', 'm-chart' ) );
603 }
604
605 if ( 'highcharts' == $_POST['library'] ) {
606 $library = m_chart()->highcharts();
607 }
608
609 // Set these values so that get_chart_args has them already available before we call it
610 $library->args = m_chart()->get_chart_default_args;
611 $library->post = (object) array(
612 'ID' => $post->ID,
613 'post_title' => sanitize_text_field( $_POST['title'] ),
614 );
615
616 // validate_post_meta returns only valid post meta values and does data validation on each item
617 $library->post_meta = m_chart()->validate_post_meta( $_POST['post_meta'] );
618
619 wp_send_json_success( $library->get_chart_args( $library->post->ID, $library->args, true, false ) );
620 }
621
622 /**
623 * Return a name spaced field name
624 *
625 * @param string the field name we want to name space
626 *
627 * @param string a name spaced field name
628 */
629 public function get_field_name( $field_name ) {
630 return m_chart()->slug . '[' . $field_name . ']';
631 }
632
633 /**
634 * Return a name spaced field id
635 *
636 * @param string the field id we want to name space
637 *
638 * @param string a name spaced field id
639 */
640 public function get_field_id( $field_name ) {
641 return m_chart()->slug . '-' . $field_name;
642 }
643 }
644