<?php
require_once 'odoo_config.php';
class OdooAPI {
private $url;
private $db;
private $username;
private $password;
private $uid;
public function __construct() {
$this->url = OdooConfig::ODOO_URL;
$this->db = OdooConfig::ODOO_DB;
$this->username = OdooConfig::ODOO_USERNAME;
$this->password = OdooConfig::ODOO_PASSWORD;
$this->uid = null;
}
/**
* Create XML-RPC request manually
*/
private function createXmlRpcRequest($method, $params) {
$xml = '<?xml version="1.0"?>' . "\n";
$xml .= '<methodCall>' . "\n";
$xml .= '<methodName>' . htmlspecialchars($method) . '</methodName>' . "\n";
$xml .= '<params>' . "\n";
foreach ($params as $param) {
$xml .= '<param>' . $this->encodeValue($param) . '</param>' . "\n";
}
$xml .= '</params>' . "\n";
$xml .= '</methodCall>' . "\n";
return $xml;
}
/**
* Encode value for XML-RPC
*/
private function encodeValue($value) {
if (is_string($value)) {
return '<value><string>' . htmlspecialchars($value) . '</string></value>';
} elseif (is_int($value)) {
return '<value><int>' . $value . '</int></value>';
} elseif (is_bool($value)) {
return '<value><boolean>' . ($value ? '1' : '0') . '</boolean></value>';
} elseif (is_array($value)) {
if (array_keys($value) === range(0, count($value) - 1)) {
// Indexed array
$xml = '<value><array><data>';
foreach ($value as $item) {
$xml .= $this->encodeValue($item);
}
$xml .= '</data></array></value>';
return $xml;
} else {
// Associative array (struct)
$xml = '<value><struct>';
foreach ($value as $key => $val) {
$xml .= '<member>';
$xml .= '<name>' . htmlspecialchars($key) . '</name>';
$xml .= $this->encodeValue($val);
$xml .= '</member>';
}
$xml .= '</struct></value>';
return $xml;
}
} elseif (is_null($value) || $value === false) {
return '<value><boolean>0</boolean></value>';
} else {
return '<value><string>' . htmlspecialchars((string)$value) . '</string></value>';
}
}
/**
* Parse XML-RPC response
*/
private function parseXmlRpcResponse($xml) {
$doc = new DOMDocument();
$doc->loadXML($xml);
$fault = $doc->getElementsByTagName('fault');
if ($fault->length > 0) {
throw new Exception('XML-RPC Fault');
}
$params = $doc->getElementsByTagName('param');
if ($params->length > 0) {
return $this->decodeValue($params->item(0)->firstChild);
}
return false;
}
/**
* Decode XML-RPC value
*/
private function decodeValue($node) {
if (!$node) return null;
$value = $node->firstChild;
if (!$value) return null;
switch ($value->nodeName) {
case 'string':
return $value->nodeValue;
case 'int':
case 'i4':
return (int)$value->nodeValue;
case 'boolean':
return (bool)$value->nodeValue;
case 'array':
$result = [];
$data = $value->getElementsByTagName('value');
foreach ($data as $item) {
$result[] = $this->decodeValue($item);
}
return $result;
case 'struct':
$result = [];
$members = $value->getElementsByTagName('member');
foreach ($members as $member) {
$name = $member->getElementsByTagName('name')->item(0)->nodeValue;
$val = $member->getElementsByTagName('value')->item(0);
$result[$name] = $this->decodeValue($val);
}
return $result;
default:
return $value->nodeValue;
}
}
/**
* Authenticate with Odoo
*/
private function authenticate() {
if ($this->uid !== null) {
return $this->uid;
}
try {
$xml = $this->createXmlRpcRequest('authenticate', [
$this->db,
$this->username,
$this->password,
[]
]);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->url . OdooConfig::XMLRPC_COMMON);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: text/xml']);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception('HTTP Error: ' . $httpCode);
}
$this->uid = $this->parseXmlRpcResponse($response);
if (!$this->uid || $this->uid === false) {
throw new Exception('Authentication failed');
}
return $this->uid;
} catch (Exception $e) {
error_log('Odoo Authentication Error: ' . $e->getMessage());
return false;
}
}
/**
* Execute Odoo method
*/
private function execute($model, $method, $args = [], $kwargs = []) {
$uid = $this->authenticate();
if (!$uid) {
return false;
}
try {
$xml = $this->createXmlRpcRequest('execute_kw', [
$this->db,
$uid,
$this->password,
$model,
$method,
$args,
$kwargs
]);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->url . OdooConfig::XMLRPC_OBJECT);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: text/xml']);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception('HTTP Error: ' . $httpCode);
}
return $this->parseXmlRpcResponse($response);
} catch (Exception $e) {
error_log('Odoo API Error: ' . $e->getMessage());
return false;
}
}
/**
* Create a new lead in CRM
*/
public function createLead($data) {
$leadData = [
'name' => $data['name'] ?? 'New Lead',
'contact_name' => $data['contact_name'] ?? '',
'phone' => $data['phone'] ?? '',
'email_from' => $data['email'] ?? '',
'description' => $data['description'] ?? '',
'source_id' => $this->getSourceId($data['source'] ?? ''),
'stage_id' => OdooConfig::LEAD_STAGE_NEW,
'country_id' => OdooConfig::DEFAULT_COUNTRY_ID,
'company_id' => OdooConfig::DEFAULT_COMPANY_ID,
'user_id' => false, // Unassigned initially
];
// Add custom fields if provided
if (isset($data['branch_name'])) {
$leadData['x_branch'] = $data['branch_name'];
}
if (isset($data['service_type'])) {
$leadData['x_service_type'] = $data['service_type'];
}
if (isset($data['car_info'])) {
$leadData['x_car_info'] = $data['car_info'];
}
if (isset($data['visit_date'])) {
$leadData['x_visit_date'] = $data['visit_date'];
}
if (isset($data['reservation_date'])) {
$leadData['x_reservation_date'] = $data['reservation_date'];
}
return $this->execute('crm.lead', 'create', [$leadData]);
}
/**
* Get or create source ID
*/
private function getSourceId($sourceName) {
if (empty($sourceName)) {
return false;
}
// Search for existing source
$sourceIds = $this->execute('utm.source', 'search', [['name', '=', $sourceName]]);
if ($sourceIds && count($sourceIds) > 0) {
return $sourceIds[0];
}
// Create new source if not found
$sourceId = $this->execute('utm.source', 'create', [['name' => $sourceName]]);
return $sourceId;
}
/**
* Create partner (customer) record
*/
public function createPartner($data) {
$partnerData = [
'name' => $data['name'] ?? '',
'phone' => $data['phone'] ?? '',
'email' => $data['email'] ?? '',
'is_company' => false,
'customer_rank' => 1,
'country_id' => OdooConfig::DEFAULT_COUNTRY_ID,
];
return $this->execute('res.partner', 'create', [$partnerData]);
}
/**
* Search for existing partner by phone or email
*/
public function findPartner($phone = '', $email = '') {
$domain = [];
if (!empty($phone)) {
$domain[] = ['phone', '=', $phone];
}
if (!empty($email)) {
if (!empty($domain)) {
$domain = ['|'] + $domain + [['email', '=', $email]];
} else {
$domain[] = ['email', '=', $email];
}
}
if (empty($domain)) {
return false;
}
$partnerIds = $this->execute('res.partner', 'search', [$domain], ['limit' => 1]);
return $partnerIds && count($partnerIds) > 0 ? $partnerIds[0] : false;
}
/**
* Create activity/task for follow-up
*/
public function createActivity($leadId, $activityType, $summary, $note = '') {
$activityData = [
'res_model' => 'crm.lead',
'res_id' => $leadId,
'activity_type_id' => $activityType,
'summary' => $summary,
'note' => $note,
'date_deadline' => date('Y-m-d', strtotime('+1 day')),
];
return $this->execute('mail.activity', 'create', [$activityData]);
}
/**
* Test connection to Odoo
*/
public function testConnection() {
try {
$uid = $this->authenticate();
if ($uid) {
// Try to read user info
$userInfo = $this->execute('res.users', 'read', [$uid], ['fields' => ['name', 'login']]);
return $userInfo !== false;
}
return false;
} catch (Exception $e) {
error_log('Odoo Connection Test Error: ' . $e->getMessage());
return false;
}
}
}
?>