$method, 'timeout' => 12, 'redirection' => 0, 'sslverify' => true, 'limit_response_size' => 262144, 'headers' => ['Authorization' => 'Bearer ' . $token, 'Accept' => 'application/json', 'Accept-Language' => 'en', 'User-Agent' => 'WizupForms/' . WIZUP_FORMS_VERSION], ]; if ($payload !== null) { $args['headers']['Content-Type'] = 'application/json'; $args['headers']['Idempotency-Key'] = $key; $args['body'] = wp_json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } return wp_safe_remote_request(WIZUP_FORMS_API . $path, $args); } public static function send(array $payload, string $key, array $workflow = [], ?callable $checkpoint = null, array $meta = []): array { if (($workflow['kind'] ?? '') === 'repeat_note') { return Repeat_Inquiry::deliver($workflow, $key, $checkpoint); } $response = self::request('POST', '/leads', $payload, $key); if (is_wp_error($response)) { return ['state' => 'retry', 'code' => 0, 'note' => 'תקלה בתקשורת; יבוצע ניסיון חוזר.', 'delay' => 60]; } $code = wp_remote_retrieve_response_code($response); $body = json_decode(wp_remote_retrieve_body($response), true); if ($code === 422) { if (self::is_duplicate($body) && Settings::repeat_notes()) { return Repeat_Inquiry::start($payload, $key, $checkpoint, $meta); } if ($checkpoint && is_array($body['errors'] ?? null)) { $errors = []; foreach (array_slice($body['errors'], 0, 10, true) as $field => $messages) { if (!is_array($messages)) { continue; } foreach (array_slice($messages, 0, 3) as $message) { if (is_string($message)) { $errors[mb_substr((string) $field, 0, 100)][] = mb_substr(sanitize_text_field($message), 0, 500); } } } $checkpoint(['kind' => 'validation_error', 'errors' => $errors]); } return ['state' => 'failed', 'code' => 422, 'note' => self::validation_note($body)]; } if ($code >= 200 && $code < 300) { if (empty($body['data']['id']) || !is_numeric($body['data']['id'])) { return ['state' => 'attention', 'code' => $code, 'note' => 'השרת החזיר תשובה לא מוכרת. יש לבדוק ב-Wizup לפני פעולה נוספת.']; } $missing = array_diff_key($payload['custom_fields'] ?? [], is_array($body['data']['custom_fields'] ?? null) ? $body['data']['custom_fields'] : []); return ['state' => $missing ? 'warning' : 'sent', 'code' => $code, 'lead_id' => (int) $body['data']['id'], 'note' => $missing ? 'הליד נוצר אך חלק ממפתחות השדות המותאמים לא נשמרו. יש לבדוק שהם מוגדרים ב-Wizup.' : 'הליד נוצר ב-Wizup.']; } if ($code === 429 || $code >= 500) { $retry = wp_remote_retrieve_header($response, 'retry-after'); $delay = is_scalar($retry) && ctype_digit((string) $retry) ? (int) $retry : (is_string($retry) ? max(0, (int) strtotime($retry) - time()) : 0); return ['state' => 'retry', 'code' => $code, 'note' => $code === 429 ? 'מגבלת קצב או מכסת API. השליחה תנסה שוב.' : 'תקלה בשרת Wizup. השליחה תנסה שוב.', 'delay' => min(86400, max(60, $delay))]; } $messages = [ 401 => 'ה-Token נדחה או פג תוקף. יש לבדוק את החיבור.', 403 => 'אין הרשאת leads:write, גישת API בחבילה או הרשאה מתאימה לחשבון.', 409 => 'נדרש בירור ב-Wizup לגבי הפעולה. לא נשלחת בקשה חדשה אוטומטית.', 404 => 'נקודת ה-API לא נמצאה בשרת.', ]; return ['state' => $code === 409 ? 'attention' : 'failed', 'code' => $code, 'note' => $messages[$code] ?? 'השליחה נדחתה על ידי השרת. יש לבדוק את החיבור.']; } public static function is_duplicate($body): bool { if (!is_array($body) || !is_array($body['errors'] ?? null) || array_keys($body['errors']) !== ['phone'] || !is_array($body['errors']['phone'])) { return false; } return count($body['errors']['phone']) === 1 && in_array($body['errors']['phone'][0] ?? null, [ 'This phone number is already registered in the leads.', 'This phone number is already registered.', 'The phone has already been taken.', 'The phone number has already been taken.', 'מספר הטלפון הזה כבר רשום כליד.', 'מספר הטלפון הזה כבר רשום.', 'שדה מספר טלפון כבר תפוס.', ], true); } public static function validation_note($body): string { if (self::is_duplicate($body)) { return 'מספר הטלפון כבר קיים כליד ב-Wizup. הפנייה החוזרת לא נשמרה. אפשר להפעיל קליטת פניות חוזרות כהערה בהגדרות ולבצע בדיקה חוזרת.'; } $phone_errors = is_array($body['errors']['phone'] ?? null) ? $body['errors']['phone'] : []; if (array_intersect($phone_errors, ['The phone field must be a valid number.', 'The phone field must be a valid phone number.', 'Enter a valid phone number with its country code.', 'יש להזין מספר טלפון תקין עם קידומת מדינה.'])) { return 'Wizup דחתה את תקינות מספר הטלפון או את קידומת המדינה. ודאו ששדה הטלפון ממופה למספר הנכון. דחייה זו אינה אישור לכפילות.'; } if (is_array($body) && ($body['error'] ?? '') === 'invalid_idempotency_key') { return 'מזהה הפעולה נדחה על ידי Wizup. יש לבדוק את גרסת התוסף.'; } $labels = ['name' => 'שם', 'phone' => 'מספר טלפון', 'email' => 'אימייל', 'stage_id' => 'שלב ליד']; $invalid = []; foreach (array_keys(is_array($body['errors'] ?? null) ? $body['errors'] : []) as $field) { if (isset($labels[$field])) { $invalid[] = $labels[$field]; } elseif ($field === 'custom_fields' || str_starts_with((string) $field, 'custom_fields.')) { $invalid[] = 'שדות מותאמים (שדה חובה, סוג או אורך הערך)'; } } return $invalid ? 'Wizup דחתה את הנתונים בשדות: ' . implode(', ', array_unique($invalid)) . '. בדקו את המיפוי והערכים ושלחו טופס מתוקן.' : 'הנתונים נדחו ב-Wizup ללא פירוט שדות מוכר. יש לבדוק את המיפוי ושדות החובה.'; } public static function refresh_stages() { $items = []; for ($page = 1; $page <= 20; $page++) { $response = self::request('GET', '/lead-stages?per_page=100&page=' . $page); if (is_wp_error($response)) { return new \WP_Error('network', 'לא ניתן להתחבר ל-Wizup. נסו שוב.'); } $code = wp_remote_retrieve_response_code($response); $body = json_decode(wp_remote_retrieve_body($response), true); if ($code !== 200 || !is_array($body['data'] ?? null)) { return new \WP_Error('connection', 'בדיקת החיבור נכשלה (HTTP ' . $code . '). יש לבדוק Token, הרשאת leads:read וגישה ל-API בחבילה.'); } foreach ($body['data'] as $row) { if (isset($row['id'], $row['name']) && is_numeric($row['id']) && is_scalar($row['name'])) { $items[(int) $row['id']] = sanitize_text_field((string) $row['name']); } } if ($page >= (int) ($body['last_page'] ?? 1)) { break; } } update_option('wizup_forms_stages', ['connection' => Settings::fingerprint(), 'items' => $items, 'checked_at' => time()], false); return $items; } }