| @@ -2873,8 +2873,15 @@ | ||
| 2873 | 2873 | |
| 2874 | 2874 | // Success: reshape the list into autocomplete {label,value} pairs. |
| 2875 | 2875 | if ( isset( $answer['success'] ) && true === $answer['success'] ) { |
| 2876 | 2876 | $mls_data = $answer['mls_list']; |
| 2877 | + | |
| 2878 | + // Fresh catalogue in hand: refresh registered connections' display | |
| 2879 | + // names so an MLS renamed upstream shows its new name everywhere | |
| 2880 | + // (Connections table, import task picker) after a cache clear. | |
| 2881 | + if ( is_array( $mls_data ) && class_exists( 'Mlsimport_Connections' ) ) { | |
| 2882 | + Mlsimport_Connections::sync_names( $mls_data ); | |
| 2883 | + } | |
| 2877 | 2884 | // Prepend the "not listed" opt-out choice. |
| 2878 | 2885 | $mls_data['0'] = esc_html__( 'My MLS is not on this list', 'mlsimport' ); |
| 2879 | 2886 | |
| 2880 | 2887 | $autofill_array = array(); |
| @@ -2952,14 +2959,17 @@ | ||
| 2952 | 2959 | /** |
| 2953 | 2960 | * Cron entry point for the daily SaaS reconciliation pass. |
| 2954 | 2961 | * |
| 2955 | 2962 | * Bails out early unless at least one non-trashed import task has a title, then |
| 2956 | - * calls the one deep reconciliation interface when an MLS name is configured. | |
| 2963 | + * hands off to the per-connection runner (#279) when an MLS name is configured: | |
| 2964 | + * one sub-run of the deep reconciliation module per registered connection, | |
| 2965 | + * or the single legacy unscoped run while no connections are registered. | |
| 2957 | 2966 | * |
| 2958 | - * The same function handles the deduplicated one-hour retry hook. Trigger code | |
| 2959 | - * intentionally owns no snapshot, status, batching, or deletion decisions. | |
| 2967 | + * The same function handles the deduplicated one-hour retry hook — a retry | |
| 2968 | + * re-runs all connections, and completed ones converge to no-op keeps. Trigger | |
| 2969 | + * code intentionally owns no snapshot, status, batching, or deletion decisions. | |
| 2960 | 2970 | * |
| 2961 | - * @return array<string, int|string>|null Reconciliation Outcome, or null when ineligible. | |
| 2971 | + * @return array<int, array<string, int|string>>|null Outcome per connection, or null when ineligible. | |
| 2962 | 2972 | */ |
| 2963 | 2973 | function mlsimport_saas_reconciliation_event_function() { |
| 2964 | 2974 | |
| 2965 | 2975 | // Pull the API token (side effect: ensures a fresh token) and plugin options. |
| @@ -2979,19 +2989,13 @@ | ||
| 2979 | 2989 | if ( ! $has_titled_task ) { |
| 2980 | 2990 | return null; |
| 2981 | 2991 | } |
| 2982 | 2992 | |
| 2983 | - // Only reconcile when an MLS name is configured. | |
| 2993 | + // Only reconcile when an MLS name is configured. The runner sequences one | |
| 2994 | + // scoped sub-run per registered connection (legacy unscoped run when the | |
| 2995 | + // registry is empty) and records/logs every outcome itself. | |
| 2984 | 2996 | if ( isset( $options['mlsimport_mls_name'] ) && '' !== $options['mlsimport_mls_name'] ) { |
| 2985 | - $environment = new Mlsimport_Reconciliation_WordPress_Environment( | |
| 2986 | - static function () use ( $mlsimport ): array { | |
| 2987 | - return $mlsimport->admin->mlsimport_saas_get_mls_reconciliation_data(); | |
| 2988 | - } | |
| 2989 | - ); | |
| 2990 | - | |
| 2991 | - $outcome = ( new Mlsimport_Reconciliation( $environment ) )->reconcile_current_listings(); | |
| 2992 | - error_log( 'MLSImport reconciliation outcome: ' . wp_json_encode( $outcome ) ); | |
| 2993 | - return $outcome; | |
| 2997 | + return mlsimport_reconciliation_run_connections(); | |
| 2994 | 2998 | } |
| 2995 | 2999 | |
| 2996 | 3000 | return null; |
| 2997 | 3001 | } |
| @@ -3095,11 +3099,12 @@ | ||
| 3095 | 3099 | * @param int $postID Import Task post ID. |
| 3096 | 3100 | * @return void Output is echoed directly. |
| 3097 | 3101 | */ |
| 3098 | 3102 | function mlsimport_populate_columns_params_display( $postID ) { |
| 3099 | - // Field definitions come from the admin class. | |
| 3103 | + // Field definitions come from the admin class, scoped to the TASK's own | |
| 3104 | + // connection (#277) so each row's parameters render against its MLS. | |
| 3100 | 3105 | global $mlsimport; |
| 3101 | - $field_import = $mlsimport->admin->mlsimport_saas_return_mls_fields(); | |
| 3106 | + $field_import = $mlsimport->admin->mlsimport_saas_return_mls_fields( mlsimport_task_mls_id( (int) $postID ) ); | |
| 3102 | 3107 | |
| 3103 | 3108 | // Fields whose stored value is always shown verbatim (never collapsed to "ALL"). |
| 3104 | 3109 | $select_all_none = array( |
| 3105 | 3110 | 'InternetAddressDisplayYN', |
| @@ -3229,9 +3234,15 @@ | ||
| 3229 | 3234 | 1 === (int) get_post_meta( $post->ID, 'mlsimport_item_stat_cron', true ), |
| 3230 | 3235 | time(), |
| 3231 | 3236 | // Watermarks are stored with wp_date() in site-local time, so |
| 3232 | 3237 | // the overdue cutoff must be built the same way to compare. |
| 3233 | - wp_date( 'Y-m-d\TH:i', time() - MLSIMPORT_TASK_HEALTH_OVERDUE_AFTER ) | |
| 3238 | + wp_date( 'Y-m-d\TH:i', time() - MLSIMPORT_TASK_HEALTH_OVERDUE_AFTER ), | |
| 3239 | + // The hourly runner's own eligibility rule (GitHub issue #330): | |
| 3240 | + // a task it will never pick up must not read as merely overdue. | |
| 3241 | + mlsimport_cron_task_is_eligible( | |
| 3242 | + (int) get_post_meta( $post->ID, 'mlsimport_initial_import_completed', true ), | |
| 3243 | + (string) get_post_meta( $post->ID, 'mlsimport_spawn_status', true ) | |
| 3244 | + ) | |
| 3234 | 3245 | ); |
| 3235 | 3246 | ?> |
| 3236 | 3247 | <span class="mlsimport-task-health mlsimport-task-health--<?php echo esc_attr( $health['level'] ); ?>"> |
| 3237 | 3248 | <?php echo esc_html( $health['label'] ); ?> |