PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.10.22
ووسلام – همگام سازی ووکامرس و باسلام v1.10.22
1.10.23 1.10.22 1.10.21 1.10.19 1.10.20 1.10.18 1.10.17 1.10.15 1.10.14 1.10.13 1.10.12 1.10.10 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.2 1.9.1 1.9.0 All 56 releases
← All changes | includes/Services/Products/AutoConnectProducts.php +82 -5 1.10.2 → 1.10.22 View file →
@@ -10,8 +10,10 @@
10 10 defined('ABSPATH') || exit;
11 11
12 12 class AutoConnectProducts
13 13 {
14 + private const FALLBACK_BATCH_SIZE = 10;
15 +
14 16 public function checkSameProduct($title = null, $cursor = null)
15 17 {
16 18 try {
17 19 $getProductData = new FetchProductsData();
@@ -43,13 +45,15 @@
43 45
44 46 foreach ($syncBasalamProducts['data'] as $syncBasalamProduct) {
45 47 $normalizedTitle = trim($syncBasalamProduct['title']);
46 48
47 - if (mb_strlen($normalizedTitle) >= 120) {
48 - $likeTitle = $normalizedTitle . '%';
49 - } else {
50 - $likeTitle = $normalizedTitle;
51 - }
49 + // A WooCommerce title often adds a model/code suffix that is
50 + // not present in Basalam (for example: "... کد 50"). Use a
51 + // prefix only for sufficiently specific titles; short,
52 + // generic titles must retain exact-match behaviour.
53 + $likeTitle = mb_strlen($normalizedTitle, 'UTF-8') >= 12
54 + ? $wpdb->esc_like($normalizedTitle) . '%'
55 + : $normalizedTitle;
52 56
53 57 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct lookup on core posts/postmeta tables; no cache key available for this title match.
54 58 $productId = $wpdb->get_var(
55 59 $wpdb->prepare("
@@ -97,8 +101,9 @@
97 101 'message' => 'اتصال محصولات کامل شد.',
98 102 'status_code' => 200,
99 103 'has_more' => false,
100 104 'next_cursor' => null,
105 + 'completed' => true,
101 106 ];
102 107 } else {
103 108 return [
104 109 'error' => true,
@@ -105,8 +110,9 @@
105 110 'message' => 'محصول مشابهی یافت نشد.',
106 111 'status_code' => 404,
107 112 'has_more' => false,
108 113 'next_cursor' => null,
114 + 'completed' => true,
109 115 ];
110 116 }
111 117 }
112 118 } catch (RetryableException $e) {
@@ -124,6 +130,77 @@
124 130 'operation' => 'اتصال خودکار محصولات',
125 131 ]);
126 132 throw $e;
127 133 }
134 + }
135 +
136 + /**
137 + * Search Basalam for the remaining unconnected WooCommerce products.
138 + *
139 + * The first pass is intentionally vendor-list based because it is cheap.
140 + * This second pass reuses the exact endpoint used by the single-product
141 + * screen, which catches harmless title differences that the first pass
142 + * cannot see.
143 + *
144 + * @return array{has_more: bool, next_product_id: int, connected: int}
145 + */
146 + public function connectUnconnectedProductsBySearch(int $lastProductId = 0, int $batchSize = self::FALLBACK_BATCH_SIZE): array
147 + {
148 + global $wpdb;
149 +
150 + $productIdMetaKey = ProductMetaKey::basalamProductId();
151 + $batchSize = max(1, min(25, $batchSize));
152 +
153 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- This is a cursor query over core product tables; every run must see newly connected products.
154 + $products = $wpdb->get_results(
155 + $wpdb->prepare(
156 + "SELECT p.ID, p.post_title
157 + FROM {$wpdb->posts} p
158 + LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = %s
159 + WHERE p.post_type = 'product'
160 + AND p.post_status = 'publish'
161 + AND pm.post_id IS NULL
162 + AND p.ID > %d
163 + ORDER BY p.ID ASC
164 + LIMIT %d",
165 + $productIdMetaKey,
166 + $lastProductId,
167 + $batchSize
168 + )
169 + );
170 +
171 + if (empty($products)) {
172 + return [
173 + 'has_more' => false,
174 + 'next_product_id' => $lastProductId,
175 + 'connected' => 0,
176 + ];
177 + }
178 +
179 + $connected = 0;
180 + $lastSeenProductId = $lastProductId;
181 + $connectProductService = new ConnectSingleProductService();
182 +
183 + foreach ($products as $product) {
184 + $lastSeenProductId = (int) $product->ID;
185 + $searchResults = $this->checkSameProduct((string) $product->post_title);
186 + $match = ProductTitleMatcher::bestMatch((string) $product->post_title, $searchResults);
187 +
188 + if (!$match) continue;
189 +
190 + if ($connectProductService->connectProductById($product->ID, $match['id'])) {
191 + $connected++;
192 + Logger::info($match['title'] . ' به محصول مشابه خود در باسلام متصل شد', [
193 + 'product_id' => (int) $product->ID,
194 + 'basalam_product_id' => $match['id'],
195 + 'عملیات' => 'اتصال جست‌وجویی محصولات ووکامرس و باسلام',
196 + ]);
197 + }
198 + }
199 +
200 + return [
201 + 'has_more' => count($products) === $batchSize,
202 + 'next_product_id' => $lastSeenProductId,
203 + 'connected' => $connected,
204 + ];
128 205 }
129 206 }