Files
WooDoo/includes/class-woodoo-api.php

260 lines
8.8 KiB
PHP
Raw Normal View History

<?php
/**
* Odoo JSON-RPC API client.
*
* Communicates with Odoo 19 via the /jsonrpc endpoint using the
* "common" (authenticate) and "object" (execute_kw) services.
* Uses WordPress's built-in HTTP API no Composer required.
*/
defined( 'ABSPATH' ) || exit;
class WooDoo_API {
private string $url;
private string $db;
private string $username;
private string $api_key;
private ?int $uid = null;
/** Cache results for one minute to reduce round-trips */
private const CACHE_TTL = 60;
public function __construct(
string $url,
string $db,
string $username,
string $api_key
) {
$this->url = $url;
$this->db = $db;
$this->username = $username;
$this->api_key = $api_key;
}
// ── Low-level JSON-RPC ────────────────────────────────────────────────
/**
* POST to /jsonrpc.
*
* @param string $service "common" | "object"
* @param string $method e.g. "authenticate" | "execute_kw"
* @param array $args positional arguments
* @return mixed decoded result or WP_Error
*/
public function jsonrpc( string $service, string $method, array $args ): mixed {
$body = wp_json_encode( [
'jsonrpc' => '2.0',
'method' => 'call',
'id' => wp_rand( 1, 999999999 ),
'params' => [
'service' => $service,
'method' => $method,
'args' => $args,
],
] );
$response = wp_remote_post(
$this->url . '/jsonrpc',
[
'headers' => [ 'Content-Type' => 'application/json' ],
'body' => $body,
'timeout' => 30,
'sslverify' => apply_filters( 'woodoo_ssl_verify', true ),
]
);
if ( is_wp_error( $response ) ) {
return $response;
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( isset( $data['error'] ) ) {
$msg = $data['error']['data']['message']
?? $data['error']['message']
?? 'Unknown Odoo error';
return new WP_Error( 'odoo_error', $msg, $data['error'] );
}
return $data['result'] ?? null;
}
// ── Authentication ────────────────────────────────────────────────────
/**
* Authenticate and cache the uid for this request lifecycle.
*/
public function authenticate(): ?int {
if ( $this->uid ) return $this->uid;
$result = $this->jsonrpc( 'common', 'authenticate', [
$this->db,
$this->username,
$this->api_key,
[],
] );
if ( is_wp_error( $result ) || ! is_int( $result ) || $result <= 0 ) {
return null;
}
$this->uid = $result;
return $this->uid;
}
// ── ORM execute_kw wrapper ────────────────────────────────────────────
/**
* Call any ORM method via execute_kw.
*
* @param string $model e.g. 'res.partner'
* @param string $method e.g. 'search_read'
* @param array $args positional args (list of lists usually)
* @param array $kwargs keyword args (fields, limit, offset, etc.)
*/
public function execute_kw( string $model, string $method, array $args = [], array $kwargs = [] ): mixed {
$uid = $this->authenticate();
if ( ! $uid ) {
return new WP_Error( 'woodoo_auth', 'Could not authenticate with Odoo.' );
}
return $this->jsonrpc( 'object', 'execute_kw', [
$this->db,
$uid,
$this->api_key,
$model,
$method,
$args,
$kwargs,
] );
}
// ── Convenience helpers ───────────────────────────────────────────────
public function search_read(
string $model,
array $domain = [],
array $fields = [],
int $limit = 0,
int $offset = 0,
string $order = ''
): array {
$kwargs = [ 'fields' => $fields ];
if ( $limit > 0 ) $kwargs['limit'] = $limit;
if ( $offset > 0 ) $kwargs['offset'] = $offset;
if ( $order !== '' ) $kwargs['order'] = $order;
$result = $this->execute_kw( $model, 'search_read', [ $domain ], $kwargs );
return is_array( $result ) ? $result : [];
}
public function search(
string $model,
array $domain = [],
int $limit = 0,
int $offset = 0,
string $order = ''
): array {
$kwargs = [];
if ( $limit > 0 ) $kwargs['limit'] = $limit;
if ( $offset > 0 ) $kwargs['offset'] = $offset;
if ( $order !== '' ) $kwargs['order'] = $order;
$result = $this->execute_kw( $model, 'search', [ $domain ], $kwargs );
return is_array( $result ) ? $result : [];
}
public function read( string $model, array $ids, array $fields = [] ): array {
$kwargs = $fields ? [ 'fields' => $fields ] : [];
$result = $this->execute_kw( $model, 'read', [ $ids ], $kwargs );
return is_array( $result ) ? $result : [];
}
public function create( string $model, array $values ): ?int {
$result = $this->execute_kw( $model, 'create', [ $values ] );
return is_int( $result ) ? $result : null;
}
public function write( string $model, array $ids, array $values ): bool {
$result = $this->execute_kw( $model, 'write', [ $ids, $values ] );
return $result === true;
}
public function search_count( string $model, array $domain = [] ): int {
$result = $this->execute_kw( $model, 'search_count', [ $domain ] );
return is_int( $result ) ? $result : 0;
}
public function unlink( string $model, array $ids ): bool {
$result = $this->execute_kw( $model, 'unlink', [ $ids ] );
return $result === true;
}
// ── Partner helpers ───────────────────────────────────────────────────
/**
* Find a partner by email or create one.
* Returns the Odoo partner ID.
*/
public function find_or_create_partner( string $email, string $name, array $extra = [] ): ?int {
$found = $this->search( 'res.partner', [ [ 'email', '=', $email ] ], 1 );
if ( ! empty( $found ) ) {
return (int) $found[0];
}
return $this->create( 'res.partner', array_merge( [
'name' => $name,
'email' => $email,
], $extra ) );
}
// ── Product helpers ───────────────────────────────────────────────────
/**
* Find a product.product by its SKU (default_code).
* Returns product ID or null if not found.
*/
public function find_product_by_sku( string $sku ): ?int {
if ( empty( $sku ) ) return null;
$found = $this->search( 'product.product', [ [ 'default_code', '=', $sku ] ], 1 );
return ! empty( $found ) ? (int) $found[0] : null;
}
// ── Diagnostics ───────────────────────────────────────────────────────
/**
* Test connectivity and credentials.
* Returns ['success' => bool, 'message' => string, 'version' => string|null]
*/
public function test_connection(): array {
// Version check (no auth needed)
$version = $this->jsonrpc( 'common', 'version', [] );
if ( is_wp_error( $version ) ) {
return [
'success' => false,
'message' => 'Cannot reach Odoo: ' . $version->get_error_message(),
'version' => null,
];
}
$ver_str = $version['server_version'] ?? 'unknown';
// Try authenticate
$uid = $this->authenticate();
if ( ! $uid ) {
return [
'success' => false,
'message' => "Reached Odoo {$ver_str} but authentication failed. Check username / API key.",
'version' => $ver_str,
];
}
return [
'success' => true,
'message' => "Connected to Odoo {$ver_str} as UID {$uid}.",
'version' => $ver_str,
];
}
}