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

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