├── CHANGELOG.md
├── src
├── Exceptions
│ ├── AuthenticationException.php
│ ├── ConfigurationException.php
│ └── OdooException.php
├── Odoo
│ ├── Response
│ │ ├── EmptyListResponse.php
│ │ ├── BooleanResponse.php
│ │ ├── NumericResponse.php
│ │ ├── ListResponse.php
│ │ ├── EmptyResponse.php
│ │ ├── ScalarResponse.php
│ │ ├── ObjectResponse.php
│ │ ├── FaultCodeResponse.php
│ │ ├── VersionResponse.php
│ │ └── Response.php
│ ├── Client.php
│ ├── Request
│ │ ├── ContextBuilder.php
│ │ ├── QueryBuilder.php
│ │ ├── OptionsBuilder.php
│ │ ├── Request.php
│ │ └── RequestBuilder.php
│ ├── RequestFactory.php
│ ├── CommonEndpoint.php
│ ├── ResponseFactory.php
│ ├── Endpoint.php
│ ├── ConfigFactory.php
│ ├── Config.php
│ └── ObjectEndpoint.php
├── Facades
│ └── Odoo.php
├── Support
│ ├── helpers.php
│ └── Proxy.php
├── Providers
│ └── OdooServiceProvider.php
└── Odoo.php
├── test.sh
├── phpunit.xml
├── docker-compose.yml
├── composer.json
├── LICENSE.txt
├── docker
├── php8
│ └── Dockerfile
└── php7.4
│ └── Dockerfile
├── config
└── laravel-odoo-api.php
└── Readme.md
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # ChangeLog
2 |
3 | ## V0.1.0
4 |
5 | Write a Laravel Connector for Odoo based on Laradoo
6 |
--------------------------------------------------------------------------------
/src/Exceptions/AuthenticationException.php:
--------------------------------------------------------------------------------
1 | value);
17 | }
18 | }
--------------------------------------------------------------------------------
/src/Facades/Odoo.php:
--------------------------------------------------------------------------------
1 | response = $response;
16 | }
17 |
18 |
19 | }
--------------------------------------------------------------------------------
/src/Odoo/Response/ScalarResponse.php:
--------------------------------------------------------------------------------
1 | value = $raw;
20 | }
21 |
22 | public function unwrap()
23 | {
24 | return $this->rawResponse;
25 | }
26 | }
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 | ./tests/
14 |
15 |
16 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.7"
2 |
3 | services:
4 | app:
5 | build:
6 | context: .
7 | dockerfile: Dockerfile
8 | args:
9 | USER_ID: $uid
10 | GROUP_ID: $gid
11 | image: php
12 | environment:
13 | XDEBUG_MODE: "debug"
14 | XDEBUG_SESSION: "PHPSTORM"
15 | PHP_IDE_CONFIG: "serverName=box"
16 | XDEBUG_CONFIG: "client_host=172.17.0.1 client_port=9003"
17 | SSH_AUTH_SOCK: "/ssh-agent"
18 | volumes:
19 | - ${HOME}/.composer:/.composer
20 | - ./:/var/www
--------------------------------------------------------------------------------
/src/Odoo/Response/ObjectResponse.php:
--------------------------------------------------------------------------------
1 | value;
26 | }
27 |
28 |
29 | }
--------------------------------------------------------------------------------
/src/Odoo/Response/FaultCodeResponse.php:
--------------------------------------------------------------------------------
1 | faultString, $this->faultCode, null, $this->rawResponse);
29 | }
30 | }
--------------------------------------------------------------------------------
/src/Odoo/Client.php:
--------------------------------------------------------------------------------
1 | ripcordClient = Ripcord::client($url, $options, $transport);
19 | }
20 |
21 | public function __call($name, $arguments)
22 | {
23 | //Just Proxy
24 | return call_user_func_array([$this->ripcordClient, $name], $arguments);
25 | }
26 |
27 | public function response()
28 | {
29 | return $this->ripcordClient->_response;
30 | }
31 | }
--------------------------------------------------------------------------------
/src/Odoo/Response/VersionResponse.php:
--------------------------------------------------------------------------------
1 | rawResponse;
34 | }
35 | }
--------------------------------------------------------------------------------
/src/Odoo/Response/Response.php:
--------------------------------------------------------------------------------
1 | rawResponse = $raw;
18 | $this->apply($raw);
19 | }
20 |
21 | public abstract static function applies($raw): bool;
22 |
23 | public function apply($rawResponse)
24 | {
25 | $vars = get_object_vars($this);
26 | unset($vars['rawResponse']);
27 | foreach ($vars as $var => $value) {
28 | if(array_key_exists($var, $rawResponse)) {
29 | $this->{$var} = $rawResponse[$var];
30 | }
31 | }
32 | }
33 |
34 | public abstract function unwrap();
35 | }
36 |
--------------------------------------------------------------------------------
/src/Odoo/Request/ContextBuilder.php:
--------------------------------------------------------------------------------
1 | options[$key] = $value;
15 | return $this;
16 | }
17 |
18 | public function build()
19 | {
20 | return $this->options;
21 | }
22 |
23 | public function isEmpty()
24 | {
25 | return empty($this->options);
26 | }
27 |
28 | public function setLang($lang)
29 | {
30 | $this->set('lang', $lang);
31 | }
32 |
33 | public function setCompanyId($companyId)
34 | {
35 | $this->set('company_id', $companyId);
36 | }
37 |
38 | public function setOptions(array $args){
39 | foreach ($args as $key => $value) {
40 | $this->set($key, $value);
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "obuchmann/laravel-odoo-api",
3 | "description": "Laravel connector for Odoo",
4 | "keywords": [
5 | "odoo",
6 | "laravel",
7 | "package"
8 | ],
9 | "license": "MIT",
10 | "authors": [
11 | {
12 | "name": "Oliver Buchmann",
13 | "email": "obuchmanncode@gmail.com"
14 | }
15 | ],
16 | "autoload": {
17 | "psr-4": {
18 | "Obuchmann\\LaravelOdooApi\\": "src/"
19 | },
20 | "files": [
21 | "src/Support/helpers.php"
22 | ]
23 | },
24 | "minimum-stability": "stable",
25 | "require": {
26 | "php": "^7.4|^8.0",
27 | "illuminate/support": "~7|~8",
28 | "darkaonline/ripcord": "^0.1.7"
29 | },
30 | "require-dev": {
31 | "phpunit/phpunit": "~9.0"
32 | },
33 | "extra": {
34 | "laravel": {
35 | "providers": [
36 | "Obuchmann\\LaravelOdooApi\\Providers\\OdooServiceProvider"
37 | ],
38 | "aliases": {
39 | "Odoo": "Obuchmann\\LaravelOdooApi\\Facades\\Odoo"
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c)
4 |
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/src/Support/helpers.php:
--------------------------------------------------------------------------------
1 | client = $client;
29 | $this->responseFactory = $responseFactory;
30 | }
31 |
32 |
33 | public function newRequest(?Config $config = null)
34 | {
35 | $request = new RequestBuilder();
36 | $request->setClient($this->client);
37 | $request->setResponseFactory($this->responseFactory);
38 |
39 | if ($config) {
40 | $request
41 | ->setDatabase($config->getDatabase())
42 | ->setPassword($config->getPassword());
43 | }
44 | return $request;
45 | }
46 |
47 |
48 | }
--------------------------------------------------------------------------------
/docker/php8/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:8.0-cli
2 |
3 | ENV COMPOSER_HOME=/.composer
4 |
5 | # Composer
6 | RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --1
7 |
8 | # Composer cache
9 | RUN mkdir -p /.composer/cache && chmod -R 777 /.composer/cache
10 |
11 | RUN apt-get update && apt-get install -y --no-install-recommends \
12 | libxml2-dev \
13 | && apt-get clean && rm -rf /var/lib/apt/lists/*
14 |
15 | # TODO: Replace after release
16 | #RUN docker-php-ext-install xmlrpc
17 | RUN pecl install xmlrpc-1.0.0RC2; \
18 | docker-php-ext-enable xmlrpc;
19 |
20 | RUN pecl install xdebug; \
21 | docker-php-ext-enable xdebug;
22 | RUN echo "error_reporting = E_ALL" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; \
23 | echo "display_startup_errors = On" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; \
24 | echo "display_errors = On" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; \
25 | echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; \
26 | echo "client_host=172.17.0.1" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; \
27 | echo "xdebug.client_port=9000" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini;
--------------------------------------------------------------------------------
/src/Providers/OdooServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->runningInConsole()) {
19 | $this->bootForConsole();
20 | }
21 | }
22 |
23 | /**
24 | * Register the application services.
25 | *
26 | * @return void
27 | */
28 | public function register()
29 | {
30 | $this->mergeConfigFrom(__DIR__.'/../../config/laravel-odoo-api.php', 'laravel-odoo-api');
31 |
32 | $this->app->bind(Odoo::class, function () {
33 | return new Odoo($this->app['config']->get('laravel-odoo-api'));
34 | });
35 | }
36 |
37 | private function bootForConsole()
38 | {
39 | $this->publishes([
40 | __DIR__.'/../../config/laravel-odoo-api.php' => config_path('laravel-odoo-api.php'),
41 | ], 'laravel-odoo-api.config');
42 |
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Odoo/Request/QueryBuilder.php:
--------------------------------------------------------------------------------
1 | conditions = $conditions;
18 | }
19 |
20 |
21 | public function where(string $field, string $operator, $value)
22 | {
23 | $this->conditions[] = [$field, $operator, $value];
24 | return $this;
25 | }
26 |
27 |
28 | public function orWhere(string $field, string $operator, $value)
29 | {
30 | if ($this->isEmpty()) {
31 | throw new \RuntimeException("Or Term is not possible at start");
32 | }
33 | $this->conditions = array_merge(
34 | array_slice($this->conditions, 0, -1),
35 | ['|'],
36 | array_slice($this->conditions, -1, 1),
37 | [[$field, $operator, $value]]
38 | );
39 | return $this;
40 | }
41 |
42 | public function build(): array
43 | {
44 | return $this->conditions;
45 | }
46 |
47 | public function isEmpty(): bool
48 | {
49 | return empty($this->conditions);
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/src/Odoo/CommonEndpoint.php:
--------------------------------------------------------------------------------
1 | getClient()->version();
24 | return $this->getResponseFactory()->makeResponse($response, VersionResponse::class);
25 | }
26 |
27 | /**
28 | * @return int
29 | * @throws AuthenticationException
30 | */
31 | public function authenticate(): int
32 | {
33 | $client = $this->getClient(true);
34 | $uid = $client->authenticate(
35 | $this->getConfig()->getDatabase(),
36 | $this->getConfig()->getUsername(),
37 | $this->getConfig()->getPassword(),
38 | ['empty' => 'false'] // Context // Bug in v14 - it must not be empty
39 | );
40 | if ($uid > 0) {
41 | return $uid;
42 | }
43 |
44 | throw new AuthenticationException("Authentication failed!", 0, null, $client->response());
45 | }
46 | }
--------------------------------------------------------------------------------
/src/Odoo/Request/OptionsBuilder.php:
--------------------------------------------------------------------------------
1 | context = $context;
22 | } else {
23 | $this->context = new ContextBuilder();
24 | }
25 | }
26 |
27 |
28 | public function set($key, $value)
29 | {
30 | $this->options[$key] = $value;
31 | return $this;
32 | }
33 |
34 | public function build(?array $skipOptions = null)
35 | {
36 | $options = $this->options;
37 | if(!empty($skipOptions)){
38 | // Filter Skipped Options
39 | $options = collect($options)->filter(function($_, $key) use($skipOptions){
40 | return array_search($key, $skipOptions) === false;
41 | })->all();
42 | }
43 |
44 | if ($this->context->isEmpty()) {
45 | return $options;
46 | } else {
47 | return $options + ['context' => $this->context->build()];
48 | }
49 | }
50 |
51 | public function getContext(): ContextBuilder
52 | {
53 | return $this->context;
54 | }
55 |
56 | public function setContext(ContextBuilder $context): OptionsBuilder
57 | {
58 | $this->context = $context;
59 | return $this;
60 | }
61 |
62 |
63 | }
--------------------------------------------------------------------------------
/config/laravel-odoo-api.php:
--------------------------------------------------------------------------------
1 | env('LARAVEL_ODOO_API_HOST',''),
11 |
12 | /*
13 | |--------------------------------------------------------------------------
14 | | Database name
15 | |--------------------------------------------------------------------------
16 | */
17 | 'database' => env('LARAVEL_ODOO_API_DB_NAME',''),
18 |
19 | /*
20 | |--------------------------------------------------------------------------
21 | | User name
22 | |--------------------------------------------------------------------------
23 | */
24 | 'username' => env('LARAVEL_ODOO_API_USER_NAME', ''),
25 |
26 | /*
27 | |--------------------------------------------------------------------------
28 | | User password
29 | |--------------------------------------------------------------------------
30 | */
31 | 'password' => env('LARAVEL_ODOO_API_USER_PASSWORD', ''),
32 |
33 | /*
34 | |--------------------------------------------------------------------------
35 | | Encoding
36 | |--------------------------------------------------------------------------
37 | */
38 | 'encoding' => env('LARAVEL_ODOO_API_ENCODING',env('RIPCORD_ENCODING', 'utf-8')),
39 |
40 | /*
41 | |--------------------------------------------------------------------------
42 | | API Suffix
43 | | xmlrpc' from version 7.0 and earlier, 'xmlrpc/2' from version 8.0 and above.
44 | |--------------------------------------------------------------------------
45 | */
46 | 'api-suffix' => env('LARAVEL_ODOO_API_API_SUFFIX','xmlrpc/2'),
47 |
48 |
49 | ];
--------------------------------------------------------------------------------
/docker/php7.4/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:7.4-cli
2 |
3 | ENV COMPOSER_HOME=/.composer
4 |
5 | # Composer
6 | RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --1
7 |
8 | # Composer cache
9 | RUN mkdir -p /.composer/cache && chmod -R 777 /.composer/cache
10 |
11 |
12 | # Arguments defined in docker-compose.yml
13 | ARG USER_ID
14 | ARG GROUP_ID
15 |
16 | # Create system user to run Composer and Artisan Commands
17 | RUN if [ ${USER_ID:-0} -ne 0 ] && [ ${GROUP_ID:-0} -ne 0 ]; then \
18 | userdel -f www-data &&\
19 | if getent group www-data ; then groupdel www-data; fi &&\
20 | groupadd -g ${GROUP_ID} www-data &&\
21 | useradd -l -u ${USER_ID} -g www-data www-data &&\
22 | install -d -m 0755 -o www-data -g www-data /home/www-data &&\
23 | chown --changes --silent --no-dereference --recursive \
24 | --from=33:33 ${USER_ID}:${GROUP_ID} \
25 | /home/www-data \
26 | /.composer \
27 | ;fi
28 |
29 |
30 | # XMLRPC
31 | RUN apt-get update && apt-get install -y --no-install-recommends \
32 | libxml2-dev \
33 | && apt-get clean && rm -rf /var/lib/apt/lists/*
34 | RUN docker-php-ext-install -j$(nproc) xmlrpc
35 |
36 |
37 | RUN pecl install xdebug; \
38 | docker-php-ext-enable xdebug;
39 | RUN echo "error_reporting = E_ALL" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; \
40 | echo "display_startup_errors = On" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; \
41 | echo "display_errors = On" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; \
42 | echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; \
43 | echo "client_host=172.17.0.1" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; \
44 | echo "xdebug.client_port=9000" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini;
45 |
46 | # Set working directory
47 | WORKDIR /var/www
48 |
49 | USER www-data
50 |
--------------------------------------------------------------------------------
/src/Support/Proxy.php:
--------------------------------------------------------------------------------
1 | $method) {
18 | if (is_numeric($alias)) {
19 | $this->proxies[$prefix . $method] = [$class, $method];
20 | } else {
21 | $this->proxies[$prefix . $alias] = [$class, $method];
22 | }
23 | }
24 | }
25 |
26 | protected function guard($methods, $callable)
27 | {
28 | if(!is_array($methods)){
29 | $methods = [$methods];
30 | }
31 |
32 | foreach ($methods as $method) {
33 | if(!array_key_exists($method, $this->guards)){
34 | $this->guards[$method] = [];
35 | }
36 | $this->guards[$method][] = $callable;
37 | }
38 |
39 | }
40 |
41 | public function __call($name, $arguments)
42 | {
43 |
44 | // Run Guards
45 | if (array_key_exists($name, $this->guards) || array_key_exists(self::$FLUENT_PREFIX . $name, $this->guards) ) {
46 | foreach ($this->guards[$name] as $guard){
47 | if(false === call_user_func_array($guard, $arguments)){
48 | return null;
49 | }
50 | }
51 | }
52 |
53 | if (array_key_exists($name, $this->proxies)) {
54 | return call_user_func_array($this->proxies[$name], $arguments);
55 | }
56 | if (array_key_exists(self::$FLUENT_PREFIX . $name, $this->proxies)) {
57 | call_user_func_array($this->proxies[self::$FLUENT_PREFIX . $name], $arguments);
58 | return $this;
59 | }
60 | throw new \Exception("Invalid Method $name called");
61 | }
62 |
63 | }
--------------------------------------------------------------------------------
/src/Odoo/ResponseFactory.php:
--------------------------------------------------------------------------------
1 | isSubclassOf(Response::class)){
51 | throw new OdooException("Invalid Response Class given!");
52 | }
53 | return $class->newInstance($rawResponse);
54 | } catch (\ReflectionException $e) {
55 | // Its not possible to use this type -> its ok
56 | }
57 | }
58 | }
59 | throw new OdooException("No suitable Response Class given!");
60 | }
61 | }
--------------------------------------------------------------------------------
/src/Odoo/Endpoint.php:
--------------------------------------------------------------------------------
1 | configFactory = $configFactory;
58 | $this->name = $name;
59 | }
60 |
61 | protected function getConfig(): Config
62 | {
63 | // Delay Config init
64 | if (null == $this->config) {
65 | $this->config = $this->configFactory->build();
66 | }
67 | return $this->config;
68 | }
69 |
70 | protected function getClient($forceNew = false): Client
71 | {
72 | if ($forceNew || null == $this->client) {
73 | $config = $this->getConfig();
74 | $this->url = $config->getHost() . $config->getSuffix() . $this->name;
75 | $this->client = new Client($this->url, [
76 | 'encoding' => $config->getEncoding()
77 | ]);
78 | }
79 | return $this->client;
80 | }
81 |
82 | protected function getResponseFactory(): ResponseFactory
83 | {
84 | return new ResponseFactory();
85 | }
86 |
87 | protected function getRequestFactory($forceNew = false): RequestFactory {
88 | if($forceNew || null == $this->requestFactory){
89 | $this->requestFactory = new RequestFactory($this->getClient(), $this->getResponseFactory());
90 | }
91 | return $this->requestFactory;
92 | }
93 |
94 | }
--------------------------------------------------------------------------------
/src/Odoo/ConfigFactory.php:
--------------------------------------------------------------------------------
1 | config = $config;
25 | }
26 |
27 | /**
28 | * @return Config
29 | * @throws ConfigurationException
30 | */
31 | public function build(): Config
32 | {
33 | return new Config(
34 | $this->getRequired('database'),
35 | $this->getRequired('host'),
36 | $this->getRequired('username'),
37 | $this->getRequired('password'),
38 | laravelOdooApiAddCharacter(data_get($this->config, 'suffix', '/xmlrpc/2'), '/'),
39 | laravelOdooApiRemoveCharacter(data_get($this->config, 'encoding', 'utf-8'), '/')
40 | );
41 | }
42 |
43 | private function getRequired(string $key)
44 | {
45 | if(!array_key_exists($key, $this->config)){
46 | throw new ConfigurationException("Missing required config $key");
47 | }
48 | return $this->config[$key];
49 | }
50 |
51 | /**
52 | * @param mixed $host
53 | */
54 | public function setHost($host): void
55 | {
56 | $this->config['host'] = $host;
57 | }
58 |
59 | /**
60 | * @param mixed $database
61 | */
62 | public function setDatabase($database): void
63 | {
64 | $this->config['database'] = $database;
65 | }
66 |
67 | /**
68 | * @param mixed $username
69 | */
70 | public function setUsername($username): void
71 | {
72 | $this->config['username'] = $username;
73 | }
74 |
75 | /**
76 | * @param mixed $password
77 | */
78 | public function setPassword($password): void
79 | {
80 | $this->config['password'] = $password;
81 | }
82 |
83 | /**
84 | * @param mixed $suffix
85 | */
86 | public function setSuffix($suffix): void
87 | {
88 | $this->config['suffix'] = $suffix;
89 | }
90 |
91 | /**
92 | * @param mixed $encoding
93 | */
94 | public function setEncoding($encoding): void
95 | {
96 | $this->config['encoding'] = $encoding;
97 | }
98 |
99 |
100 |
101 | }
--------------------------------------------------------------------------------
/src/Odoo/Config.php:
--------------------------------------------------------------------------------
1 | database = $database;
66 | $this->host = $host;
67 | $this->username = $username;
68 | $this->password = $password;
69 | $this->suffix = $suffix;
70 | $this->encoding = $encoding;
71 | }
72 |
73 | /**
74 | * @return string
75 | */
76 | public function getDatabase(): string
77 | {
78 | return $this->database;
79 | }
80 |
81 | /**
82 | * @return string
83 | */
84 | public function getHost(): string
85 | {
86 | return $this->host;
87 | }
88 |
89 | /**
90 | * @return string
91 | */
92 | public function getUsername(): string
93 | {
94 | return $this->username;
95 | }
96 |
97 | /**
98 | * @return string
99 | */
100 | public function getPassword(): string
101 | {
102 | return $this->password;
103 | }
104 |
105 | /**
106 | * @return string
107 | */
108 | public function getSuffix(): string
109 | {
110 | return $this->suffix;
111 | }
112 |
113 | /**
114 | * @return string
115 | */
116 | public function getEncoding(): string
117 | {
118 | return $this->encoding;
119 | }
120 |
121 | }
--------------------------------------------------------------------------------
/src/Odoo/Request/Request.php:
--------------------------------------------------------------------------------
1 | client = $client;
56 | $this->responseFactory = $responseFactory;
57 | $this->database = $database;
58 | $this->uid = $uid;
59 | $this->password = $password;
60 | $this->model = $model;
61 | $this->method = $method;
62 | $this->arguments = $arguments;
63 | $this->options = $options;
64 | $this->responseClasses = $responseClasses;
65 | }
66 |
67 |
68 | public function toArray()
69 | {
70 | return [
71 | $this->database, $this->uid, $this->password,
72 | $this->model,
73 | $this->method,
74 | $this->arguments,
75 | $this->options
76 | ];
77 | }
78 |
79 | /**
80 | * @return Response
81 | * @throws \Obuchmann\LaravelOdooApi\Exceptions\OdooException
82 | */
83 | public function getResponse(): Response
84 | {
85 | $response = call_user_func([$this->client, 'execute_kw'],
86 | $this->database, $this->uid, $this->password,
87 | $this->model, $this->method,
88 | $this->arguments,
89 | $this->options
90 | );
91 |
92 | return $this->responseFactory->makeResponse($response, $this->responseClasses);
93 | }
94 |
95 | /**
96 | * @return mixed
97 | * @throws \Obuchmann\LaravelOdooApi\Exceptions\OdooException
98 | */
99 | public function get()
100 | {
101 | return $this->getResponse()->unwrap();
102 | }
103 |
104 | }
--------------------------------------------------------------------------------
/src/Odoo/ObjectEndpoint.php:
--------------------------------------------------------------------------------
1 | uid = $uid;
27 | }
28 |
29 | public function getContext()
30 | {
31 | return $this->contextBuilder;
32 | }
33 |
34 | /**
35 | * @return int|null
36 | */
37 | public function getUid(): ?int
38 | {
39 | return $this->uid;
40 | }
41 |
42 |
43 | public function __construct(ConfigFactory $configFactory)
44 | {
45 | parent::__construct($configFactory, Endpoint::OBJECT_ENDPOINT_NAME);
46 | $this->contextBuilder = new ContextBuilder();
47 | }
48 |
49 | public function newRequest()
50 | {
51 | $requets = $this->getRequestFactory()
52 | ->newRequest($this->getConfig())
53 | ->setUid($this->uid)
54 | ->setContext($this->getContext());
55 |
56 | return $requets;
57 | }
58 |
59 |
60 | public function can($permission, $model, $withExceptions = false)
61 | {
62 | $request = $this->newRequest()
63 | ->setModel($model)
64 | ->setMethod('check_access_rights')
65 | ->setArguments([$permission])
66 | ->setOption('raise_exception', $withExceptions)
67 | ->addResponseClass(BooleanResponse::class)
68 | ->build();
69 |
70 | return $request->get();
71 | }
72 |
73 | #region Shorthand Init Methods
74 | public function where($field, $operator, $value)
75 | {
76 | return $this->newRequest()
77 | ->where($field, $operator, $value);
78 | }
79 |
80 | public function limit($limit, $offset = null)
81 | {
82 | return $this->newRequest()
83 | ->limit($limit, $offset);
84 | }
85 |
86 | public function fields($fields)
87 | {
88 | $fields = is_array($fields) ? $fields : func_get_args();
89 | return $this->newRequest()
90 | ->fields($fields);
91 | }
92 |
93 | public function model($model)
94 | {
95 | return $this->newRequest()
96 | ->model($model);
97 | }
98 |
99 | /**
100 | * @param $model
101 | * @return mixed
102 | * @throws \Obuchmann\LaravelOdooApi\Exceptions\OdooException
103 | */
104 | // public function count($model)
105 | // {
106 | // return $this->newRequest()
107 | // ->model($model)
108 | // ->count();
109 | // }
110 |
111 | #endregion
112 |
113 | }
--------------------------------------------------------------------------------
/src/Odoo.php:
--------------------------------------------------------------------------------
1 | loadConfigData($config);
67 | }
68 |
69 | /**
70 | * Load data from config file.
71 | * @param array $config
72 | * @throws Exceptions\ConfigurationException
73 | */
74 | protected function loadConfigData(array $config = [])
75 | {
76 | $this->configFactory = new ConfigFactory($config);
77 |
78 | $this->proxy($this->configFactory, [
79 | 'host' => 'setHost',
80 | 'database' => 'setDatabase',
81 | 'username' => 'setUsername',
82 | 'password' => 'setPassword',
83 | 'apiSuffix' => 'setSuffix',
84 | 'encoding' => 'setEncoding',
85 | 'uid' => 'setUid'
86 | ], true);
87 |
88 | $this->proxy(new CommonEndpoint($this->configFactory), [
89 | 'version',
90 | 'authenticate',
91 | ]);
92 |
93 | $this->objectEndpoint = new ObjectEndpoint($this->configFactory);
94 |
95 | $this->proxy($this->objectEndpoint,[
96 | 'getUid',
97 | 'newRequest',
98 | 'can',
99 | 'model',
100 | 'where',
101 | 'limit',
102 | 'fields',
103 | ]);
104 |
105 | $this->proxy($this->objectEndpoint->getContext(),[
106 | 'lang' => 'setLang',
107 | 'companyId' => 'setCompanyId',
108 | 'updateContext' => 'setOptions'
109 | ], true);
110 |
111 | $this->guard([
112 | 'can',
113 | 'model',
114 | 'where',
115 | 'limit',
116 | 'fields',
117 | ],[$this, 'connect']);
118 | }
119 |
120 | /**
121 | * Tries to connect to Odoo
122 | * @return $this
123 | */
124 | public function connect()
125 | {
126 | if(!$this->getUid()){
127 | $this->forceConnect();
128 | }
129 |
130 | return $this;
131 | }
132 |
133 | /**
134 | * Forces new Login
135 | * @return $this
136 | */
137 | public function forceConnect()
138 | {
139 | $uid = $this->authenticate();
140 | $this->objectEndpoint->setUid($uid);
141 |
142 | return $this;
143 | }
144 | }
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # Laravel Odoo Api
2 |
3 | This is a medium level API to Odoo (former OpenERP) XMLRPC-API for Laravel. [Odoo website](https://www.odoo.com)
4 |
5 | This package is a successor of [Laradoo](https://github.com/Edujugon/laradoo), but there is no backwards compatibility!
6 |
7 | :warning: **This Package is not Maintained any more. Successor is [Odoo Jsonrpc](https://packagist.org/packages/obuchmann/odoo-jsonrpc)**
8 |
9 |
10 |
11 | ## Compatibility
12 |
13 | Laravel 7 and higher
14 |
15 | Odoo 8.0 and higher
16 |
17 | Php 7.4 and higher
18 |
19 | ## Installation
20 |
21 | This package is installed via [Composer](https://getcomposer.org/). To install, run the following command.
22 |
23 | ```shel
24 | composer require obuchmann/laravel-odoo-api
25 | ```
26 |
27 | Publish the package's configuration file to the application's own config directory
28 |
29 | ```php
30 | php artisan vendor:publish --provider="Obuchmann\LaravelOdooApi\Providers\OdooServiceProvider" --tag="config"
31 | ```
32 |
33 | ###
34 |
35 | This package supports autodiscover.
36 |
37 | If you don't use autodiscover for reasons, you can add the provider as described below.
38 |
39 | Register Laravel Odoo Api service by adding it to the providers array.
40 |
41 | ```php
42 | 'providers' => array(
43 | ...
44 | Obuchmann\LaravelOdooApi\Providers\OdooServiceProvider::class
45 | )
46 | ```
47 |
48 | You can also add the Alias facade.
49 | ```php
50 | 'aliases' => array(
51 | ...
52 | 'Odoo' => Obuchmann\LaravelOdooApi\Facades\Odoo::class,
53 | )
54 | ```
55 |
56 | ### Configuration
57 |
58 | After publishing the package config file, the base configuration for laravel-odoo-api package is located in config/laravel-odoo-api.php
59 |
60 |
61 | Also, you can dynamically update those values calling the available setter methods:
62 |
63 | `host($url)`, `username($username)`, `password($password)`, `database($name)`, `apiSuffix($name)`
64 |
65 |
66 | ## Usage samples
67 |
68 | Instance the main Odoo class:
69 |
70 | ```php
71 | $odoo = new \Obuchmann\LaravelOdooApi\Odoo();
72 | ```
73 | You can get the Odoo API version just calling the version method:
74 |
75 | ```php
76 | $version = $odoo->version();
77 | ```
78 | > This methods doesn't require to be connected/Logged into the ERP.
79 |
80 | Connect and log into the ERP:
81 |
82 | ```php
83 | $odoo = $odoo->connect();
84 | ```
85 |
86 | All needed configuration data is taken from `laravel-odoo-api.php` config file. But you always may pass new values on the fly if required.
87 |
88 | ```php
89 | $this->odoo = $this->odoo
90 | ->username('my-user-name')
91 | ->password('my-password')
92 | ->database('my-db')
93 | ->host('https://my-host.com')
94 | ->connect();
95 | ```
96 | > // Note: `host` should contain 'http://' or 'https://'
97 |
98 | After login, you can check the user identifier like follows:
99 |
100 | ```php
101 | $userId = $this->odoo->getUid();
102 | ```
103 |
104 | You always can check the permission on a specific model:
105 |
106 | ```php
107 | $can = $odoo->can('read', 'res.partner');
108 | ```
109 | > Permissions which can be checked: 'read','write','create','unlink'
110 |
111 | Method `search provides a collection of ids based on your conditions:
112 |
113 | ```php
114 | $ids = $odoo
115 | ->model('res.partner')
116 | ->where('customer', '=', true)
117 | ->search();
118 | ```
119 |
120 | You can limit the amount of data using `limit` method and use as many as condition you need:
121 |
122 | ```php
123 | $ids = $odoo
124 | ->model('res.partner')
125 | ->where('is_company', true)
126 | ->where('customer', '=', true)
127 | ->limit(3)
128 | ->search();
129 | ```
130 |
131 | If need to get a list of models, use the `get` method:
132 |
133 | ```php
134 | $models = $odoo
135 | ->model('res.partner')
136 | ->where('customer', true)
137 | ->limit(3)
138 | ->get();
139 | ```
140 |
141 | Instead of retrieving all properties of the models, you can reduce it by adding `fields` method before the method `get`
142 |
143 | ```php
144 | $models = $odoo
145 | ->model('res.partner')
146 | ->where('customer', true)
147 | ->limit(3)
148 | ->fields(['name'])
149 | ->get();
150 | ```
151 |
152 | If not sure about what fields a model has, you can retrieve the model structure data by calling `fieldsOf` method:
153 |
154 | ```php
155 | $structure = $odoo
156 | ->model('res.partner')
157 | ->listModelFields();
158 | ```
159 |
160 | Till now we have only retrieved data from Odoo but you can also Create and Delete records.
161 |
162 | In order to create a new record just call `create` method as follows:
163 |
164 | ```php
165 | $id = $odoo
166 | ->model('res.partner')
167 | ->create(['name' => 'Bobby Brown']);
168 | ```
169 | > The method returns the id of the new record.
170 |
171 | For Deleting records we have the `delete` method:
172 |
173 | ```php
174 | $result = $odoo
175 | ->model('res.partner')
176 | ->where('name', '=', 'Bobby Brown')
177 | ->delete();
178 | ```
179 | > Notice that before calling `delete` method you have to use `where`.
180 |
181 | You can also remove records by ids like follows:
182 |
183 | ```php
184 | $result = $odoo
185 | ->model('res.partner')
186 | ->deleteById($ids);
187 | ```
188 |
189 | Update any record of your Odoo:
190 |
191 | ```php
192 | $updateSuccessfull = $odoo
193 | ->model('res.partner')
194 | ->where('name', '=', 'Bobby Brown')
195 | ->update(['name' => 'Dagobert Duck','email' => 'daduck@odoo.com']);
196 | ```
197 |
198 | Notice that all `delete` and `update` methods always returns `true` except if there was an error.
199 |
200 | Custom api Calls are also supported
201 |
202 | ```php
203 | $ids = $odoo
204 | ->model('res.partner')
205 | ->setMethod('search')
206 | ->setArguments([[
207 | ['is_company', '=', true]
208 | ]])
209 | ->setOption('limit', 3)
210 | ->addResponseClass(Odoo\Response\ListResponse::class)
211 | ->get();
212 |
213 | ```
214 |
--------------------------------------------------------------------------------
/src/Odoo/Request/RequestBuilder.php:
--------------------------------------------------------------------------------
1 | responseClasses = [FaultCodeResponse::class];
64 | $this->queryBuilder = new QueryBuilder();
65 | $this->options = new OptionsBuilder();
66 | }
67 |
68 |
69 | public function build(?array $skipOptions = null)
70 | {
71 | if(empty($this->model)){
72 | throw new OdooException("Model not set!");
73 | }
74 | return new Request(
75 | $this->client,
76 | $this->responseFactory,
77 | $this->database,
78 | $this->uid,
79 | $this->password,
80 | $this->model,
81 | $this->method,
82 | $this->getArguments(),
83 | $this->options->build($skipOptions),
84 | $this->responseClasses
85 | );
86 | }
87 |
88 |
89 | public function setDatabase($database)
90 | {
91 | $this->database = $database;
92 | return $this;
93 | }
94 |
95 | public function setPassword($password)
96 | {
97 | $this->password = $password;
98 | return $this;
99 | }
100 |
101 | public function setModel($model)
102 | {
103 | $this->model = $model;
104 | return $this;
105 | }
106 |
107 | public function setMethod($method)
108 | {
109 | $this->method = $method;
110 | return $this;
111 | }
112 |
113 | public function addArgument($argument)
114 | {
115 | $this->arguments[] = $argument;
116 | return $this;
117 | }
118 |
119 | public function setArguments($arguments)
120 | {
121 | $this->arguments = $arguments;
122 | return $this;
123 | }
124 |
125 | public function setOptions(OptionsBuilder $options)
126 | {
127 | $this->options = $options;
128 | return $this;
129 | }
130 |
131 | public function setOption($key, $value)
132 | {
133 | $this->options->set($key, $value);
134 | return $this;
135 | }
136 |
137 | public function setUid(int $uid)
138 | {
139 | $this->uid = $uid;
140 | return $this;
141 | }
142 |
143 | /**
144 | * @param Client $client
145 | * @return RequestBuilder
146 | */
147 | public function setClient(Client $client)
148 | {
149 | $this->client = $client;
150 | return $this;
151 | }
152 |
153 | /**
154 | * @param ResponseFactory $responseFactory
155 | * @return RequestBuilder
156 | */
157 | public function setResponseFactory(ResponseFactory $responseFactory)
158 | {
159 | $this->responseFactory = $responseFactory;
160 | return $this;
161 | }
162 |
163 | /**
164 | * @param array $responseClasses
165 | * @return RequestBuilder
166 | */
167 | public function setResponseClasses(array $responseClasses)
168 | {
169 | $this->responseClasses = $responseClasses;
170 | return $this;
171 | }
172 |
173 | /**
174 | * @param $class
175 | * @return $this
176 | */
177 | public function addResponseClass($class, $pushTop = false)
178 | {
179 | if ($pushTop) {
180 | array_unshift($this->responseClasses, $class);
181 | } else {
182 | array_push($this->responseClasses, $class);
183 | }
184 |
185 | return $this;
186 | }
187 |
188 |
189 | public function getArguments()
190 | {
191 | return $this->arguments;
192 | }
193 |
194 | /**
195 | * @return OptionsBuilder
196 | */
197 | public function getOptions(): OptionsBuilder
198 | {
199 | return $this->options;
200 | }
201 |
202 | /**
203 | * @return ContextBuilder
204 | */
205 | public function getContext(): ContextBuilder
206 | {
207 | return $this->options->getContext();
208 | }
209 |
210 | public function setContext(ContextBuilder $contextBuilder)
211 | {
212 | $this->getOptions()->setContext($contextBuilder);
213 | return $this;
214 | }
215 |
216 |
217 | #region Query Shorthands
218 |
219 | /**
220 | * @param $field
221 | * @param $operator
222 | * @param $value
223 | * @return $this
224 | */
225 | public function where($field, $operator, $value)
226 | {
227 | $this->queryBuilder->where($field, $operator, $value);
228 | return $this;
229 | }
230 | /**
231 | * @param $field
232 | * @param $operator
233 | * @param $value
234 | * @return $this
235 | */
236 | public function orWhere($field, $operator, $value)
237 | {
238 | $this->queryBuilder->orWhere($field, $operator, $value);
239 | return $this;
240 | }
241 |
242 | public function setConditions(array $conditions)
243 | {
244 | return $this->setQueryBuilder(new QueryBuilder($conditions));
245 | }
246 |
247 | public function setQueryBuilder(QueryBuilder $queryBuilder)
248 | {
249 | $this->queryBuilder = $queryBuilder;
250 | return $this;
251 | }
252 |
253 | public function limit($limit, $offset = null)
254 | {
255 | $this->setOption('limit', $limit);
256 | if($offset != null) {
257 | $this->setOption('offset', $offset);
258 | }
259 | return $this;
260 | }
261 | public function offset($offset)
262 | {
263 | $this->setOption('offset', $offset);
264 | return $this;
265 | }
266 |
267 | public function orderBy($field, $direction = "")
268 | {
269 | if(!empty($direction)){
270 | $field.= " ".$direction;
271 | }
272 | $this->setOption('order', $field);
273 | return $this;
274 | }
275 |
276 | public function fields($fields)
277 | {
278 | $fields = is_array($fields) ? $fields : func_get_args();
279 | $this->setOption('fields', $fields);
280 | return $this;
281 | }
282 |
283 | public function model($model)
284 | {
285 | $this->setModel($model);
286 | return $this;
287 | }
288 |
289 | #endregion
290 |
291 | #region Method shorthands
292 |
293 | /**
294 | * @param null $model
295 | * @return mixed
296 | * @throws \Obuchmann\LaravelOdooApi\Exceptions\OdooException
297 | */
298 | public function count()
299 | {
300 | $this->method = 'search_count';
301 | $this->addResponseClass(NumericResponse::class);
302 |
303 | $this->addArgument($this->queryBuilder->build());
304 |
305 | $request = $this->build();
306 |
307 | return $request->get();
308 | }
309 |
310 | /**
311 | * @return mixed
312 | * @throws OdooException
313 | */
314 | public function search()
315 | {
316 | $this->method = 'search';
317 | $this->addResponseClass(EmptyListResponse::class, true);
318 | $this->addResponseClass(ListResponse::class);
319 |
320 | $this->addArgument($this->queryBuilder->build());
321 |
322 | $request = $this->build(['fields']);
323 |
324 | return $request->get();
325 | }
326 |
327 | /**
328 | * @param $ids array|Collection
329 | * @return mixed
330 | * @throws \Obuchmann\LaravelOdooApi\Exceptions\OdooException
331 | */
332 | public function read($ids)
333 | {
334 | $ids = $this->extractIds($ids);
335 |
336 | $this->method = 'read';
337 | $this->addResponseClass(EmptyListResponse::class, true);
338 | $this->addResponseClass(ListResponse::class);
339 | $this->arguments = [$ids];
340 |
341 | $request = $this->build();
342 |
343 | return $request->get();
344 | }
345 |
346 | public function readOne($id)
347 | {
348 | return $this->read($id)->first();
349 | }
350 |
351 | public function get()
352 | {
353 | $this->method = 'search_read';
354 | $this->addResponseClass(EmptyListResponse::class, true);
355 | $this->addResponseClass(ListResponse::class);
356 |
357 | $this->addArgument($this->queryBuilder->build());
358 |
359 | $request = $this->build();
360 |
361 | return $request->get();
362 | }
363 |
364 | public function first()
365 | {
366 | return $this->limit(1)->get()->first();
367 | }
368 |
369 | public function listModelFields($attributes = ['string', 'help', 'type'])
370 | {
371 | $this->method = 'fields_get';
372 | $this->addResponseClass(ListResponse::class);
373 |
374 | $this->options->set('attributes', $attributes);
375 |
376 | $request = $this->build();
377 |
378 | return $request->get();
379 | }
380 |
381 | public function create($attributes)
382 | {
383 | $this->method = 'create';
384 | $this->addResponseClass(NumericResponse::class);
385 |
386 | $this->setArguments([$attributes]);
387 |
388 | $request = $this->build();
389 |
390 | return $request->get();
391 | }
392 |
393 | public function deleteById($ids)
394 | {
395 | if(empty($ids)){
396 | return null;
397 | }
398 | $ids = $this->extractIds($ids);
399 |
400 | $this->method = 'unlink';
401 | $this->addResponseClass(BooleanResponse::class);
402 |
403 | $this->setArguments([$ids]);
404 |
405 | $request = $this->build();
406 |
407 | return $request->get();
408 |
409 | }
410 |
411 | /**
412 | * @param bool $force
413 | * @return mixed
414 | * @throws OdooException
415 | */
416 | public function delete($force = false)
417 | {
418 | if(!$force && $this->queryBuilder->isEmpty()){
419 | throw new OdooException("You are gonna delete all records of ".$this->model. "! This is only possible with 'force' flag.");
420 | }
421 | $ids = $this->search();
422 |
423 | return $this->deleteById($ids);
424 | }
425 |
426 | public function updateById($ids, $attributes)
427 | {
428 | $ids = $this->extractIds($ids);
429 |
430 | $this->method = 'write';
431 |
432 | $this->addResponseClass(BooleanResponse::class);
433 |
434 | $this->setArguments([$ids, $attributes]);
435 |
436 | $request = $this->build(['fields']);
437 |
438 | return $request->get();
439 | }
440 |
441 | public function update($attributes, $force = false)
442 | {
443 | if(!$force && $this->queryBuilder->isEmpty()){
444 | throw new OdooException("You are gonna update all records of ".$this->model. "! This is only possible with 'force' flag.");
445 | }
446 |
447 | $ids = $this->search();
448 |
449 | return $this->updateById($ids, $attributes);
450 |
451 | }
452 |
453 | #endregion
454 |
455 | private function extractIds($ids)
456 | {
457 | if ($ids instanceof Collection) {
458 | $ids = $ids->all();
459 | }
460 | if (!is_array($ids)) {
461 | $ids = [$ids];
462 | }
463 | if (!is_array($ids)) {
464 | throw new OdooException("Invalid type given for ids");
465 | }
466 | return $ids;
467 | }
468 |
469 | }
--------------------------------------------------------------------------------