diff --git a/openfoodfacts-php-0.2.3/.gitignore b/openfoodfacts-php-0.2.3/.gitignore
new file mode 100644
index 0000000..90424b8
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/.gitignore
@@ -0,0 +1,18 @@
+# Created by .ignore support plugin (hsz.mobi)
+### Example user template template
+### Example user template
+/build/
+
+# IntelliJ project files
+.idea
+*.iml
+out
+gen### Composer template
+composer.phar
+/vendor/
+/log/
+/tests/tmp/*
+
+# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
+# You may choose to ignore a library lock file https://getcomposer.org/doc/02-libraries.md#lock-file
+composer.lock
diff --git a/openfoodfacts-php-0.2.3/.travis.yml b/openfoodfacts-php-0.2.3/.travis.yml
new file mode 100644
index 0000000..6b6c613
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/.travis.yml
@@ -0,0 +1,5 @@
+language: php
+php:
+ - '7.2'
+before_script: composer install --dev
+script: vendor/bin/phpunit
diff --git a/openfoodfacts-php-0.2.3/CHANGELOG.md b/openfoodfacts-php-0.2.3/CHANGELOG.md
new file mode 100644
index 0000000..2156042
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.0.1 (June 19, 2016) ##
+
+* Upload project struct
\ No newline at end of file
diff --git a/openfoodfacts-php-0.2.3/CONTRIBUTING.md b/openfoodfacts-php-0.2.3/CONTRIBUTING.md
new file mode 100644
index 0000000..f7cbf08
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/CONTRIBUTING.md
@@ -0,0 +1,7 @@
+## Contributing
+
+1. Fork it ( https://github.com/openfoodfacts/openfoodfacts-php/fork )
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Commit your changes (`git commit -am 'Add some feature'`)
+4. Push to the branch (`git push origin my-new-feature`)
+5. Create a new Pull Request
\ No newline at end of file
diff --git a/openfoodfacts-php-0.2.3/LICENSE b/openfoodfacts-php-0.2.3/LICENSE
new file mode 100644
index 0000000..09ada78
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Open Food Facts
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/openfoodfacts-php-0.2.3/README.md b/openfoodfacts-php-0.2.3/README.md
new file mode 100644
index 0000000..9599159
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/README.md
@@ -0,0 +1,78 @@
+# openfoodfacts-php
+![Open Food Facts](https://static.openfoodfacts.org/images/misc/openfoodfacts-logo-en-178x150.png)
+
+PHP API Wrapper for [Open Food Facts](https://openfoodfacts.org/), the open database about food.
+
+[![Project Status](http://opensource.box.com/badges/active.svg)](http://opensource.box.com/badges)
+[![Build Status](https://travis-ci.org/openfoodfacts/openfoodfacts-php.svg?branch=master)](https://travis-ci.org/openfoodfacts/openfoodfacts-php)
+[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/openfoodfacts/openfoodfacts-php.svg)](https://isitmaintained.com/project/openfoodfacts/openfoodfacts-php "Average time to resolve an issue")
+[![Percentage of issues still open](https://isitmaintained.com/badge/open/openfoodfacts/openfoodfacts-php.svg)](https://isitmaintained.com/project/openfoodfacts/openfoodfacts-php "Percentage of issues still open")
+
+## Installation
+
+With Composer:
+
+```bash
+composer require openfoodfacts/openfoodfacts-php
+```
+
+## Usage
+This is the most basic way of creating the API:
+```php
+$api = new OpenFoodFacts\Api('food','fr');
+$product = $api->getProduct('3057640385148');
+```
+In the example above you access the "food" database, limited to the French language/country scope.
+The first parameter is either
+ - "food"
+ - "beauty" or
+ - "pet"
+
+to decide which product database you want to use.
+
+The second parameter decides the language/country scope of the chosen database: f.e. "world" or "de" or "fr".
+
+For more details on this topic: see the [API Documentation](https://en.wiki.openfoodfacts.org/API/Read#Countries_and_Language_of_the_Response)
+
+These are all the parameters you really need for basic usage.
+
+As return types for ```$api->getProduct``` you get an ```Document::class``` Object.
+This may also be an Object of Type ```FoodProduct::class```,```PetProduct::class```, ```BeautyProduct::class``` depending on which API you are creating.
+These objects inherit from the more generic ```Document::class```
+
+In the example above, we use the 'food' API and there will get a ```FoodProduct::class```
+
+For getting a first overview the ```Document::class``` has a function to return an array representation(sorted) for a first start.
+```php
+$product = $api->getProduct('3057640385148');
+$productDataAsArray = $product->getData();
+```
+
+
+#### Optional Parameters
+The other parameters are optional and for a more sophisticated use of the api (from a software development point of view):
+
+An example in code is found here: [cached_example.php](examples/01-basic_api_usage/cached_example.php)
+
+LoggerInterface: A logger which decieds where to log errors to (file, console , etc)
+
+see: [PSR-3 Loggerinterface](https://www.php-fig.org/psr/psr-3/)
+
+ClientInterface: The HTTP Client - to adjust the connection configs to your needs and more
+
+see: [Guzzle HTTP Client](https://packagist.org/packages/guzzlehttp/guzzle)
+
+CacheInterface: To temporarily save the results of API request to improve the performance and to reduce the load on the API- Server
+
+see: [PSR-16 Simple Cache](https://www.php-fig.org/psr/psr-16/)
+
+## Development
+
+
+### Contributing
+
+1. Fork it ( https://github.com/openfoodfacts/openfoodfacts-php/fork )
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Commit your changes (`git commit -am 'Add some feature'`)
+4. Push to the branch (`git push origin my-new-feature`)
+5. Create a new Pull Request
diff --git a/openfoodfacts-php-0.2.3/composer.json b/openfoodfacts-php-0.2.3/composer.json
new file mode 100644
index 0000000..17c8f8e
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/composer.json
@@ -0,0 +1,41 @@
+{
+ "name": "openfoodfacts/openfoodfacts-php",
+ "description": "Open Food Facts API Wrapper, the open database about food.",
+ "homepage": "https://world.openfoodfacts.org/",
+ "type": "library",
+ "minimum-stability": "stable",
+ "license": "MIT",
+ "support": {
+ "email": "rmorenp@rampamster.org",
+ "issues": "https://github.com/openfoodfacts/openfoodfacts-php/issues",
+ "chat": "https://slack.openfoodfacts.org/",
+ "source": "https://github.com/openfoodfacts/openfoodfacts-php"
+ },
+ "authors": [
+ {
+ "name": "Roberto Moreno",
+ "email": "rmorenp@rampmaster.org",
+ "homepage": "http://www.rampmaster.org",
+ "role": "Wrapper Developer"
+ }
+ ],
+ "require": {
+ "php": ">=7.1",
+ "guzzlehttp/guzzle": "^6.3",
+ "psr/log": "^1.0",
+ "psr/simple-cache": "^1.0",
+ "ext-json": "*",
+ "ext-curl": "*"
+ },
+ "autoload": {
+ "psr-4": {
+ "OpenFoodFacts\\": "src/"
+ }
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.0",
+ "symfony/cache": "^4.3",
+ "monolog/monolog": "^1.23",
+ "ext-gd": ">=7.2"
+ }
+}
diff --git a/openfoodfacts-php-0.2.3/doc/home.md b/openfoodfacts-php-0.2.3/doc/home.md
new file mode 100644
index 0000000..30404ce
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/doc/home.md
@@ -0,0 +1 @@
+TODO
\ No newline at end of file
diff --git a/openfoodfacts-php-0.2.3/examples/00_flat_request/index.html b/openfoodfacts-php-0.2.3/examples/00_flat_request/index.html
new file mode 100644
index 0000000..af992d7
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/examples/00_flat_request/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+ OFF
+
+
+
+
+
\ No newline at end of file
diff --git a/openfoodfacts-php-0.2.3/examples/00_flat_request/request.php b/openfoodfacts-php-0.2.3/examples/00_flat_request/request.php
new file mode 100644
index 0000000..a4e5b61
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/examples/00_flat_request/request.php
@@ -0,0 +1,31 @@
+
+
+
+
+ OFF
+
+
+Example Output
+
+
+ Product Name |
+ {productName} |
+
+
+ Brand |
+ {brand} |
+
+
+ Image |
+ |
+
+
+Response Struct (Array Format)
+
+ {json}
+
+
+
\ No newline at end of file
diff --git a/openfoodfacts-php-0.2.3/examples/01-basic_api_usage/cached_example.php b/openfoodfacts-php-0.2.3/examples/01-basic_api_usage/cached_example.php
new file mode 100644
index 0000000..0b166b8
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/examples/01-basic_api_usage/cached_example.php
@@ -0,0 +1,13 @@
+
+
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\Psr16Cache;
+
+include '../../vendor/autoload.php';
+$logger = new \Monolog\Logger('test');
+$httpClient = new \GuzzleHttp\Client();
+// the PSR-6 cache object that you want to use (you might also use a PSR-16 Interface Object directly)
+$psr6Cache = new FilesystemAdapter();
+$psr16Cache = new Psr16Cache($psr6Cache);
+$api = new \OpenFoodFacts\Api('food', 'world', $logger, $httpClient, $psr16Cache);
+$product = $api->getProduct(rand(1, 50));
diff --git a/openfoodfacts-php-0.2.3/phpunit.xml b/openfoodfacts-php-0.2.3/phpunit.xml
new file mode 100644
index 0000000..88485a4
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/phpunit.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+ tests
+
+
+
+
+
+ src
+
+
+
+
+
+
+
+
+
+
+
diff --git a/openfoodfacts-php-0.2.3/src/Api.php b/openfoodfacts-php-0.2.3/src/Api.php
new file mode 100644
index 0000000..cd875e0
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/src/Api.php
@@ -0,0 +1,536 @@
+ 'https://%s.openfoodfacts.org',
+ 'beauty' => 'https://%s.openbeautyfacts.org',
+ 'pet' => 'https://%s.openpetfoodfacts.org',
+ 'product' => 'https://%s.openproductsfacts.org',
+ ];
+
+ /**
+ * This constant defines the facets usable by the API
+ *
+ * This variable is used to create the magic functions like "getIngredients" or "getBrands"
+ * @var array
+ */
+ private const FACETS = [
+ 'additives',
+ 'allergens',
+ 'brands',
+ 'categories',
+ 'countries',
+ 'contributors',
+ 'code',
+ 'entry_dates',
+ 'ingredients',
+ 'label',
+ 'languages',
+ 'nutrition_grade',
+ 'packaging',
+ 'packaging_codes',
+ 'purchase_places',
+ 'photographer',
+ 'informer',
+ 'states',
+ 'stores',
+ 'traces',
+ ];
+
+ /**
+ * This constant defines the extensions authorized for the downloading of the data
+ * @var array
+ */
+ private const FILE_TYPE_MAP = [
+ "mongodb" => "openfoodfacts-mongodbdump.tar.gz",
+ "csv" => "en.openfoodfacts.org.products.csv",
+ "rdf" => "en.openfoodfacts.org.products.rdf"
+ ];
+
+ /**
+ * the constructor of the function
+ *
+ * @param string $api the environment to search
+ * @param string $geography this parameter represent the the country code and the interface of the language
+ * @param LoggerInterface $logger this parameter define an logger
+ * @param ClientInterface|null $clientInterface
+ * @param CacheInterface|null $cacheInterface
+ */
+ public function __construct(
+ string $api = 'food',
+ string $geography = 'world',
+ LoggerInterface $logger = null,
+ ClientInterface $clientInterface = null,
+ CacheInterface $cacheInterface = null
+ )
+ {
+ $this->cache = $cacheInterface;
+ $this->logger = $logger ?? new NullLogger();
+ $this->httpClient = $clientInterface ?? new Client();
+
+ $this->geoUrl = sprintf(self::LIST_API[$api], $geography);
+ $this->geography = $geography;
+ $this->currentAPI = $api;
+ }
+
+ /**
+ * This function allows you to perform tests
+ * The domain is correct and for testing purposes only
+ */
+ public function activeTestMode() : void
+ {
+ $this->geoUrl = 'https://world.openfoodfacts.net';
+ $this->authentification('off', 'off');
+ }
+
+ /**
+ * This function store the authentication parameter
+ * @param string $username
+ * @param string $password
+ */
+ public function authentification(string $username, string $password) :void
+ {
+ $this->auth = [
+ 'user_id' => $username,
+ 'password' => $password
+ ];
+ }
+
+ /**
+ * It's a magic function, it works only for facets
+ * @param string $name The name of the function
+ * @param void $arguments not use yet (probably needed for ingredients)
+ * @return Collection The list of all documents found
+ * @throws InvalidArgumentException
+ * @throws BadRequestException
+ * @example getIngredients()
+ */
+ public function __call(string $name, $arguments) : Collection
+ {
+ //TODO : test with argument for ingredient
+ if (strpos($name, 'get') === 0) {
+ $facet = strtolower(substr($name, 3));
+ //TODO: what about PSR-12, e.g.: getNutritionGrade() ?
+
+ if (!in_array($facet, self::FACETS)) {
+ throw new BadRequestException('Facet "' . $facet . '" not found');
+ }
+
+ if ($facet === "purchase_places") {
+ $facet = "purchase-places";
+ } elseif ($facet === "packaging_codes") {
+ $facet = "packager-codes";
+ } elseif ($facet === "entry_dates") {
+ $facet = "entry-dates";
+ }
+
+ $url = $this->buildUrl(null, $facet, []);
+ $result = $this->fetch($url);
+ if ($facet !== 'ingredients') {
+ $result = [
+ 'products' => $result['tags'],
+ 'count' => $result['count'],
+ 'page' => 1,
+ 'skip' => 0,
+ 'page_size' => $result['count'],
+ ];
+ }
+ return new Collection($result, $this->currentAPI);
+ }
+
+ throw new BadRequestException('Call to undefined method '.__CLASS__.'::'.$name.'()');
+ }
+
+
+ /**
+ * this function search an Document by barcode
+ * @param string $barcode the barcode [\d]{13}
+ * @return Document A Document if found
+ * @throws InvalidArgumentException
+ * @throws ProductNotFoundException
+ * @throws BadRequestException
+ */
+ public function getProduct(string $barcode) : Document
+ {
+ $url = $this->buildUrl('api', 'product', $barcode);
+
+ $rawResult = $this->fetch($url);
+ if ($rawResult['status'] === 0) {
+ //TODO: maybe return null here? (just throw an exception if something really went wrong?
+ throw new ProductNotFoundException("Product not found", 1);
+ }
+
+ return Document::createSpecificDocument($this->currentAPI, $rawResult['product']);
+ }
+
+ /**
+ * This function return a Collection of Document search by facets
+ * @param array $query list of facets with value
+ * @param integer $page Number of the page
+ * @return Collection The list of all documents found
+ * @throws InvalidArgumentException
+ * @throws BadRequestException
+ */
+ public function getByFacets(array $query = [], int $page = 1) : Collection
+ {
+ if (empty($query)) {
+ return new Collection();
+ }
+ $search = [];
+ ksort($query);
+ foreach ($query as $key => $value) {
+ $search[] = $key;
+ $search[] = $value;
+ }
+
+ $url = $this->buildUrl(null, $search, $page);
+ $result = $this->fetch($url);
+ return new Collection($result, $this->currentAPI);
+ }
+
+ /**
+ * this function help you to add a new product (or update ??)
+ * @param array $postData The post data
+ * @return bool|string bool if the product has been added or the error message
+ * @throws BadRequestException
+ * @throws InvalidArgumentException
+ */
+ public function addNewProduct(array $postData)
+ {
+ if (!isset($postData['code']) || !isset($postData['product_name'])) {
+ throw new BadRequestException('code or product_name not found!');
+ }
+
+ $url = $this->buildUrl('cgi', 'product_jqm2.pl', []);
+ $result = $this->fetchPost($url, $postData);
+
+ if ($result['status_verbose'] === 'fields saved' && $result['status'] === 1) {
+ return true;
+ }
+ return $result['status_verbose'];
+ }
+
+ /**
+ * [uploadImage description]
+ * @param string $code the barcode of the product
+ * @param string $imageField th name of the image
+ * @param string $imagePath the path of the image
+ * @return array the http post response (cast in array)
+ * @throws BadRequestException
+ * @throws InvalidArgumentException
+ */
+ public function uploadImage(string $code, string $imageField, string $imagePath)
+ {
+ //TODO : need test
+ if ($this->currentAPI !== 'food') {
+ throw new BadRequestException('not Available yet');
+ }
+ if (!in_array($imageField, ["front", "ingredients", "nutrition"])) {
+ throw new BadRequestException('ImageField not valid!');
+ }
+ if (!file_exists($imagePath)) {
+ throw new BadRequestException('Image not found');
+ }
+
+
+ $url = $this->buildUrl('cgi', 'product_image_upload.pl', []);
+ $postData = [
+ 'code' => $code,
+ 'imagefield' => $imageField,
+ 'imgupload_' . $imageField => fopen($imagePath, 'r')
+ ];
+ return $this->fetchPost($url, $postData, true);
+ }
+
+ /**
+ * A search function
+ * @param string $search a search term (fulltext)
+ * @param integer $page Number of the page
+ * @param integer $pageSize The page size
+ * @param string $sortBy the sort
+ * @return Collection The list of all documents found
+ * @throws BadRequestException
+ * @throws InvalidArgumentException
+ */
+ public function search(string $search, int $page = 1, int $pageSize = 20, string $sortBy = 'unique_scans')
+ {
+ $parameters = [
+ 'search_terms' => $search,
+ 'page' => $page,
+ 'page_size' => $pageSize,
+ 'sort_by' => $sortBy,
+ 'json' => '1',
+ ];
+
+ $url = $this->buildUrl('cgi', 'search.pl', $parameters);
+ $result = $this->fetch($url, false);
+ return new Collection($result, $this->currentAPI);
+ }
+
+ /**
+ * This function download all data from OpenFoodFact
+ * @param string $filePath the location where you want to put the stream
+ * @param string $fileType mongodb/csv/rdf
+ * @return bool return true when download is complete
+ * @throws BadRequestException
+ */
+ public function downloadData(string $filePath, string $fileType = "mongodb")
+ {
+
+ if (!isset(self::FILE_TYPE_MAP[$fileType])) {
+ $this->logger->warning(
+ 'OpenFoodFact - fetch - failed - File type not recognized!',
+ ['fileType' => $fileType, 'availableTypes' => self::FILE_TYPE_MAP]
+ );
+ throw new BadRequestException('File type not recognized!');
+ }
+
+ $url = $this->buildUrl('data', self::FILE_TYPE_MAP[$fileType]);
+ try {
+ $response = $this->httpClient->request('get', $url, ['sink' => $filePath]);
+ } catch (GuzzleException $guzzleException) {
+ $this->logger->warning(sprintf('OpenFoodFact - fetch - failed - GET : %s', $url), ['exception' => $guzzleException]);
+ $exception = new BadRequestException($guzzleException->getMessage(), $guzzleException->getCode(), $guzzleException);
+
+ throw $exception;
+ }
+
+ $this->logger->info('OpenFoodFact - fetch - GET : ' . $url . ' - ' . $response->getStatusCode());
+
+ //TODO: validate response here (server may respond with 200 - OK but you might not get valid data as a response)
+
+ return $response->getStatusCode() == 200;
+ }
+
+
+ /**
+ * This private function do a http request
+ * @param string $url the url to fetch
+ * @param boolean $isJsonFile the request must be finish by '.json' ?
+ * @return array return the result of the request in array format
+ * @throws InvalidArgumentException
+ * @throws BadRequestException
+ */
+ private function fetch(string $url, bool $isJsonFile = true) : array
+ {
+
+ $url .= ($isJsonFile? '.json' : '');
+ $realUrl = $url;
+ $cacheKey = md5($realUrl);
+
+ if (!empty($this->cache) && $this->cache->has($cacheKey)) {
+ $cachedResult = $this->cache->get($cacheKey);
+ return $cachedResult;
+ }
+
+ $data = [
+ 'on_stats' => function (TransferStats $stats) use (&$realUrl) {
+ // this function help to find redirection
+ // On redirect we lost some parameters like page
+ $realUrl= (string)$stats->getEffectiveUri();
+ }
+ ];
+ if ($this->auth) {
+ $data['auth'] = array_values($this->auth);
+ }
+
+ try {
+ $response = $this->httpClient->request('get', $url, $data);
+ } catch (GuzzleException $guzzleException) {
+ $this->logger->warning(sprintf('OpenFoodFact - fetch - failed - GET : %s', $url), ['exception' => $guzzleException]);
+ //TODO: What to do on a error? - return empty array?
+ $exception = new BadRequestException($guzzleException->getMessage(), $guzzleException->getCode(), $guzzleException);
+
+ throw $exception;
+ }
+ if ($realUrl !== $url) {
+ $this->logger->warning('OpenFoodFact - The url : '. $url . ' has been redirect to ' . $realUrl);
+ trigger_error('OpenFoodFact - Your request has been redirect');
+ }
+ $this->logger->info('OpenFoodFact - fetch - GET : ' . $url . ' - ' . $response->getStatusCode());
+
+ $jsonResult = json_decode($response->getBody(), true);
+
+ if (!empty($this->cache) && !empty($jsonResult)) {
+ $this->cache->set($cacheKey, $jsonResult);
+ }
+
+ return $jsonResult;
+ }
+
+ /**
+ * This function performs the same job of the "fetch" function except the call method and parameters
+ * @param string $url The url to fetch
+ * @param array $postData The post data
+ * @param boolean $isMultipart The data is multipart ?
+ * @return array return the result of the request in array format
+ * @throws InvalidArgumentException
+ * @throws BadRequestException
+ */
+ private function fetchPost(string $url, array $postData, bool $isMultipart = false) : array
+ {
+ $data = [];
+ if ($this->auth) {
+ $data['auth'] = array_values($this->auth);
+ }
+ if ($isMultipart) {
+ foreach ($postData as $key => $value) {
+ $data['multipart'][] = [
+ 'name' => $key,
+ 'contents' => $value
+ ];
+ }
+ } else {
+ $data['form_params'] = $postData;
+ }
+
+ $cacheKey = md5($url . json_encode($data));
+
+ if (!empty($this->cache) && $this->cache->has($cacheKey)) {
+ return $this->cache->get($cacheKey);
+ }
+
+ try {
+ $response = $this->httpClient->request('post', $url, $data);
+ }catch (GuzzleException $guzzleException){
+ $exception = new BadRequestException($guzzleException->getMessage(), $guzzleException->getCode(), $guzzleException);
+
+ throw $exception;
+ }
+
+ $this->logger->info('OpenFoodFact - fetch - GET : ' . $url . ' - ' . $response->getStatusCode());
+
+ $jsonResult = json_decode($response->getBody(), true);
+
+ if (!empty($this->cache) && !empty($jsonResult)) {
+ $this->cache->set($cacheKey, $jsonResult);
+ }
+
+ return $jsonResult;
+ }
+
+ /**
+ * This private function generates an url according to the parameters
+ * @param string|null $service
+ * @param string|array|null $resourceType
+ * @param string|array|null $parameters
+ * @return string the generated url
+ */
+ private function buildUrl(string $service = null, $resourceType = null, $parameters = null) : string
+ {
+ $baseUrl = null;
+ switch ($service) {
+ case 'api':
+ $baseUrl = implode('/', [
+ $this->geoUrl,
+ $service,
+ 'v0',
+ $resourceType,
+ $parameters
+ ]);
+ break;
+ case 'data':
+ $baseUrl = implode('/', [
+ $this->geoUrl,
+ $service,
+ $resourceType
+ ]);
+ break;
+ case 'cgi':
+ $baseUrl = implode('/', [
+ $this->geoUrl,
+ $service,
+ $resourceType
+ ]);
+ $baseUrl .= '?' . http_build_query($parameters);
+ break;
+ case null:
+ default:
+ if (is_array($resourceType)) {
+ $resourceType = implode('/', $resourceType);
+ }
+ if ($resourceType == 'ingredients') {
+ //need test
+ $resourceType = implode('/', ["state", "complete", $resourceType]);
+ $parameters = 1;
+ }
+ $baseUrl = implode('/', array_filter([
+ $this->geoUrl,
+ $resourceType,
+ $parameters
+ ], function ($value) {
+ return !empty($value);
+ }));
+ break;
+ }
+ return $baseUrl;
+ }
+}
diff --git a/openfoodfacts-php-0.2.3/src/Collection.php b/openfoodfacts-php-0.2.3/src/Collection.php
new file mode 100644
index 0000000..02695e5
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/src/Collection.php
@@ -0,0 +1,133 @@
+ [],
+ 'count' => 0,
+ 'page' => 0,
+ 'skip' => 0,
+ 'page_size' => 0,
+ ];
+ $this->listDocuments = [];
+
+ if (!empty($data['products'])) {
+ $currentApi = '';
+ if (null !== $api) {
+ $currentApi = $api;
+ }
+ foreach ($data['products'] as $document) {
+ if($document instanceof Document){
+ $this->listDocuments[] = $document;
+ }elseif (is_array($document)){
+ $this->listDocuments[] = Document::createSpecificDocument($currentApi, $document);
+ }else {
+ throw new \InvalidArgumentException(sprintf('Would expect an OpenFoodFacts\Document Interface or Array here. Got: %s', gettype($document)));
+ }
+
+ }
+ }
+
+ $this->count = $data['count'];
+ $this->page = $data['page'];
+ $this->skip = $data['skip'];
+ $this->pageSize = $data['page_size'];
+ }
+
+ /**
+ * @return int get the current page
+ */
+ public function getPage() : int
+ {
+ return $this->page;
+ }
+ /**
+ * @return int get the number of element skipped
+ */
+ public function getSkip() : int
+ {
+ return $this->skip;
+ }
+ /**
+ * @return int get the number of element by page for this collection
+ */
+ public function getPageSize() : int
+ {
+ return $this->pageSize;
+ }
+
+ /**
+ * @return int the number of element in this Collection
+ */
+ public function pageCount() : int
+ {
+ return count($this->listDocuments);
+ }
+
+ /**
+ * @return int the number of element for this search
+ */
+ public function searchCount() : int
+ {
+ return $this->count;
+ }
+
+ /**
+ * Implementation of Iterator
+ */
+
+ /**
+ * @inheritDoc
+ */
+ public function rewind()
+ {
+ reset($this->listDocuments);
+ }
+ /**
+ * @inheritDoc
+ */
+ public function current()
+ {
+ return current($this->listDocuments);
+ }
+ /**
+ * @inheritDoc
+ */
+ public function key()
+ {
+ return key($this->listDocuments);
+ }
+ /**
+ * @inheritDoc
+ */
+ public function next()
+ {
+ return next($this->listDocuments);
+ }
+ /**
+ * @inheritDoc
+ */
+ public function valid()
+ {
+ $key = key($this->listDocuments);
+ return ($key !== null && $key !== false);
+ }
+}
diff --git a/openfoodfacts-php-0.2.3/src/Document.php b/openfoodfacts-php-0.2.3/src/Document.php
new file mode 100644
index 0000000..6034000
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/src/Document.php
@@ -0,0 +1,83 @@
+data = $data;
+ $this->api = $api;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function __get(string $name)
+ {
+ return $this->data[$name];
+ }
+ /**
+ * @inheritDoc
+ */
+ public function __isset(string $name):bool
+ {
+ return isset($this->data[$name]);
+ }
+
+ /**
+ * Returns a sorted representation of the complete Document Data
+ * @return array
+ */
+ public function getData(): array
+ {
+ $this->recursiveSortArray($this->data);
+ return $this->data;
+ }
+
+ /**
+ * Returns a Document in the type regarding to the API used.
+ * May be a Child of "Document" e.g.: FoodDocument or ProductDocument
+ * @param string $apiIdentifier
+ * @param array $data
+ * @return Document
+ */
+ public static function createSpecificDocument(string $apiIdentifier, array $data): Document
+ {
+ if ($apiIdentifier === '') {
+ return new Document($data, $apiIdentifier);
+ }
+
+ $className = "OpenFoodFacts\Document\\" . ucfirst($apiIdentifier) . 'Document';
+
+ if (class_exists($className) && is_subclass_of($className, Document::class)) {
+ return new $className($data, $apiIdentifier);
+ }
+
+ return new Document($data, $apiIdentifier);
+ }
+
+}
diff --git a/openfoodfacts-php-0.2.3/src/Document/BeautyDocument.php b/openfoodfacts-php-0.2.3/src/Document/BeautyDocument.php
new file mode 100644
index 0000000..99a2e55
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/src/Document/BeautyDocument.php
@@ -0,0 +1,10 @@
+recursiveDeleteDirectory($dir . "/" . $object);
+ } else {
+ unlink($dir . "/" . $object);
+ }
+ }
+ }
+ rmdir($dir);
+ }
+ }
+}
\ No newline at end of file
diff --git a/openfoodfacts-php-0.2.3/src/RecursiveSortingTrait.php b/openfoodfacts-php-0.2.3/src/RecursiveSortingTrait.php
new file mode 100644
index 0000000..fcb6259
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/src/RecursiveSortingTrait.php
@@ -0,0 +1,38 @@
+isAssoc($arr)) {
+ ksort($arr);
+ } else {
+ asort($arr);
+ }
+ foreach ($arr as &$a) {
+ if (is_array($a)) {
+ $this->recursiveSortArray($a);
+ }
+ }
+ }
+}
diff --git a/openfoodfacts-php-0.2.3/tests/ApiFoodCacheTest.php b/openfoodfacts-php-0.2.3/tests/ApiFoodCacheTest.php
new file mode 100644
index 0000000..c91d8fe
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/tests/ApiFoodCacheTest.php
@@ -0,0 +1,178 @@
+pushHandler(new StreamHandler('log/test.log'));
+ $psr6Cache = new FilesystemAdapter(sprintf('testrun_%u', rand(0, 1000)), 10, 'tests/tmp/cache');
+ $cache = new Psr16Cache($psr6Cache);
+
+ $httpClient = new GuzzleHttp\Client([
+// "http_errors" => false, // MUST not use as it crashes error handling
+ 'Connection' => 'close',
+ CURLOPT_FORBID_REUSE => true,
+ CURLOPT_FRESH_CONNECT => true,
+ 'defaults' => [
+ 'headers' => [
+ 'CURLOPT_USERAGENT' => 'OFF - PHP - SDK - Unit Test',
+ ],
+ ],
+ ]);
+
+ $api = new Api('food', 'fr-en', $log, $httpClient, $cache);
+ $this->assertInstanceOf(Api::class, $api);
+ $this->api = $api;
+
+ }
+
+ public function testApi(): void
+ {
+
+ $prd = $this->api->getProduct('3057640385148');
+
+ $this->assertInstanceOf(FoodDocument::class, $prd);
+ $this->assertInstanceOf(Document::class, $prd);
+
+ $this->assertTrue(isset($prd->product_name));
+ $this->assertNotEmpty($prd->product_name);
+
+ try {
+ $product = $this->api->getProduct('305764038514800');
+ $this->assertTrue(false);
+ } catch (ProductNotFoundException $e) {
+ $this->assertTrue(true);
+ }
+
+ try {
+ $result = $this->api->downloadData('tests/mongodb', 'nopeFile');
+ $this->assertTrue(false);
+ } catch (BadRequestException $e) {
+ $this->assertEquals($e->getMessage(), 'File type not recognized!');
+ }
+
+ // $result = $this->api->downloadData('tests/tmp/mongodb');
+ // $this->assertTrue(true);
+ }
+
+ public function testApiCollection(): void
+ {
+
+ $collection = $this->api->getByFacets([]);
+ $this->assertInstanceOf(Collection::class, $collection);
+ $this->assertEquals($collection->pageCount(), 0);
+
+ try {
+ $collection = $this->api->getByFacets(['trace' => 'egg', 'country' => 'france'], 3);
+ $this->assertTrue(false);
+ } catch (\PHPUnit\Framework\Error\Notice $e) {
+ $this->assertEquals($e->getMessage(), 'OpenFoodFact - Your request has been redirect');
+ }
+
+ $collection = $this->api->getByFacets(['trace' => 'eggs', 'country' => 'france'], 3);
+ $this->assertInstanceOf(Collection::class, $collection);
+ $this->assertEquals($collection->pageCount(), 20);
+ $this->assertEquals($collection->getPage(), 3);
+ $this->assertEquals($collection->getSkip(), 40);
+ $this->assertEquals($collection->getPageSize(), 20);
+ $this->assertGreaterThan(1000, $collection->searchCount());
+
+ foreach ($collection as $key => $doc) {
+ if ($key > 1) {
+ break;
+ }
+
+ $this->assertInstanceOf(FoodDocument::class, $doc);
+ $this->assertInstanceOf(Document::class, $doc);
+
+ }
+
+ }
+
+ public function testApiSearch(): void
+ {
+
+ $collection = $this->api->search('volvic', 3, 30);
+ $this->assertInstanceOf(Collection::class, $collection);
+ $this->assertEquals($collection->pageCount(), 30);
+ $this->assertGreaterThan(100, $collection->searchCount());
+
+ }
+
+
+ public function testFacets(): void
+ {
+
+ $collection = $this->api->getIngredients();
+ $this->assertInstanceOf(Collection::class, $collection);
+ $this->assertEquals($collection->pageCount(), 20);
+ $this->assertEquals($collection->getPageSize(), 20);
+ $this->assertGreaterThan(70000, $collection->searchCount());
+
+ try {
+ $collection = $this->api->getIngredient();
+ $this->assertInstanceOf(Collection::class, $collection);
+ $this->assertTrue(false);
+ } catch (BadRequestException $e) {
+ $this->assertEquals($e->getMessage(), 'Facet "ingredient" not found');
+ }
+
+ $collection = $this->api->getPurchase_places();
+ $this->assertInstanceOf(Collection::class, $collection);
+ $collection = $this->api->getPackaging_codes();
+ $this->assertInstanceOf(Collection::class, $collection);
+ $collection = $this->api->getEntry_dates();
+ $this->assertInstanceOf(Collection::class, $collection);
+
+ try {
+ $collection = $this->api->getIngredient();
+ $this->assertTrue(false);
+ } catch (BadRequestException $e) {
+ $this->assertEquals($e->getMessage(), 'Facet "ingredient" not found');
+ }
+
+ try {
+ $collection = $this->api->nope();
+ } catch (\Exception $e) {
+ $this->assertTrue(true);
+ }
+ }
+
+ protected function tearDown()
+ {
+ $this->recursiveDeleteDirectory('tests/tmp');
+ }
+
+}
diff --git a/openfoodfacts-php-0.2.3/tests/ApiFoodTest.php b/openfoodfacts-php-0.2.3/tests/ApiFoodTest.php
new file mode 100644
index 0000000..8b8ae32
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/tests/ApiFoodTest.php
@@ -0,0 +1,248 @@
+pushHandler(new StreamHandler('log/test.log'));
+
+ $this->api = new Api('food', 'fr-en', $log);
+ @rmdir('tests/tmp');
+ @mkdir('tests/tmp');
+ }
+
+ public function testApi(): void
+ {
+
+ $prd = $this->api->getProduct('3057640385148');
+
+ $this->assertInstanceOf(FoodDocument::class, $prd);
+ $this->assertInstanceOf(Document::class, $prd);
+ $this->assertTrue(isset($prd->product_name));
+ $this->assertNotEmpty($prd->product_name);
+
+ try {
+ $product = $this->api->getProduct('305764038514800');
+ $this->assertTrue(false);
+ } catch (ProductNotFoundException $e) {
+ $this->assertTrue(true);
+ }
+
+ try {
+ $result = $this->api->downloadData('tests/mongodb', 'nopeFile');
+ $this->assertTrue(false);
+ } catch (BadRequestException $e) {
+ $this->assertEquals($e->getMessage(), 'File type not recognized!');
+ }
+
+ // $result = $this->api->downloadData('tests/tmp/mongodb');
+ // $this->assertTrue(true);
+ }
+
+ public function testApiCollection(): void
+ {
+
+ $collection = $this->api->getByFacets([]);
+ $this->assertInstanceOf(Collection::class, $collection);
+ $this->assertEquals($collection->pageCount(), 0);
+
+ try {
+ $collection = $this->api->getByFacets(['trace' => 'egg', 'country' => 'france'], 3);
+ $this->assertTrue(false);
+ } catch (\PHPUnit\Framework\Error\Notice $e) {
+ $this->assertEquals($e->getMessage(), 'OpenFoodFact - Your request has been redirect');
+ }
+
+ $collection = $this->api->getByFacets(['trace' => 'eggs', 'country' => 'france'], 3);
+ $this->assertInstanceOf(Collection::class, $collection);
+ $this->assertEquals($collection->pageCount(), 20);
+ $this->assertEquals($collection->getPage(), 3);
+ $this->assertEquals($collection->getSkip(), 40);
+ $this->assertEquals($collection->getPageSize(), 20);
+ $this->assertGreaterThan(1000, $collection->searchCount());
+
+ foreach ($collection as $key => $doc) {
+ if ($key > 1) {
+ break;
+ }
+ $this->assertInstanceOf(FoodDocument::class, $doc);
+ $this->assertInstanceOf(Document::class, $doc);
+
+ }
+
+ }
+
+ public function testApiAddProduct(): void
+ {
+ $this->api->activeTestMode();
+ try {
+ $prd = $this->api->getProduct('3057640385148');
+ $this->assertInstanceOf(FoodDocument::class, $prd);
+ $this->assertInstanceOf(Document::class, $prd);
+ } catch (Exception $exception) {
+ if ($exception->getPrevious() instanceof ServerException && $exception->getPrevious()->getCode() === 503) {
+ $this->markTestSkipped(
+ 'Testing API currently not available.'
+ );
+ }
+ }
+
+ $postData = ['code' => $prd->code, 'product_name' => $prd->product_name];
+
+ $result = $this->api->addNewProduct($postData);
+ $this->assertTrue(is_bool($result));
+
+
+ $postData = ['product_name' => $prd->product_name];
+
+ try {
+ $result = $this->api->addNewProduct($postData);
+ $this->assertTrue(false);
+ } catch (BadRequestException $e) {
+ $this->assertTrue(true);
+ }
+ $postData = ['code' => '', 'product_name' => $prd->product_name];
+ $result = $this->api->addNewProduct($postData);
+ $this->assertTrue(is_string($result));
+ $this->assertEquals($result, 'no code or invalid code');
+
+ }
+
+ public function testApiAddImage(): void
+ {
+
+ $this->api->activeTestMode();
+ try {
+ $prd = $this->api->getProduct('3057640385148');
+ $this->assertInstanceOf(Collection::class, $prd);
+ } catch (Exception $exception) {
+ if ($exception->getPrevious() instanceof ServerException && $exception->getPrevious()->getCode() === 503) {
+ $this->markTestSkipped(
+ 'Testing API currently not available.'
+ );
+ }
+ }
+
+ try {
+ $this->api->uploadImage('3057640385148', 'fronts', 'nothing');
+ $this->assertTrue(false);
+ } catch (BadRequestException $e) {
+ $this->assertEquals($e->getMessage(), 'ImageField not valid!');
+ }
+ try {
+ $this->api->uploadImage('3057640385148', 'front', 'nothing');
+ $this->assertTrue(false);
+ } catch (BadRequestException $e) {
+ $this->assertEquals($e->getMessage(), 'Image not found');
+ }
+ $file1 = $this->createRandomImage();
+
+ $result = $this->api->uploadImage('3057640385148', 'front', $file1);
+ $this->assertEquals($result['status'], 'status ok');
+ $this->assertTrue(isset($result['imagefield']));
+ $this->assertTrue(isset($result['image']));
+ $this->assertTrue(isset($result['image']['imgid']));
+
+
+ }
+
+ public function testApiSearch(): void
+ {
+
+ $collection = $this->api->search('volvic', 3, 30);
+ $this->assertInstanceOf(Collection::class, $collection);
+ $this->assertEquals($collection->pageCount(), 30);
+ $this->assertGreaterThan(100, $collection->searchCount());
+
+ }
+
+
+ public function testFacets(): void
+ {
+
+ $collection = $this->api->getIngredients();
+ $this->assertInstanceOf(Collection::class, $collection);
+ $this->assertEquals($collection->pageCount(), 20);
+ $this->assertEquals($collection->getPageSize(), 20);
+ $this->assertGreaterThan(70000, $collection->searchCount());
+
+ try {
+ $collection = $this->api->getIngredient();
+ $this->assertInstanceOf(Collection::class, $collection);
+ $this->assertTrue(false);
+ } catch (BadRequestException $e) {
+ $this->assertEquals($e->getMessage(), 'Facet "ingredient" not found');
+ }
+
+ $collection = $this->api->getPurchase_places();
+ $this->assertInstanceOf(Collection::class, $collection);
+ $collection = $this->api->getPackaging_codes();
+ $this->assertInstanceOf(Collection::class, $collection);
+ $collection = $this->api->getEntry_dates();
+ $this->assertInstanceOf(Collection::class, $collection);
+
+ try {
+ $collection = $this->api->getIngredient();
+ $this->assertTrue(false);
+ } catch (BadRequestException $e) {
+ $this->assertEquals($e->getMessage(), 'Facet "ingredient" not found');
+ }
+
+ try {
+ $collection = $this->api->nope();
+ } catch (\Exception $e) {
+ $this->assertTrue(true);
+ }
+ }
+
+
+ private function createRandomImage(): string
+ {
+
+ $width = 400;
+ $height = 200;
+
+ $imageRes = imagecreatetruecolor($width, $height);
+ for ($row = 0; $row <= $height; $row++) {
+ for ($column = 0; $column <= $width; $column++) {
+ $colour = imagecolorallocate($imageRes, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255));
+ imagesetpixel($imageRes, $column, $row, $colour);
+ }
+ }
+ $path = 'tests/tmp/image_' . time() . '.jpg';
+ if (imagejpeg($imageRes, $path)) {
+ return $path;
+ }
+ throw new \Exception("Error Processing Request", 1);
+
+ }
+
+ protected function tearDown()
+ {
+ $this->recursiveDeleteDirectory('tests/tmp');
+ }
+
+}
diff --git a/openfoodfacts-php-0.2.3/tests/ApiPetTest.php b/openfoodfacts-php-0.2.3/tests/ApiPetTest.php
new file mode 100644
index 0000000..a727b74
--- /dev/null
+++ b/openfoodfacts-php-0.2.3/tests/ApiPetTest.php
@@ -0,0 +1,80 @@
+pushHandler(new StreamHandler('log/test.log'));
+
+ $this->api = new Api('pet', 'fr', $log);
+
+ foreach (glob('tests/images/*') as $file) {
+ unlink($file);
+ }
+ }
+
+ public function testApi()
+ {
+
+ $prd = $this->api->getProduct('7613035799738');
+
+ $this->assertInstanceOf(PetDocument::class, $prd);
+ $this->assertInstanceOf(Document::class, $prd);
+ $this->assertTrue(isset($prd->product_name));
+ $this->assertNotEmpty($prd->product_name);
+
+ }
+
+ public function testApiAddImage()
+ {
+ try {
+ $this->api->uploadImage('7613035799738', 'fronts', 'nothing');
+ $this->assertTrue(false);
+ } catch (BadRequestException $e) {
+ $this->assertEquals($e->getMessage(), 'not Available yet');
+ $this->markTestSkipped(
+ $e->getMessage()
+ );
+ }
+
+ }
+
+ public function testApiSearch()
+ {
+
+ $collection = $this->api->search('chat', 3, 30);
+
+ $this->assertInstanceOf(Collection::class, $collection);
+ $this->assertEquals($collection->pageCount(), 30);
+ $this->assertGreaterThan(100, $collection->searchCount());
+
+ }
+
+ protected function tearDown()
+ {
+ $this->recursiveDeleteDirectory('tests/tmp');
+ }
+
+}