PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 7.2
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v7.2
7.2.1 7.2 7.1.2 7.1.1 7.1 7.0.4 7.0.6 7.0.7 6.3.8 6.3.7 6.3.6 6.3.5 6.3.4 6.3.3 6.3.1 trunk 5.7.3 5.7.5 5.8.1 5.8.2 5.8.3 5.8.4 5.8.6 6.0.4 6.0.5 All 36 releases
← All changes | includes/ThemeImport.php +40 -7 7.1.27.2 View file →
@@ -334,9 +334,10 @@
334 334 * Record the SaaS connection-health state (#208).
335 335 *
336 336 * Stores array{status, since} in the mlsimport_connection_health option:
337 337 * 'healthy', 'credentials_invalid' (server rejected the stored account),
338 - * or 'credentials_missing' (nothing configured). Transient failures such
338 + * 'no_subscription' (password accepted, account not active — #322) or
339 + * 'credentials_missing' (nothing configured). Transient failures such
339 340 * as network timeouts never call this, so a working state is not lost to
340 341 * a hiccup. Re-recording an unchanged status is skipped so 'since' keeps
341 342 * pointing at when the state actually began.
342 343 *
@@ -372,9 +373,12 @@
372 373 * Request a fresh JWT from the SaaS 'token' endpoint and cache it.
373 374 *
374 375 * Reads the stored username/password, POSTs them, and on success stores the
375 376 * token in a transient plus the expiry timestamp in an option. Bumps the
376 - * 'token_failures' telemetry counter on every failure path.
377 + * 'token_failures' telemetry counter on every failure path — WITHOUT a
378 + * connection id (#283): the SaaS JWT is account-level, shared by every
379 + * connection, so its failures belong to no single MLS and count only in
380 + * the global bucket.
377 381 *
378 382 * @return bool True on successful refresh, false otherwise.
379 383 */
380 384 private static function refreshToken() {
@@ -434,21 +438,29 @@
434 438
435 439 // Decode the JSON token response.
436 440 $body = wp_remote_retrieve_body($response);
437 441 $data = json_decode($body, true);
442 + $code = intval( $response['response']['code'] ?? 0 );
438 443
439 444 // Reject any response missing success/token/expires.
440 445 if (!isset($data['success']) || !$data['success'] || !isset($data['token']) || !isset($data['expires'])) {
441 446 mlsimport_telemetry_bump( 'token_failures' );
442 447 delete_option( 'mlsimport_token_refresh_lock' );
443 - // The server answered and said no → the credentials themselves are
444 - // bad (terminal until the user fixes them). A malformed/partial
445 - // body is a server hiccup instead and leaves health untouched.
448 + // The server answered and said no → terminal until the user acts.
449 + // HTTP 403 means the password was right but the account has no
450 + // active subscription (#322); anything else is bad credentials.
451 + // A malformed/partial body is a server hiccup instead and leaves
452 + // health untouched.
446 453 if ( is_array( $data ) && array_key_exists( 'success', $data ) && ! $data['success'] ) {
447 - self::setConnectionHealth( 'credentials_invalid' );
454 + self::setConnectionHealth( 403 === $code ? 'no_subscription' : 'credentials_invalid' );
455 + // Same verdict, remembered for the "not connected" screens.
456 + mlsimport_account_status_record( array( 'success' => false, 'error_code' => $code ) );
448 457 }
449 458 return false;
450 459 }
460 +
461 + // A working login wipes any remembered failure reason (#322).
462 + mlsimport_account_status_record( $data );
451 463
452 464 // Store new token and expiry
453 465 //$mlsimport->admin->mlsimport_saas_store_mls_api_token_transient($data['token']);
454 466
@@ -668,8 +680,18 @@
668 680 // Capture the owning task before its meta row is deleted below; the
669 681 // success activity entry still needs it afterward.
670 682 $ownerTaskId = intval(get_post_meta($deleteId, 'MLSimport_item_inserted', true));
671 683
684 + // Dedupe (issue #282): this raw-SQL path bypasses the WP delete
685 + // hooks, so capture the address group now (meta is gone after the
686 + // raw delete) and re-evaluate it after success — deleting a flagged
687 + // winner must promote its hidden loser.
688 + $dedupeAddressKey = (string) get_post_meta($deleteId, 'mlsimport_address_key', true);
689 + // Telemetry (#283): the deletion counts against the
690 + // listing's OWN connection — read the provenance stamp
691 + // (#278) before the raw delete wipes its meta.
692 + $provenanceMlsId = (int) get_post_meta($deleteId, 'mlsimport_mls_id', true);
693 +
672 694 global $wpdb;
673 695 // Raw SQL delete skips wp_delete_post (too slow), so nothing cleans the
674 696 // property's term relationships, term counts or listings row. Do that
675 697 // cleanup explicitly (SQL-first) before removing the post itself.
@@ -696,11 +718,17 @@
696 718 foreach ($attachments as $attachmentId) {
697 719 wp_delete_attachment($attachmentId, true);
698 720 }
699 721
722 + // Dedupe (issue #282): the post row is durably gone — settle the
723 + // surviving copies of its address group (promote a hidden loser).
724 + if ('' !== $dedupeAddressKey && function_exists('mlsimport_dedupe_evaluate')) {
725 + mlsimport_dedupe_evaluate($dedupeAddressKey, (string) $postType);
726 + }
727 +
700 728 // Record the deletion in the activity feed only after it happened.
701 729 mlsimport_record_activity( 'deleted', $deleteId, $ListingKey, $ownerTaskId, 'reconciliation' );
702 - mlsimport_telemetry_bump( 'deleted' );
730 + mlsimport_telemetry_bump( 'deleted', 1, $provenanceMlsId );
703 731
704 732 $logEntry = 'MYSQL DELETE -> Property with id ' . $deleteId . ' (' . $postType . ') (status ' . $deleteIdStatus . ') and ' . $ListingKey . ' was deleted on ' . current_time('Y-m-d\TH:i') . PHP_EOL;
705 733 $this->writeImportLogs($logEntry, 'delete');
706 734 }
@@ -741,10 +769,15 @@
741 769 return false;
742 770 }
743 771
744 772 // Translate the shallow legacy option array into the stable module settings.
773 + // The listing's provenance (issue #278) is the task's OWN connection binding
774 + // (#277) read straight from post meta — deliberately NO current-connection
775 + // fallback on the write path (decision #266): an unbound task stamps 0
776 + // rather than silently adopting whichever connection is globally selected.
745 777 $settings = array(
746 778 'task_id' => (int) ( $itemIdArray['item_id'] ?? 0 ),
779 + 'mls_id' => (int) get_post_meta( (int) ( $itemIdArray['item_id'] ?? 0 ), 'mlsimport_item_mls_id', true ),
747 780 'source' => (string) $tipImport,
748 781 'statuses' => is_array( $mlsimportItemOptionData['mlsimport_item_standardstatus'] ?? null )
749 782 ? $mlsimportItemOptionData['mlsimport_item_standardstatus']
750 783 : array(),