Changeset 3645504
- Timestamp:
- 08/13/2026 01:45:23 PM (2 months ago)
- Location:
- shopflow-for-asana
- Files:
-
- 17 added
- 8 edited
-
tags/1.0.1 (added)
-
tags/1.0.1/assets (added)
-
tags/1.0.1/assets/images (added)
-
tags/1.0.1/assets/images/menu-icon.png (added)
-
tags/1.0.1/assets/js (added)
-
tags/1.0.1/assets/js/xsffa-admin.js (added)
-
tags/1.0.1/assets/js/xsffa-chart-js.js (added)
-
tags/1.0.1/assets/js/xsffa-chart.js (added)
-
tags/1.0.1/includes (added)
-
tags/1.0.1/includes/class-xsffa-asana-api.php (added)
-
tags/1.0.1/includes/class-xsffa-core.php (added)
-
tags/1.0.1/includes/class-xsffa-handler.php (added)
-
tags/1.0.1/includes/class-xsffa-orders-table.php (added)
-
tags/1.0.1/includes/class-xsffa-settings.php (added)
-
tags/1.0.1/includes/helpers.php (added)
-
tags/1.0.1/readme.txt (added)
-
tags/1.0.1/shopflow-for-asana.php (added)
-
trunk/assets/js/xsffa-admin.js (modified) (1 diff)
-
trunk/includes/class-xsffa-asana-api.php (modified) (19 diffs)
-
trunk/includes/class-xsffa-core.php (modified) (2 diffs)
-
trunk/includes/class-xsffa-orders-table.php (modified) (1 diff)
-
trunk/includes/class-xsffa-settings.php (modified) (12 diffs)
-
trunk/includes/helpers.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (3 diffs)
-
trunk/shopflow-for-asana.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
shopflow-for-asana/trunk/assets/js/xsffa-admin.js
r3253553 r3645504 1 1 jQuery(document).ready(function ($) { 2 // Refresh the Project/Assignee dropdowns when the Workspace changes, 3 // since their available options depend on which workspace is selected. 4 $('#xsffa-field-workspace_id').on('change', function () { 5 const workspaceId = $(this).val(); 6 const $project = $('#xsffa-field-project_id'); 7 const $assignee = $('#xsffa-field-assignee_id'); 8 9 if (!workspaceId || (!$project.length && !$assignee.length)) { 10 return; 11 } 12 13 $project.prop('disabled', true); 14 $assignee.prop('disabled', true); 15 16 $.ajax({ 17 url: xsffa_ajax.ajax_url, 18 type: 'POST', 19 data: { 20 action: 'xsffa_get_workspace_dependents', 21 workspace_id: workspaceId, 22 _ajax_nonce: xsffa_ajax.nonce 23 }, 24 success: function (response) { 25 if (response.success) { 26 populateSelect($project, response.data.projects); 27 populateSelect($assignee, response.data.assignees); 28 } else { 29 alert('Error: ' + response.data.message); 30 } 31 }, 32 error: function () { 33 alert('Failed to load projects/assignees for this workspace.'); 34 }, 35 complete: function () { 36 $project.prop('disabled', false); 37 $assignee.prop('disabled', false); 38 } 39 }); 40 }); 41 42 // Replaces a <select>'s options with the given { value: label } map, 43 // using .text() (not string concatenation) so labels from the API 44 // can't inject markup. 45 function populateSelect($select, options) { 46 if (!$select.length) { 47 return; 48 } 49 $select.empty(); 50 $select.append($('<option>', { value: '', selected: true, disabled: true }).text('Select an option')); 51 $.each(options, function (value, label) { 52 $select.append($('<option>', { value: value }).text(label)); 53 }); 54 } 55 2 56 // Handle Sync button click for individual orders 3 57 $(document).on('click', '.sync-order', function (e) { -
shopflow-for-asana/trunk/includes/class-xsffa-asana-api.php
r3253553 r3645504 21 21 * @var string 22 22 */ 23 public const ASANA_ORDER_META_KEY = '_xclickpw_asana_task_id'; 23 public const ASANA_ORDER_META_KEY = '_xsffa_asana_task_id'; 24 25 /** 26 * Previous (incorrectly prefixed, copy-pasted from another plugin) meta 27 * key. Kept read-only for backwards compatibility with orders synced 28 * before this was fixed; never written to new orders. 29 * 30 * @var string 31 */ 32 public const LEGACY_ASANA_ORDER_META_KEY = '_xclickpw_asana_task_id'; 24 33 25 34 /** … … 55 64 */ 56 65 private string $asana_project_id; 66 67 /** 68 * Asana workspace ID. 69 * 70 * @var string 71 */ 72 private string $asana_workspace_id; 73 74 /** 75 * Human-readable reason the most recent Asana API request failed, if any. 76 * 77 * @var string 78 */ 79 private string $last_error = ''; 80 81 /** 82 * Gets the Asana task ID an order was synced with, checking the current 83 * meta key first and falling back to the legacy key for orders synced 84 * before it was renamed. 85 * 86 * @param WC_Order $order WooCommerce Order object. 87 * @return string Asana task ID, or an empty string if never synced. 88 */ 89 public static function get_synced_task_id( $order ): string { 90 $task_id = $order->get_meta( self::ASANA_ORDER_META_KEY, true ); 91 92 if ( ! $task_id ) { 93 $task_id = $order->get_meta( self::LEGACY_ASANA_ORDER_META_KEY, true ); 94 } 95 96 return $task_id; 97 } 57 98 58 99 /** … … 68 109 */ 69 110 public function init_options(): void { 70 $this->asana_api_token = xsffa_core()->settings->options['access_token'] ?? ''; 71 $this->asana_project_id = xsffa_core()->settings->options['project_id'] ?? ''; 111 $this->asana_api_token = xsffa_core()->settings->options['access_token'] ?? ''; 112 $this->asana_project_id = xsffa_core()->settings->options['project_id'] ?? ''; 113 $this->asana_workspace_id = xsffa_core()->settings->options['workspace_id'] ?? ''; 72 114 } 73 115 … … 93 135 'status' => self::ORDER_STATUSES, 94 136 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 137 'relation' => 'AND', 95 138 array( 96 139 'key' => self::ASANA_ORDER_META_KEY, 140 'compare' => 'NOT EXISTS', 141 ), 142 array( 143 'key' => self::LEGACY_ASANA_ORDER_META_KEY, 97 144 'compare' => 'NOT EXISTS', 98 145 ), … … 135 182 'status' => self::ORDER_STATUSES, 136 183 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 184 'relation' => 'AND', 137 185 array( 138 186 'key' => self::ASANA_ORDER_META_KEY, 187 'compare' => 'NOT EXISTS', 188 ), 189 array( 190 'key' => self::LEGACY_ASANA_ORDER_META_KEY, 139 191 'compare' => 'NOT EXISTS', 140 192 ), … … 161 213 } 162 214 163 // Get orders with out Asana task ID meta.215 // Get orders with an Asana task ID meta (current or legacy key). 164 216 $orders = wc_get_orders( 165 217 array( 166 'limit' => self::MAX_ORDERS_PER_REQUEST, 167 'offset' => $offset, 168 'status' => self::ORDER_STATUSES, 169 'meta_key' => self::ASANA_ORDER_META_KEY, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 170 'meta_compare' => 'EXISTS', 218 'limit' => self::MAX_ORDERS_PER_REQUEST, 219 'offset' => $offset, 220 'status' => self::ORDER_STATUSES, 221 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 222 'relation' => 'OR', 223 array( 224 'key' => self::ASANA_ORDER_META_KEY, 225 'compare' => 'EXISTS', 226 ), 227 array( 228 'key' => self::LEGACY_ASANA_ORDER_META_KEY, 229 'compare' => 'EXISTS', 230 ), 231 ), 171 232 ) 172 233 ); … … 202 263 $remaining_orders = wc_get_orders( 203 264 array( 204 'limit' => -1, 205 'offset' => $offset, 206 'status' => self::ORDER_STATUSES, 207 'meta_key' => self::ASANA_ORDER_META_KEY, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 208 'meta_compare' => 'EXISTS', 209 'return' => 'ids', 265 'limit' => -1, 266 'offset' => $offset, 267 'status' => self::ORDER_STATUSES, 268 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 269 'relation' => 'OR', 270 array( 271 'key' => self::ASANA_ORDER_META_KEY, 272 'compare' => 'EXISTS', 273 ), 274 array( 275 'key' => self::LEGACY_ASANA_ORDER_META_KEY, 276 'compare' => 'EXISTS', 277 ), 278 ), 279 'return' => 'ids', 210 280 ) 211 281 ); … … 220 290 */ 221 291 public function sync_order( int $order_id ) { 292 $this->last_error = ''; 293 222 294 if ( ! $order_id ) { 295 $this->last_error = 'Invalid order ID.'; 223 296 return null; 224 297 } 225 298 226 299 if ( ! function_exists( 'wc_get_order' ) ) { 300 $this->last_error = 'WooCommerce is not active.'; 227 301 return null; 228 302 } … … 230 304 $order = wc_get_order( $order_id ); 231 305 if ( ! $order ) { 306 $this->last_error = 'Order not found.'; 232 307 return null; 233 308 } 234 309 235 310 // Prevent duplicate sync (check if already synced). 236 if ( $order->get_meta( self::ASANA_ORDER_META_KEY ) ) { 311 if ( self::get_synced_task_id( $order ) ) { 312 $this->last_error = 'Order is already synced to Asana.'; 237 313 return null; 238 314 } … … 248 324 } 249 325 250 return $response['data']['gid'] ;326 return $response['data']['gid'] ?? null; 251 327 } 252 328 … … 262 338 // Store Asana task ID in order meta. 263 339 $order->update_meta_data( self::ASANA_ORDER_META_KEY, $asana_task_id ); 340 // Clean up the legacy key so this order is fully migrated. 341 $order->delete_meta_data( self::LEGACY_ASANA_ORDER_META_KEY ); 264 342 265 343 // Add order note with Asana task link. … … 345 423 * Send request to Asana API. 346 424 * 347 * @param string $endpoint API endpoint (e.g., 'tasks') .425 * @param string $endpoint API endpoint (e.g., 'tasks'), or a full URL (e.g. a pagination `next_page.uri`). 348 426 * @param array $data Data to send in the request. 349 427 * @param string $method HTTP method (GET, POST, PUT, etc.). … … 351 429 */ 352 430 private function send_asana_request( string $endpoint, array $data = array(), string $method = 'GET' ) { 353 $url = "{$this->asana_api_url}/{$endpoint}";431 $url = ( 0 === strpos( $endpoint, 'http' ) ) ? $endpoint : "{$this->asana_api_url}/{$endpoint}"; 354 432 355 433 $args = array( … … 368 446 369 447 if ( is_wp_error( $response ) ) { 448 $this->last_error = $response->get_error_message(); 370 449 return false; 371 450 } 372 451 373 return json_decode( wp_remote_retrieve_body( $response ), true ); 452 $status = wp_remote_retrieve_response_code( $response ); 453 $body = json_decode( wp_remote_retrieve_body( $response ), true ); 454 455 if ( $status < 200 || $status >= 300 ) { 456 $this->last_error = $body['errors'][0]['message'] ?? "Asana API returned HTTP {$status}."; 457 return false; 458 } 459 460 $this->last_error = ''; 461 462 return $body; 463 } 464 465 /** 466 * Gets the reason the most recent Asana API request failed. 467 * 468 * @return string Human-readable error message, or an empty string if the last request succeeded. 469 */ 470 public function get_last_error(): string { 471 return $this->last_error; 472 } 473 474 /** 475 * Maximum number of pages to follow for a single paginated list request. 476 * At 100 items per page, this covers up to 5,000 results — a safety 477 * cap so a runaway `next_page` chain can't loop indefinitely. 478 */ 479 private const MAX_PAGES = 50; 480 481 /** 482 * Fetches every page of a paginated Asana "list" endpoint (workspaces, 483 * projects, users), following `next_page.uri` until exhausted. 484 * 485 * @param string $endpoint API endpoint, e.g. 'projects?workspace=123'. 486 * @return array Combined `data` items from every page, or an empty array on failure. 487 */ 488 private function fetch_all_pages( string $endpoint ): array { 489 $separator = ( false === strpos( $endpoint, '?' ) ) ? '?' : '&'; 490 $url = $endpoint . $separator . 'limit=100'; 491 492 $items = array(); 493 494 for ( $page = 0; $url && $page < self::MAX_PAGES; $page++ ) { 495 $response = $this->send_asana_request( $url ); 496 497 if ( ! $response ) { 498 break; 499 } 500 501 $items = array_merge( $items, $response['data'] ?? array() ); 502 $url = $response['next_page']['uri'] ?? null; 503 } 504 505 return $items; 506 } 507 508 /** 509 * Retrieves workspaces from Asana API. 510 * 511 * @return array List of workspaces or an empty array on failure. 512 */ 513 public function retrieve_workspaces(): array { 514 $data = $this->fetch_all_pages( 'workspaces' ); 515 516 $result = array(); 517 foreach ( $data as $workspace ) { 518 $result[ $workspace['gid'] ] = $workspace['name']; 519 } 520 521 return $result; 374 522 } 375 523 … … 377 525 * Retrieves projects from Asana API. 378 526 * 527 * @param string|null $workspace_id Workspace to look up projects for. Defaults to the saved workspace setting. 379 528 * @return array List of projects or an empty array on failure. 380 529 */ 381 public function retrieve_projects(): array { 382 $response = $this->send_asana_request( 'projects' ); 383 384 if ( ! $response ) { 530 public function retrieve_projects( ?string $workspace_id = null ): array { 531 $workspace_id = $workspace_id ?? $this->asana_workspace_id; 532 533 if ( empty( $workspace_id ) ) { 534 $this->last_error = 'Select a workspace first.'; 385 535 return array(); 386 536 } 387 537 388 $data = $ response['data'] ?? array();538 $data = $this->fetch_all_pages( 'projects?workspace=' . rawurlencode( $workspace_id ) ); 389 539 390 540 $result = array(); … … 402 552 */ 403 553 public function resync_order( int $order_id ) { 554 $this->last_error = ''; 555 404 556 if ( ! function_exists( 'wc_get_order' ) ) { 557 $this->last_error = 'WooCommerce is not active.'; 405 558 return null; 406 559 } … … 408 561 $order = wc_get_order( $order_id ); 409 562 if ( ! $order ) { 563 $this->last_error = 'Order not found.'; 410 564 return null; 411 565 } 412 566 413 567 // Get current asana_task_id. 414 $asana_task_id = $order->get_meta( self::ASANA_ORDER_META_KEY, true);568 $asana_task_id = self::get_synced_task_id( $order ); 415 569 416 570 // Update Asana task if it exists. … … 443 597 } 444 598 445 return $response['data']['gid']; 446 } 447 599 return $response['data']['gid'] ?? null; 600 } 601 602 $this->last_error = 'Order has not been synced to Asana yet; nothing to resync.'; 448 603 return null; 449 604 } … … 452 607 * Retrieves users (assignees) from Asana API. 453 608 * 609 * @param string|null $workspace_id Workspace to look up users for. Defaults to the saved workspace setting. 454 610 * @return array List of users or an empty array on failure. 455 611 */ 456 public function retrieve_assignees(): array { 457 $response = $this->send_asana_request( 'users' ); 458 459 if ( ! $response ) { 612 public function retrieve_assignees( ?string $workspace_id = null ): array { 613 $workspace_id = $workspace_id ?? $this->asana_workspace_id; 614 615 if ( empty( $workspace_id ) ) { 616 $this->last_error = 'Select a workspace first.'; 460 617 return array(); 461 618 } 462 619 463 $data = $ response['data'] ?? array();620 $data = $this->fetch_all_pages( 'users?workspace=' . rawurlencode( $workspace_id ) ); 464 621 465 622 $result = array(); -
shopflow-for-asana/trunk/includes/class-xsffa-core.php
r3253553 r3645504 82 82 $this->handler = new XSFFA_Handler(); 83 83 $this->asana_api = new XSFFA_Asana_Api(); 84 85 add_action( 'init', array( $this, 'load_textdomain' ) );86 84 } 87 85 … … 100 98 101 99 /** 102 * Loads the plugin's text domain for translations.103 *104 * @return void105 */106 public function load_textdomain() {107 load_plugin_textdomain( 'shopflow-for-asana', false, XSFFA_PLUGIN_REL_PATH . 'languages/' );108 }109 110 /**111 100 * Is the current page is plugin page? 112 101 * -
shopflow-for-asana/trunk/includes/class-xsffa-orders-table.php
r3253553 r3645504 5 5 * @package shopflow-for-asana 6 6 */ 7 8 if ( ! defined( 'ABSPATH' ) ) { 9 exit; 10 } 7 11 8 12 // WP_List_Table is not loaded automatically so we need to load it in our application. -
shopflow-for-asana/trunk/includes/class-xsffa-settings.php
r3253553 r3645504 39 39 'auto_sync' => '1', 40 40 'access_token' => '', 41 'workspace_id' => '', 41 42 'project_id' => '', 42 43 'assignee_id' => '', … … 52 53 'auto_sync' => 'bool', // Auto sync. 53 54 'access_token' => 'string', // API key. 55 'workspace_id' => 'select', // Workspace ID. 54 56 'project_id' => 'select', // Project ID. 55 57 'assignee_id' => 'select', // Assignee ID. … … 65 67 'auto_sync' => 'Enable this to automatically sync WooCommerce orders to Asana.', 66 68 'access_token' => 'Enter your Asana Personal Access Token. Create a token from the <a href="https://app.asana.com/0/developer-console" target="_blank" rel="noopener noreferrer">Asana Developer Console</a>.', 69 'workspace_id' => 'Choose the Asana Workspace to sync orders into. Required if your Asana account belongs to more than one workspace.', 67 70 'project_id' => 'Choose the Asana Project where orders should be synced.', 68 71 'assignee_id' => 'Choose the Asana User who should be assigned to the task.', … … 99 102 add_action( 'wp_ajax_xsffa_sync_single_order', array( $this, 'xsffa_sync_single_order' ) ); 100 103 add_action( 'wp_ajax_xsffa_resync_single_order', array( $this, 'xsffa_resync_single_order' ) ); 104 add_action( 'wp_ajax_xsffa_get_workspace_dependents', array( $this, 'xsffa_get_workspace_dependents' ) ); 105 } 106 107 /** 108 * Handles AJAX requests to refresh the Project/Assignee dropdowns when 109 * the selected Workspace changes, before the settings form is saved. 110 * 111 * @return void 112 */ 113 public function xsffa_get_workspace_dependents() { 114 check_ajax_referer( 'xsffa_sync_orders_nonce' ); 115 116 $workspace_id = isset( $_POST['workspace_id'] ) ? sanitize_text_field( wp_unslash( $_POST['workspace_id'] ) ) : ''; 117 118 if ( ! $workspace_id ) { 119 wp_send_json_error( array( 'message' => 'Missing workspace ID.' ) ); 120 } 121 122 wp_send_json_success( 123 array( 124 'projects' => xsffa_core()->asana_api->retrieve_projects( $workspace_id ), 125 'assignees' => xsffa_core()->asana_api->retrieve_assignees( $workspace_id ), 126 ) 127 ); 101 128 } 102 129 … … 145 172 'auto_sync' => esc_html__( 'Auto Sync', 'shopflow-for-asana' ), 146 173 'access_token' => esc_html__( 'Personal Access Token', 'shopflow-for-asana' ), 174 'workspace_id' => esc_html__( 'Workspace', 'shopflow-for-asana' ), 147 175 'project_id' => esc_html__( 'Project ID', 'shopflow-for-asana' ), 148 176 'assignee_id' => esc_html__( 'Assignee ID', 'shopflow-for-asana' ), … … 235 263 if ( isset( $this->select_options[ $key ] ) && is_array( $this->select_options[ $key ] ) ) { 236 264 printf( 237 '<select %s required name="xsffa_settings[%s]">', 265 '<select id="xsffa-field-%s" %s required name="xsffa_settings[%s]">', 266 esc_attr( $key ), 238 267 esc_attr( $disabled ), 239 268 esc_attr( $key ) … … 319 348 */ 320 349 private function disable_check_project_id(): bool { 350 return empty( $this->options['access_token'] ) || empty( $this->options['workspace_id'] ); 351 } 352 353 /** 354 * Disables the "Workspace" setting if the Asana access token is not set. 355 * 356 * @return bool True if the setting should be disabled, false otherwise. 357 */ 358 private function disable_check_workspace_id(): bool { 321 359 return empty( $this->options['access_token'] ); 322 360 } … … 330 368 */ 331 369 private function disable_check_assignee_id(): bool { 332 return empty( $this->options['access_token'] ) ;370 return empty( $this->options['access_token'] ) || empty( $this->options['workspace_id'] ); 333 371 } 334 372 … … 700 738 701 739 /** 740 * Retrieves workspaces from Asana API. 741 * 742 * @return array List of workspaces or an empty array on failure. 743 */ 744 private function get_options_workspace_id(): array { 745 return xsffa_core()->asana_api->retrieve_workspaces(); 746 } 747 748 /** 702 749 * Retrieves projects from Asana API. 703 750 * … … 746 793 747 794 // Check if the order is synced (has an Asana task ID). 748 if ( $order->get_meta( XSFFA_Asana_Api::ASANA_ORDER_META_KEY, true) ) {795 if ( XSFFA_Asana_Api::get_synced_task_id( $order ) ) { 749 796 ++$synced_orders[ $date ]; 750 797 } … … 905 952 wp_send_json_success( array( 'task_id' => $task_id ) ); 906 953 } else { 907 wp_send_json_error( array( 'message' => 'Failed to sync order.' ) ); 954 $reason = xsffa_core()->asana_api->get_last_error(); 955 wp_send_json_error( array( 'message' => $reason ? "Failed to sync order: {$reason}" : 'Failed to sync order.' ) ); 908 956 } 909 957 } … … 928 976 wp_send_json_success( array( 'task_id' => $task_id ) ); 929 977 } else { 930 wp_send_json_error( array( 'message' => 'Failed to resync order.' ) ); 978 $reason = xsffa_core()->asana_api->get_last_error(); 979 wp_send_json_error( array( 'message' => $reason ? "Failed to resync order: {$reason}" : 'Failed to resync order.' ) ); 931 980 } 932 981 } -
shopflow-for-asana/trunk/includes/helpers.php
r3253553 r3645504 5 5 * @package shopflow-for-asana 6 6 */ 7 8 if ( ! defined( 'ABSPATH' ) ) { 9 exit; 10 } 7 11 8 12 if ( ! function_exists( 'xsffa_sanitize_settings' ) ) { … … 77 81 $order_date = $order->get_date_created()->date( 'Y-m-d H:i:s' ); 78 82 $status = $order->get_status(); 79 $asana_task_id = $order->get_meta( XSFFA_Asana_Api::ASANA_ORDER_META_KEY, true);83 $asana_task_id = XSFFA_Asana_Api::get_synced_task_id( $order ); 80 84 81 85 return array( -
shopflow-for-asana/trunk/readme.txt
r3253553 r3645504 3 3 Tags: asana, woocommerce, order sync, task management, automation 4 4 Requires at least: 5.6 5 Tested up to: 6.75 Tested up to: 7.0 6 6 Requires PHP: 7.4 7 Stable tag: 1.0. 07 Stable tag: 1.0.1 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 63 63 == Changelog == 64 64 65 = 1.0.1 = 66 * Confirmed compatibility with WordPress 7.0. 67 * Declared compatibility with WooCommerce High-Performance Order Storage (HPOS). 68 * Added a Workspace setting, required for Asana accounts that belong to more than one workspace. Fixes the Project/Assignee dropdowns showing no options for such accounts, and fixes order sync failing silently in the same situation. 69 * The Project and Assignee dropdowns now refresh live via AJAX when the Workspace selection changes, instead of only updating after saving the page. 70 * Project, Assignee, and Workspace lists are now fetched across all pages from Asana, instead of only the first page. 71 * Order sync errors now show the actual reason from Asana (e.g. an invalid token) instead of a single generic "Failed to sync order" message for every failure. 72 * Fixed PHP warnings on order sync failure caused by an unguarded array access. 73 * Fixed the order-sync meta key using the wrong plugin's prefix (a copy-paste leftover); orders already synced under the old key are still recognized via a compatibility fallback, so nothing needs to be re-synced. 74 * Removed discouraged `load_plugin_textdomain()` call (translations are auto-loaded by WordPress.org since 4.6). 75 * Added missing direct-file-access guards in `includes/helpers.php` and `includes/class-xsffa-orders-table.php`. 76 65 77 = 1.0.0 = 66 78 * Initial release. … … 72 84 == Upgrade Notice == 73 85 86 = 1.0.1 = 87 Compatibility update for WordPress 7.0 and WooCommerce HPOS, plus more reliable order sync error handling. Safe, non-breaking update. 88 74 89 = 1.0.0 = 75 90 Upgrade now to automate your WooCommerce order management with Asana. Turn orders into tasks effortlessly! -
shopflow-for-asana/trunk/shopflow-for-asana.php
r3253553 r3645504 4 4 * Requires Plugins: woocommerce 5 5 * Description: Automatically sync WooCommerce orders to Asana tasks. 6 * Version: 1.0. 06 * Version: 1.0.1 7 7 * Author: Xplodman 8 8 * Author URI: https://github.com/xplodman/ … … 22 22 define( 'XSFFA_PLUGIN_REL_PATH', dirname( XSFFA_PLUGIN_BASEDIR ) . '/' ); 23 23 define( 'XSFFA_PLUGIN_PATH', plugin_dir_path( __FILE__ ) ); 24 25 // Declare compatibility with WooCommerce High-Performance Order Storage (HPOS). 26 // This plugin only ever accesses order data through wc_get_order()/wc_get_orders(), 27 // so it doesn't touch the legacy post tables directly and is HPOS-safe. 28 add_action( 29 'before_woocommerce_init', 30 function () { 31 if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { 32 \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true ); 33 } 34 } 35 ); 24 36 25 37 // Autoload classes.
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)