PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.10.22
ووسلام – همگام سازی ووکامرس و باسلام v1.10.22
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 1.8.8 All 55 releases
sync-basalam / includes / Services / Products / ProductTitleMatcher.php

ProductTitleMatcher.php in ووسلام – همگام سازی ووکامرس و باسلام 1.10.22, at includes/Services/Products/ProductTitleMatcher.php

100 lines 3.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace SyncBasalam\Services\Products;
4
5 defined('ABSPATH') || exit;
6
7 /**
8 * Finds a safe title match between a WooCommerce product and a Basalam result.
9 *
10 * Basalam's search endpoint is deliberately used before this matcher. The
11 * matcher only decides whether one of those search results is close enough to
12 * connect; it must not turn an arbitrary search result into a connection.
13 */
14 final class ProductTitleMatcher
15 {
16 private const MIN_MATCH_SCORE = 80;
17 private const MIN_COMMON_TOKENS = 3;
18 private const MIN_PREFIX_LENGTH = 12;
19
20 public static function normalize(string $title): string
21 {
22 $title = strtr($title, [
23 'ي' => 'ی',
24 'ى' => 'ی',
25 'ك' => 'ک',
26 'ۀ' => 'ه',
27 'ة' => 'ه',
28 "‌" => ' ',
29 ]);
30
31 $title = mb_strtolower(trim($title), 'UTF-8');
32 $title = preg_replace('/[^\p{L}\p{N}]+/u', ' ', $title);
33 $title = preg_replace('/\s+/u', ' ', (string) $title);
34
35 return trim((string) $title);
36 }
37
38 /**
39 * Return a score from 0 to 100. Scores below 80 are intentionally not
40 * considered safe enough for an automatic connection.
41 */
42 public static function score(string $wooTitle, string $basalamTitle): int
43 {
44 $wooTitle = self::normalize($wooTitle);
45 $basalamTitle = self::normalize($basalamTitle);
46
47 if ($wooTitle === '' || $basalamTitle === '') return 0;
48 if ($wooTitle === $basalamTitle) return 100;
49
50 $shortTitle = mb_strlen($wooTitle, 'UTF-8') <= mb_strlen($basalamTitle, 'UTF-8')
51 ? $wooTitle
52 : $basalamTitle;
53 $longTitle = $shortTitle === $wooTitle ? $basalamTitle : $wooTitle;
54
55 if (
56 mb_strlen($shortTitle, 'UTF-8') >= self::MIN_PREFIX_LENGTH
57 && str_starts_with($longTitle, $shortTitle)
58 ) {
59 return 90;
60 }
61
62 $wooTokens = array_values(array_unique(explode(' ', $wooTitle)));
63 $basalamTokens = array_values(array_unique(explode(' ', $basalamTitle)));
64 $commonTokens = count(array_intersect($wooTokens, $basalamTokens));
65
66 if ($commonTokens < self::MIN_COMMON_TOKENS) return 0;
67
68 $wooCoverage = $commonTokens / count($wooTokens);
69 $basalamCoverage = $commonTokens / count($basalamTokens);
70 $coverage = min($wooCoverage, $basalamCoverage);
71
72 if ($coverage < 0.8) return 0;
73
74 return min(89, 70 + (int) round($coverage * 20));
75 }
76
77 /**
78 * @param array<int, array<string, mixed>> $products
79 * @return array<string, mixed>|null
80 */
81 public static function bestMatch(string $wooTitle, array $products): ?array
82 {
83 $bestProduct = null;
84 $bestScore = 0;
85
86 foreach ($products as $product) {
87 if (!is_array($product) || empty($product['title']) || empty($product['id'])) continue;
88
89 $score = self::score($wooTitle, (string) $product['title']);
90
91 if ($score > $bestScore) {
92 $bestScore = $score;
93 $bestProduct = $product;
94 }
95 }
96
97 return $bestScore >= self::MIN_MATCH_SCORE ? $bestProduct : null;
98 }
99 }
100