├── CHANGELOG.md ├── LICENSE.txt ├── Readme.md ├── composer.json ├── config └── laravel-odoo-api.php ├── docker-compose.yml ├── docker ├── php7.4 │ └── Dockerfile └── php8 │ └── Dockerfile ├── phpunit.xml ├── src ├── Exceptions │ ├── AuthenticationException.php │ ├── ConfigurationException.php │ └── OdooException.php ├── Facades │ └── Odoo.php ├── Odoo.php ├── Odoo │ ├── Client.php │ ├── CommonEndpoint.php │ ├── Config.php │ ├── ConfigFactory.php │ ├── Endpoint.php │ ├── ObjectEndpoint.php │ ├── Request │ │ ├── ContextBuilder.php │ │ ├── OptionsBuilder.php │ │ ├── QueryBuilder.php │ │ ├── Request.php │ │ └── RequestBuilder.php │ ├── RequestFactory.php │ ├── Response │ │ ├── BooleanResponse.php │ │ ├── EmptyListResponse.php │ │ ├── EmptyResponse.php │ │ ├── FaultCodeResponse.php │ │ ├── ListResponse.php │ │ ├── NumericResponse.php │ │ ├── ObjectResponse.php │ │ ├── Response.php │ │ ├── ScalarResponse.php │ │ └── VersionResponse.php │ └── ResponseFactory.php ├── Providers │ └── OdooServiceProvider.php └── Support │ ├── Proxy.php │ └── helpers.php └── test.sh /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | ## V0.1.0 4 | 5 | Write a Laravel Connector for Odoo based on Laradoo 6 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests/ 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Exceptions/AuthenticationException.php: -------------------------------------------------------------------------------- 1 | response = $response; 16 | } 17 | 18 | 19 | } -------------------------------------------------------------------------------- /src/Facades/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 | } -------------------------------------------------------------------------------- /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/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/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/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/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/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/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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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/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/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 | } -------------------------------------------------------------------------------- /src/Odoo/RequestFactory.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 | } -------------------------------------------------------------------------------- /src/Odoo/Response/BooleanResponse.php: -------------------------------------------------------------------------------- 1 | faultString, $this->faultCode, null, $this->rawResponse); 29 | } 30 | } -------------------------------------------------------------------------------- /src/Odoo/Response/ListResponse.php: -------------------------------------------------------------------------------- 1 | value); 17 | } 18 | } -------------------------------------------------------------------------------- /src/Odoo/Response/NumericResponse.php: -------------------------------------------------------------------------------- 1 | value; 26 | } 27 | 28 | 29 | } -------------------------------------------------------------------------------- /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/Response/ScalarResponse.php: -------------------------------------------------------------------------------- 1 | value = $raw; 20 | } 21 | 22 | public function unwrap() 23 | { 24 | return $this->rawResponse; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Odoo/Response/VersionResponse.php: -------------------------------------------------------------------------------- 1 | rawResponse; 34 | } 35 | } -------------------------------------------------------------------------------- /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/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/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/Support/helpers.php: -------------------------------------------------------------------------------- 1 |