| @@ -94,19 +94,8 @@ | ||
| 94 | 94 | } |
| 95 | 95 | } |
| 96 | 96 | |
| 97 | 97 | /** |
| 98 | - * Verify that the current user can edit a chart. | |
| 99 | - * | |
| 100 | - * @param int $chart_id Chart ID. | |
| 101 | - */ | |
| 102 | - private function _verify_chart_access( $chart_id ): void { | |
| 103 | - if ( ! self::can_edit_chart( $chart_id ) ) { | |
| 104 | - wp_send_json_error( array( 'message' => __( 'Unauthorized.', 'visualizer' ) ), 403 ); | |
| 105 | - } | |
| 106 | - } | |
| 107 | - | |
| 108 | - /** | |
| 109 | 98 | * Persist chart data + series from a source. |
| 110 | 99 | * |
| 111 | 100 | * @param int $chart_id Chart ID. |
| 112 | 101 | * @param Visualizer_Source $source Data source instance. |
| @@ -169,9 +158,8 @@ | ||
| 169 | 158 | $chart_id = intval( isset( $_POST['chart_id'] ) ? $_POST['chart_id'] : 0 ); |
| 170 | 159 | if ( ! $chart_id || ! get_post( $chart_id ) ) { |
| 171 | 160 | wp_send_json_error( array( 'message' => __( 'Chart not found.', 'visualizer' ) ) ); |
| 172 | 161 | } |
| 173 | - $this->_verify_chart_access( $chart_id ); | |
| 174 | 162 | wp_send_json_success( |
| 175 | 163 | array( |
| 176 | 164 | 'upload_nonce' => wp_create_nonce( 'visualizer-ai-upload-' . $chart_id ), |
| 177 | 165 | ) |
| @@ -191,9 +179,8 @@ | ||
| 191 | 179 | $chart = $chart_id ? get_post( $chart_id ) : null; |
| 192 | 180 | if ( ! $chart || $chart->post_type !== Visualizer_Plugin::CPT_VISUALIZER ) { |
| 193 | 181 | wp_send_json_error( array( 'message' => __( 'Chart not found.', 'visualizer' ) ) ); |
| 194 | 182 | } |
| 195 | - $this->_verify_chart_access( $chart_id ); | |
| 196 | 183 | |
| 197 | 184 | $series = get_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, true ); |
| 198 | 185 | $data = Visualizer_Module::get_chart_data( $chart, '', false ); |
| 199 | 186 | $code = get_post_meta( $chart_id, self::CF_D3_CODE, true ); |
| @@ -218,9 +205,9 @@ | ||
| 218 | 205 | |
| 219 | 206 | /** |
| 220 | 207 | * Determines whether a remote URL serves an XLSX file. |
| 221 | 208 | * |
| 222 | - * Uses the shared remote-fetch policy and checks ZIP magic number (PK\x03\x04). | |
| 209 | + * Uses wp_safe_remote_get() and checks ZIP magic number (PK\x03\x04). | |
| 223 | 210 | * |
| 224 | 211 | * @access private |
| 225 | 212 | * @param string $url The remote URL to probe. |
| 226 | 213 | * @return bool TRUE if the file appears to be XLSX, FALSE otherwise. |
| @@ -230,17 +217,16 @@ | ||
| 230 | 217 | if ( ! $tmpfile ) { |
| 231 | 218 | return false; |
| 232 | 219 | } |
| 233 | 220 | |
| 234 | - $response = Visualizer_Remote_Fetch::request( | |
| 221 | + $response = wp_safe_remote_get( | |
| 235 | 222 | $url, |
| 236 | 223 | array( |
| 237 | - 'timeout' => 15, | |
| 238 | - 'redirection' => 5, | |
| 239 | - 'stream' => true, | |
| 240 | - 'filename' => $tmpfile, | |
| 241 | - 'headers' => array( 'Range' => 'bytes=0-3' ), | |
| 242 | - 'limit_response_size' => 4, | |
| 224 | + 'timeout' => 15, | |
| 225 | + 'redirection' => 5, | |
| 226 | + 'stream' => true, | |
| 227 | + 'filename' => $tmpfile, | |
| 228 | + 'headers' => array( 'Range' => 'bytes=0-3' ), | |
| 243 | 229 | ) |
| 244 | 230 | ); |
| 245 | 231 | |
| 246 | 232 | if ( is_wp_error( $response ) ) { |
| @@ -271,9 +257,8 @@ | ||
| 271 | 257 | } |
| 272 | 258 | if ( ! get_post( $chart_id ) ) { |
| 273 | 259 | wp_send_json_error( array( 'message' => __( 'Chart not found.', 'visualizer' ) ) ); |
| 274 | 260 | } |
| 275 | - $this->_verify_chart_access( $chart_id ); | |
| 276 | 261 | |
| 277 | 262 | $source_type = isset( $_POST['source_type'] ) ? sanitize_key( $_POST['source_type'] ) : 'csv_string'; |
| 278 | 263 | $source = null; |
| 279 | 264 | $tmp_files = array(); |
| @@ -311,8 +296,19 @@ | ||
| 311 | 296 | wp_send_json_error( array( 'message' => __( 'No URL provided.', 'visualizer' ) ) ); |
| 312 | 297 | } |
| 313 | 298 | $url = wp_unslash( $_POST['file_url'] ); |
| 314 | 299 | |
| 300 | + // Allow local absolute paths in dev (same CSVs used by Classic). | |
| 301 | + if ( is_string( $url ) && file_exists( $url ) && is_readable( $url ) ) { | |
| 302 | + $ext = strtolower( pathinfo( $url, PATHINFO_EXTENSION ) ); | |
| 303 | + if ( 'xlsx' === $ext && class_exists( 'Visualizer_Source_Xlsx' ) ) { | |
| 304 | + $source = new Visualizer_Source_Xlsx( $url ); | |
| 305 | + } else { | |
| 306 | + $source = new Visualizer_Source_Csv( $url ); | |
| 307 | + } | |
| 308 | + break; | |
| 309 | + } | |
| 310 | + | |
| 315 | 311 | if ( function_exists( 'wp_http_validate_url' ) ) { |
| 316 | 312 | $validated_url = wp_http_validate_url( (string) $url ); |
| 317 | 313 | $url = false === $validated_url ? false : (string) $validated_url; |
| 318 | 314 | } else { |
| @@ -368,11 +364,8 @@ | ||
| 368 | 364 | break; |
| 369 | 365 | |
| 370 | 366 | // ── Database query ──────────────────────────────────────────────── |
| 371 | 367 | case 'db_query': |
| 372 | - if ( ! current_user_can( 'manage_options' ) || ! is_super_admin() || ! Visualizer_Module::is_pro() ) { | |
| 373 | - wp_send_json_error( array( 'message' => __( 'Action not allowed for this user.', 'visualizer' ) ), 403 ); | |
| 374 | - } | |
| 375 | 368 | if ( empty( $_POST['db_query'] ) ) { |
| 376 | 369 | wp_send_json_error( array( 'message' => __( 'No query provided.', 'visualizer' ) ) ); |
| 377 | 370 | } |
| 378 | 371 | $query = wp_unslash( $_POST['db_query'] ); |
| @@ -438,9 +431,8 @@ | ||
| 438 | 431 | $chart_id = intval( isset( $_POST['chart_id'] ) ? $_POST['chart_id'] : 0 ); |
| 439 | 432 | if ( ! $chart_id || ! get_post( $chart_id ) ) { |
| 440 | 433 | wp_send_json_error( array( 'message' => __( 'Chart not found.', 'visualizer' ) ) ); |
| 441 | 434 | } |
| 442 | - $this->_verify_chart_access( $chart_id ); | |
| 443 | 435 | |
| 444 | 436 | $prompt = isset( $_POST['prompt'] ) ? sanitize_textarea_field( wp_unslash( $_POST['prompt'] ) ) : ''; |
| 445 | 437 | $series = isset( $_POST['series'] ) ? wp_unslash( $_POST['series'] ) : ''; |
| 446 | 438 | $data = isset( $_POST['data'] ) ? wp_unslash( $_POST['data'] ) : ''; |
| @@ -501,12 +493,8 @@ | ||
| 501 | 493 | $workflow_id = $response_body['data']['workflowId']; |
| 502 | 494 | } |
| 503 | 495 | } |
| 504 | 496 | |
| 505 | - if ( ! empty( $workflow_id ) ) { | |
| 506 | - set_transient( 'viz_ai_wf_' . $workflow_id, get_current_user_id(), 6 * HOUR_IN_SECONDS ); | |
| 507 | - } | |
| 508 | - | |
| 509 | 497 | wp_send_json_success( |
| 510 | 498 | array( |
| 511 | 499 | 'workflow_id' => $workflow_id, |
| 512 | 500 | ) |
| @@ -527,12 +515,8 @@ | ||
| 527 | 515 | if ( empty( $workflow_id ) ) { |
| 528 | 516 | wp_send_json_error( array( 'message' => __( 'Missing workflow ID.', 'visualizer' ) ) ); |
| 529 | 517 | } |
| 530 | 518 | |
| 531 | - if ( (int) get_transient( 'viz_ai_wf_' . $workflow_id ) !== get_current_user_id() ) { | |
| 532 | - wp_send_json_error( array( 'message' => __( 'Unauthorized.', 'visualizer' ) ), 403 ); | |
| 533 | - } | |
| 534 | - | |
| 535 | 519 | $agents_url = VISUALIZER_AGENTS_URL; |
| 536 | 520 | $workflow_slug = $this->_get_workflow_slug(); |
| 537 | 521 | $headers = $this->_get_agents_headers(); |
| 538 | 522 | |
| @@ -572,9 +556,8 @@ | ||
| 572 | 556 | |
| 573 | 557 | if ( ! $chart_id || ! get_post( $chart_id ) ) { |
| 574 | 558 | wp_send_json_error( array( 'message' => __( 'Chart not found.', 'visualizer' ) ) ); |
| 575 | 559 | } |
| 576 | - $this->_verify_chart_access( $chart_id ); | |
| 577 | 560 | if ( empty( $code ) ) { |
| 578 | 561 | wp_send_json_error( array( 'message' => __( 'No chart code found. Generate a chart first.', 'visualizer' ) ) ); |
| 579 | 562 | } |
| 580 | 563 | |