Browse Source

Add OpenFoodFacts 0.2.3 lib

docjyJ 3 years ago
parent
commit
5b37c48d36
27 changed files with 1682 additions and 0 deletions
  1. 18
    0
      openfoodfacts-php-0.2.3/.gitignore
  2. 5
    0
      openfoodfacts-php-0.2.3/.travis.yml
  3. 3
    0
      openfoodfacts-php-0.2.3/CHANGELOG.md
  4. 7
    0
      openfoodfacts-php-0.2.3/CONTRIBUTING.md
  5. 21
    0
      openfoodfacts-php-0.2.3/LICENSE
  6. 78
    0
      openfoodfacts-php-0.2.3/README.md
  7. 41
    0
      openfoodfacts-php-0.2.3/composer.json
  8. 1
    0
      openfoodfacts-php-0.2.3/doc/home.md
  9. 13
    0
      openfoodfacts-php-0.2.3/examples/00_flat_request/index.html
  10. 31
    0
      openfoodfacts-php-0.2.3/examples/00_flat_request/request.php
  11. 28
    0
      openfoodfacts-php-0.2.3/examples/00_flat_request/response.html
  12. 13
    0
      openfoodfacts-php-0.2.3/examples/01-basic_api_usage/cached_example.php
  13. 36
    0
      openfoodfacts-php-0.2.3/phpunit.xml
  14. 536
    0
      openfoodfacts-php-0.2.3/src/Api.php
  15. 133
    0
      openfoodfacts-php-0.2.3/src/Collection.php
  16. 83
    0
      openfoodfacts-php-0.2.3/src/Document.php
  17. 10
    0
      openfoodfacts-php-0.2.3/src/Document/BeautyDocument.php
  18. 10
    0
      openfoodfacts-php-0.2.3/src/Document/FoodDocument.php
  19. 10
    0
      openfoodfacts-php-0.2.3/src/Document/PetDocument.php
  20. 10
    0
      openfoodfacts-php-0.2.3/src/Document/ProductDocument.php
  21. 14
    0
      openfoodfacts-php-0.2.3/src/Exception/BadRequestException.php
  22. 14
    0
      openfoodfacts-php-0.2.3/src/Exception/ProductNotFoundException.php
  23. 23
    0
      openfoodfacts-php-0.2.3/src/FilesystemTrait.php
  24. 38
    0
      openfoodfacts-php-0.2.3/src/RecursiveSortingTrait.php
  25. 178
    0
      openfoodfacts-php-0.2.3/tests/ApiFoodCacheTest.php
  26. 248
    0
      openfoodfacts-php-0.2.3/tests/ApiFoodTest.php
  27. 80
    0
      openfoodfacts-php-0.2.3/tests/ApiPetTest.php

+ 18
- 0
openfoodfacts-php-0.2.3/.gitignore View File

@@ -0,0 +1,18 @@
1
+# Created by .ignore support plugin (hsz.mobi)
2
+### Example user template template
3
+### Example user template
4
+/build/
5
+
6
+# IntelliJ project files
7
+.idea
8
+*.iml
9
+out
10
+gen### Composer template
11
+composer.phar
12
+/vendor/
13
+/log/
14
+/tests/tmp/*
15
+
16
+# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
17
+# You may choose to ignore a library lock file https://getcomposer.org/doc/02-libraries.md#lock-file
18
+composer.lock

+ 5
- 0
openfoodfacts-php-0.2.3/.travis.yml View File

@@ -0,0 +1,5 @@
1
+language: php
2
+php:
3
+  - '7.2'
4
+before_script: composer install --dev
5
+script: vendor/bin/phpunit

+ 3
- 0
openfoodfacts-php-0.2.3/CHANGELOG.md View File

@@ -0,0 +1,3 @@
1
+## 0.0.1 (June 19, 2016) ##
2
+
3
+*   Upload project struct

+ 7
- 0
openfoodfacts-php-0.2.3/CONTRIBUTING.md View File

@@ -0,0 +1,7 @@
1
+## Contributing
2
+
3
+1. Fork it ( https://github.com/openfoodfacts/openfoodfacts-php/fork )
4
+2. Create your feature branch (`git checkout -b my-new-feature`)
5
+3. Commit your changes (`git commit -am 'Add some feature'`)
6
+4. Push to the branch (`git push origin my-new-feature`)
7
+5. Create a new Pull Request

+ 21
- 0
openfoodfacts-php-0.2.3/LICENSE View File

@@ -0,0 +1,21 @@
1
+MIT License
2
+
3
+Copyright (c) 2020 Open Food Facts
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in all
13
+copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+SOFTWARE.

+ 78
- 0
openfoodfacts-php-0.2.3/README.md View File

@@ -0,0 +1,78 @@
1
+# openfoodfacts-php
2
+![Open Food Facts](https://static.openfoodfacts.org/images/misc/openfoodfacts-logo-en-178x150.png)
3
+
4
+PHP API Wrapper for [Open Food Facts](https://openfoodfacts.org/), the open database about food.
5
+
6
+[![Project Status](http://opensource.box.com/badges/active.svg)](http://opensource.box.com/badges)
7
+[![Build Status](https://travis-ci.org/openfoodfacts/openfoodfacts-php.svg?branch=master)](https://travis-ci.org/openfoodfacts/openfoodfacts-php)
8
+[![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")
9
+[![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")
10
+
11
+## Installation
12
+
13
+With Composer:
14
+
15
+```bash
16
+composer require openfoodfacts/openfoodfacts-php
17
+```
18
+
19
+## Usage
20
+This is the most basic way of creating the API:
21
+```php
22
+$api = new OpenFoodFacts\Api('food','fr');
23
+$product = $api->getProduct('3057640385148');
24
+```
25
+In the example above you access the "food" database, limited to the French language/country scope. 
26
+The first parameter is either 
27
+ - "food"
28
+ - "beauty" or 
29
+ - "pet"
30
+ 
31
+to decide which product database you want to use.
32
+
33
+The second parameter decides the language/country scope of the chosen database: f.e. "world" or "de" or "fr". 
34
+
35
+For more details on this topic: see the [API Documentation](https://en.wiki.openfoodfacts.org/API/Read#Countries_and_Language_of_the_Response)
36
+
37
+These are all the parameters you really need for basic usage.
38
+
39
+As return types for ```$api->getProduct``` you get an ```Document::class``` Object. 
40
+This may also be an Object of Type  ```FoodProduct::class```,```PetProduct::class```, ```BeautyProduct::class``` depending on which API you are creating.
41
+These objects inherit from the more generic ```Document::class```
42
+
43
+In the example above, we use the 'food' API and there will get a ```FoodProduct::class```
44
+
45
+For getting a first overview the ```Document::class``` has a function to return an array representation(sorted) for a first start. 
46
+```php
47
+$product = $api->getProduct('3057640385148');
48
+$productDataAsArray = $product->getData();
49
+```
50
+
51
+
52
+#### Optional Parameters
53
+The other parameters are optional and for a more sophisticated use of the api (from a software development point of view):
54
+
55
+An example in code is found here: [cached_example.php](examples/01-basic_api_usage/cached_example.php)
56
+
57
+LoggerInterface: A logger which decieds where to log errors to (file, console , etc) 
58
+
59
+see: [PSR-3 Loggerinterface](https://www.php-fig.org/psr/psr-3/)
60
+
61
+ClientInterface: The HTTP Client - to adjust the connection configs to your needs and more 
62
+
63
+see: [Guzzle HTTP Client](https://packagist.org/packages/guzzlehttp/guzzle)
64
+
65
+CacheInterface: To temporarily save the results of API request to improve the performance and to reduce the load on the API- Server 
66
+
67
+see: [PSR-16 Simple Cache](https://www.php-fig.org/psr/psr-16/)
68
+
69
+## Development
70
+
71
+
72
+### Contributing
73
+
74
+1. Fork it ( https://github.com/openfoodfacts/openfoodfacts-php/fork )
75
+2. Create your feature branch (`git checkout -b my-new-feature`)
76
+3. Commit your changes (`git commit -am 'Add some feature'`)
77
+4. Push to the branch (`git push origin my-new-feature`)
78
+5. Create a new Pull Request

+ 41
- 0
openfoodfacts-php-0.2.3/composer.json View File

@@ -0,0 +1,41 @@
1
+{
2
+  "name": "openfoodfacts/openfoodfacts-php",
3
+  "description": "Open Food Facts API Wrapper, the open database about food.",
4
+  "homepage": "https://world.openfoodfacts.org/",
5
+  "type": "library",
6
+  "minimum-stability": "stable",
7
+  "license": "MIT",
8
+  "support": {
9
+    "email": "rmorenp@rampamster.org",
10
+    "issues": "https://github.com/openfoodfacts/openfoodfacts-php/issues",
11
+    "chat": "https://slack.openfoodfacts.org/",
12
+    "source": "https://github.com/openfoodfacts/openfoodfacts-php"
13
+  },
14
+  "authors": [
15
+    {
16
+      "name": "Roberto Moreno",
17
+      "email": "rmorenp@rampmaster.org",
18
+      "homepage": "http://www.rampmaster.org",
19
+      "role": "Wrapper Developer"
20
+    }
21
+  ],
22
+  "require": {
23
+    "php": ">=7.1",
24
+    "guzzlehttp/guzzle": "^6.3",
25
+    "psr/log": "^1.0",
26
+    "psr/simple-cache": "^1.0",
27
+    "ext-json": "*",
28
+    "ext-curl": "*"
29
+  },
30
+  "autoload": {
31
+    "psr-4": {
32
+      "OpenFoodFacts\\": "src/"
33
+    }
34
+  },
35
+  "require-dev": {
36
+    "phpunit/phpunit": "^7.0",
37
+    "symfony/cache": "^4.3",
38
+    "monolog/monolog": "^1.23",
39
+    "ext-gd": ">=7.2"
40
+  }
41
+}

+ 1
- 0
openfoodfacts-php-0.2.3/doc/home.md View File

@@ -0,0 +1 @@
1
+TODO

+ 13
- 0
openfoodfacts-php-0.2.3/examples/00_flat_request/index.html View File

@@ -0,0 +1,13 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+<head>
4
+    <meta charset="UTF-8">
5
+    <title>OFF</title>
6
+</head>
7
+<body>
8
+<form action="./request.php" method="get">
9
+    <input type="text" class="form-control" placeholder="barcode" name="ean13">
10
+    <input type="submit" name="Ok" value="Find this Barcode !">
11
+</form>
12
+</body>
13
+</html>

+ 31
- 0
openfoodfacts-php-0.2.3/examples/00_flat_request/request.php View File

@@ -0,0 +1,31 @@
1
+<?php
2
+
3
+// Default parameters by case
4
+$country = 'fr'; // Country by using OFF
5
+$productSlug = 'produit'; // Product by language (producto in spanish or product in english)
6
+
7
+// Format URL
8
+$url = 'https://{country}.openfoodfacts.org/api/v0/{product}/{scan}.json';
9
+
10
+// Where we will set the value of the scan
11
+$barcode = (int) $_GET['ean13'];
12
+
13
+$url = str_replace(['{country}','{product}','{scan}'],[$country,$productSlug,$barcode],$url);
14
+
15
+// Connection to the API (french version here)
16
+$result = file_get_contents($url);
17
+
18
+// Decoding the JSON into an usable array (the value "true" confirms that the return is only an array)
19
+$json = json_decode($result, true);
20
+
21
+// Get the datas we want
22
+$productName = $json['product']['product_name'];
23
+$brand = $json['product']['brands'];
24
+$image = $json['product']['image_small_url'];
25
+
26
+$viewData = file_get_contents('response.html');
27
+
28
+echo str_replace(
29
+    ['{productName}','{brand}','{image}','{json}'],
30
+    [$productName,$brand,$image,print_r($json,true)],
31
+    $viewData);

+ 28
- 0
openfoodfacts-php-0.2.3/examples/00_flat_request/response.html View File

@@ -0,0 +1,28 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+<head>
4
+    <meta charset="UTF-8">
5
+    <title>OFF</title>
6
+</head>
7
+<body>
8
+<h2>Example Output</h2>
9
+<table>
10
+    <tr>
11
+        <td>Product Name</td>
12
+        <td>{productName}</td>
13
+    </tr>
14
+    <tr>
15
+        <td>Brand</td>
16
+        <td>{brand}</td>
17
+    </tr>
18
+    <tr>
19
+        <td>Image</td>
20
+        <td><img src="{image}"/></td>
21
+    </tr>
22
+</table>
23
+<h2>Response Struct (Array Format)</h2>
24
+<pre>
25
+    {json}
26
+</pre>
27
+</body>
28
+</html>

+ 13
- 0
openfoodfacts-php-0.2.3/examples/01-basic_api_usage/cached_example.php View File

@@ -0,0 +1,13 @@
1
+
2
+
3
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
4
+use Symfony\Component\Cache\Psr16Cache;
5
+
6
+include '../../vendor/autoload.php';
7
+$logger     = new \Monolog\Logger('test');
8
+$httpClient = new \GuzzleHttp\Client();
9
+// the PSR-6 cache object that you want to use (you might also use a PSR-16 Interface Object directly)
10
+$psr6Cache  = new FilesystemAdapter();
11
+$psr16Cache = new Psr16Cache($psr6Cache);
12
+$api        = new \OpenFoodFacts\Api('food', 'world', $logger, $httpClient, $psr16Cache);
13
+$product    = $api->getProduct(rand(1, 50));

+ 36
- 0
openfoodfacts-php-0.2.3/phpunit.xml View File

@@ -0,0 +1,36 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<phpunit
3
+      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+      xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd"
5
+      backupGlobals="false"
6
+      colors="true"
7
+      bootstrap="vendor/autoload.php"
8
+>
9
+
10
+    <php>
11
+        <ini name="error_reporting" value="-1" />
12
+        <server name="KERNEL_CLASS" value="AppKernel" />
13
+    </php>
14
+
15
+    <testsuites>
16
+        <testsuite name="AllTests">
17
+            <directory>tests</directory>
18
+        </testsuite>
19
+    </testsuites>
20
+
21
+    <filter>
22
+        <whitelist>
23
+            <directory>src</directory>
24
+            <!--<exclude>
25
+                <directory>src/*Bundle/Resources</directory>
26
+            </exclude>-->
27
+        </whitelist>
28
+    </filter>
29
+
30
+    <logging>
31
+        <log type="coverage-html" target="./build/coverrage" />
32
+        <log type="coverage-clover" target="./build/logs/clover.xml" />
33
+        <log type="junit" target="./build/logs/junit.xml" />
34
+        <log type="testdox-html" target="./build/logstestdox.html" />
35
+    </logging>
36
+</phpunit>

+ 536
- 0
openfoodfacts-php-0.2.3/src/Api.php View File

@@ -0,0 +1,536 @@
1
+<?php /** @noinspection ALL */
2
+
3
+namespace OpenFoodFacts;
4
+
5
+
6
+use GuzzleHttp\Client;
7
+use GuzzleHttp\ClientInterface;
8
+use GuzzleHttp\Exception\GuzzleException;
9
+use GuzzleHttp\TransferStats;
10
+use OpenFoodFacts\Exception\BadRequestException;
11
+use OpenFoodFacts\Exception\ProductNotFoundException;
12
+use Psr\Log\LoggerInterface;
13
+use Psr\Log\NullLogger;
14
+use Psr\SimpleCache\CacheInterface;
15
+use Psr\SimpleCache\InvalidArgumentException;
16
+
17
+/**
18
+ * this class provide [...]
19
+ *
20
+ * It a fork of the python OpenFoodFact rewrite on PHP 7.2
21
+ */
22
+class Api
23
+{
24
+
25
+    /**
26
+     * the httpClient for all http request
27
+     * @var ClientInterface
28
+     */
29
+    private $httpClient;
30
+
31
+    /**
32
+     * this property store the current base of the url
33
+     * @var string
34
+     */
35
+    private $geoUrl     = 'https://%s.openfoodfacts.org';
36
+
37
+    /**
38
+     * this property store the current API (it could be : food/beauty/pet )
39
+     * @var string
40
+     */
41
+    private $currentAPI = '';
42
+
43
+    /**
44
+     * This property store the current location for http call
45
+     *
46
+     * This property could be world for all product or you can specify le country code (cc) and
47
+     * language of the interface (lc). If you want filter on french product you can set fr as country code.
48
+     * We strongly recommend to use english as language of the interface
49
+     *
50
+     * @example fr-en
51
+     * @link https://en.wiki.openfoodfacts.org/API/Read#Country_code_.28cc.29_and_Language_of_the_interface_.28lc.29
52
+     * @var string
53
+     */
54
+    private $geography  = 'world';
55
+
56
+    /**
57
+     * this property store the auth parameter (username and password)
58
+     * @var array
59
+     */
60
+    private $auth       = null;
61
+
62
+    /**
63
+     * this property help you to log information
64
+     * @var LoggerInterface
65
+     */
66
+    private $logger     = null;
67
+
68
+    /**
69
+     * this constant defines the environments usable by the API
70
+     * @var array
71
+     */
72
+    private const LIST_API = [
73
+        'food'    => 'https://%s.openfoodfacts.org',
74
+        'beauty'  => 'https://%s.openbeautyfacts.org',
75
+        'pet'     => 'https://%s.openpetfoodfacts.org',
76
+        'product' => 'https://%s.openproductsfacts.org',
77
+    ];
78
+
79
+    /**
80
+     * This constant defines the facets usable by the API
81
+     *
82
+     * This variable is used to create the magic functions like "getIngredients" or "getBrands"
83
+     * @var array
84
+     */
85
+    private const FACETS = [
86
+        'additives',
87
+        'allergens',
88
+        'brands',
89
+        'categories',
90
+        'countries',
91
+        'contributors',
92
+        'code',
93
+        'entry_dates',
94
+        'ingredients',
95
+        'label',
96
+        'languages',
97
+        'nutrition_grade',
98
+        'packaging',
99
+        'packaging_codes',
100
+        'purchase_places',
101
+        'photographer',
102
+        'informer',
103
+        'states',
104
+        'stores',
105
+        'traces',
106
+    ];
107
+
108
+    /**
109
+     * This constant defines the extensions authorized for the downloading of the data
110
+     * @var array
111
+     */
112
+    private const FILE_TYPE_MAP = [
113
+        "mongodb"   => "openfoodfacts-mongodbdump.tar.gz",
114
+        "csv"       => "en.openfoodfacts.org.products.csv",
115
+        "rdf"       => "en.openfoodfacts.org.products.rdf"
116
+    ];
117
+
118
+    /**
119
+     * the constructor of the function
120
+     *
121
+     * @param string $api the environment to search
122
+     * @param string $geography this parameter represent the the country  code and the interface of the language
123
+     * @param LoggerInterface $logger this parameter define an logger
124
+     * @param ClientInterface|null $clientInterface
125
+     * @param CacheInterface|null $cacheInterface
126
+     */
127
+    public function __construct(
128
+        string $api = 'food',
129
+        string $geography = 'world',
130
+        LoggerInterface $logger = null,
131
+        ClientInterface $clientInterface = null,
132
+        CacheInterface $cacheInterface = null
133
+    )
134
+    {
135
+        $this->cache        = $cacheInterface;
136
+        $this->logger       = $logger ?? new NullLogger();
137
+        $this->httpClient   = $clientInterface ?? new Client();
138
+
139
+        $this->geoUrl     = sprintf(self::LIST_API[$api], $geography);
140
+        $this->geography  = $geography;
141
+        $this->currentAPI = $api;
142
+    }
143
+
144
+    /**
145
+     * This function allows you to perform tests
146
+     * The domain is correct and for testing purposes only
147
+     */
148
+    public function activeTestMode() : void
149
+    {
150
+        $this->geoUrl = 'https://world.openfoodfacts.net';
151
+        $this->authentification('off', 'off');
152
+    }
153
+
154
+    /**
155
+     * This function store the authentication parameter
156
+     * @param  string $username
157
+     * @param  string $password
158
+     */
159
+    public function authentification(string $username, string $password) :void
160
+    {
161
+        $this->auth = [
162
+            'user_id'   => $username,
163
+            'password'  => $password
164
+        ];
165
+    }
166
+
167
+    /**
168
+     * It's a magic function, it works only for facets
169
+     * @param string $name The name of the function
170
+     * @param void $arguments not use yet (probably needed for ingredients)
171
+     * @return Collection        The list of all documents found
172
+     * @throws InvalidArgumentException
173
+     * @throws BadRequestException
174
+     * @example getIngredients()
175
+     */
176
+    public function __call(string $name, $arguments) : Collection
177
+    {
178
+        //TODO : test with argument for ingredient
179
+        if (strpos($name, 'get') === 0) {
180
+            $facet = strtolower(substr($name, 3));
181
+            //TODO: what about PSR-12, e.g.: getNutritionGrade() ?
182
+
183
+            if (!in_array($facet, self::FACETS)) {
184
+                throw new BadRequestException('Facet "' . $facet . '" not found');
185
+            }
186
+
187
+            if ($facet === "purchase_places") {
188
+                $facet = "purchase-places";
189
+            } elseif ($facet === "packaging_codes") {
190
+                $facet = "packager-codes";
191
+            } elseif ($facet === "entry_dates") {
192
+                $facet = "entry-dates";
193
+            }
194
+
195
+            $url = $this->buildUrl(null, $facet, []);
196
+            $result = $this->fetch($url);
197
+            if ($facet !== 'ingredients') {
198
+                $result = [
199
+                    'products'  => $result['tags'],
200
+                    'count'     => $result['count'],
201
+                    'page'      => 1,
202
+                    'skip'      => 0,
203
+                    'page_size' => $result['count'],
204
+                ];
205
+            }
206
+            return new Collection($result, $this->currentAPI);
207
+        }
208
+
209
+        throw new BadRequestException('Call to undefined method '.__CLASS__.'::'.$name.'()');
210
+    }
211
+
212
+
213
+    /**
214
+     * this function search an Document by barcode
215
+     * @param string $barcode the barcode [\d]{13}
216
+     * @return Document         A Document if found
217
+     * @throws InvalidArgumentException
218
+     * @throws ProductNotFoundException
219
+     * @throws BadRequestException
220
+     */
221
+    public function getProduct(string $barcode) : Document
222
+    {
223
+        $url = $this->buildUrl('api', 'product', $barcode);
224
+
225
+        $rawResult = $this->fetch($url);
226
+        if ($rawResult['status'] === 0) {
227
+            //TODO: maybe return null here? (just throw an exception if something really went wrong?
228
+            throw new ProductNotFoundException("Product not found", 1);
229
+        }
230
+
231
+        return Document::createSpecificDocument($this->currentAPI, $rawResult['product']);
232
+    }
233
+
234
+    /**
235
+     * This function return a Collection of Document search by facets
236
+     * @param array $query list of facets with value
237
+     * @param integer $page Number of the page
238
+     * @return Collection     The list of all documents found
239
+     * @throws InvalidArgumentException
240
+     * @throws BadRequestException
241
+     */
242
+    public function getByFacets(array $query = [], int $page = 1) : Collection
243
+    {
244
+        if (empty($query)) {
245
+            return new Collection();
246
+        }
247
+        $search = [];
248
+        ksort($query);
249
+        foreach ($query as $key => $value) {
250
+            $search[] = $key;
251
+            $search[] = $value;
252
+        }
253
+
254
+        $url = $this->buildUrl(null, $search, $page);
255
+        $result = $this->fetch($url);
256
+        return new Collection($result, $this->currentAPI);
257
+    }
258
+
259
+    /**
260
+     * this function help you to add a new product (or update ??)
261
+     * @param array $postData The post data
262
+     * @return bool|string bool if the product has been added or the error message
263
+     * @throws BadRequestException
264
+     * @throws InvalidArgumentException
265
+     */
266
+    public function addNewProduct(array $postData)
267
+    {
268
+        if (!isset($postData['code']) || !isset($postData['product_name'])) {
269
+            throw new BadRequestException('code or product_name not found!');
270
+        }
271
+
272
+        $url = $this->buildUrl('cgi', 'product_jqm2.pl', []);
273
+        $result = $this->fetchPost($url, $postData);
274
+
275
+        if ($result['status_verbose'] === 'fields saved' && $result['status'] === 1) {
276
+            return true;
277
+        }
278
+        return $result['status_verbose'];
279
+    }
280
+
281
+    /**
282
+     * [uploadImage description]
283
+     * @param string $code the barcode of the product
284
+     * @param string $imageField th name of the image
285
+     * @param string $imagePath the path of the image
286
+     * @return array             the http post response (cast in array)
287
+     * @throws BadRequestException
288
+     * @throws InvalidArgumentException
289
+     */
290
+    public function uploadImage(string $code, string $imageField, string $imagePath)
291
+    {
292
+        //TODO : need test
293
+        if ($this->currentAPI !== 'food') {
294
+            throw new BadRequestException('not Available yet');
295
+        }
296
+        if (!in_array($imageField, ["front", "ingredients", "nutrition"])) {
297
+            throw new BadRequestException('ImageField not valid!');
298
+        }
299
+        if (!file_exists($imagePath)) {
300
+            throw new BadRequestException('Image not found');
301
+        }
302
+
303
+
304
+        $url = $this->buildUrl('cgi', 'product_image_upload.pl', []);
305
+        $postData = [
306
+            'code'                      => $code,
307
+            'imagefield'                => $imageField,
308
+            'imgupload_' . $imageField  => fopen($imagePath, 'r')
309
+        ];
310
+        return $this->fetchPost($url, $postData, true);
311
+    }
312
+
313
+    /**
314
+     * A search function
315
+     * @param string $search a search term (fulltext)
316
+     * @param integer $page Number of the page
317
+     * @param integer $pageSize The page size
318
+     * @param string $sortBy the sort
319
+     * @return Collection        The list of all documents found
320
+     * @throws BadRequestException
321
+     * @throws InvalidArgumentException
322
+     */
323
+    public function search(string $search, int $page = 1, int $pageSize = 20, string $sortBy = 'unique_scans')
324
+    {
325
+        $parameters = [
326
+            'search_terms'  => $search,
327
+            'page'          => $page,
328
+            'page_size'     => $pageSize,
329
+            'sort_by'       => $sortBy,
330
+            'json'          => '1',
331
+        ];
332
+
333
+        $url = $this->buildUrl('cgi', 'search.pl', $parameters);
334
+        $result = $this->fetch($url, false);
335
+        return new Collection($result, $this->currentAPI);
336
+    }
337
+
338
+    /**
339
+     * This function download all data from OpenFoodFact
340
+     * @param string $filePath the location where you want to put the stream
341
+     * @param string $fileType mongodb/csv/rdf
342
+     * @return bool             return true when download is complete
343
+     * @throws BadRequestException
344
+     */
345
+    public function downloadData(string $filePath, string $fileType = "mongodb")
346
+    {
347
+
348
+        if (!isset(self::FILE_TYPE_MAP[$fileType])) {
349
+            $this->logger->warning(
350
+                'OpenFoodFact - fetch - failed - File type not recognized!',
351
+                ['fileType' => $fileType, 'availableTypes' => self::FILE_TYPE_MAP]
352
+            );
353
+            throw new BadRequestException('File type not recognized!');
354
+        }
355
+
356
+        $url        = $this->buildUrl('data', self::FILE_TYPE_MAP[$fileType]);
357
+        try {
358
+            $response = $this->httpClient->request('get', $url, ['sink' => $filePath]);
359
+        } catch (GuzzleException $guzzleException) {
360
+            $this->logger->warning(sprintf('OpenFoodFact - fetch - failed - GET : %s', $url), ['exception' => $guzzleException]);
361
+            $exception = new BadRequestException($guzzleException->getMessage(), $guzzleException->getCode(), $guzzleException);
362
+
363
+            throw $exception;
364
+        }
365
+
366
+        $this->logger->info('OpenFoodFact - fetch - GET : ' . $url . ' - ' . $response->getStatusCode());
367
+
368
+        //TODO: validate response here (server may respond with 200 - OK but you might not get valid data as a response)
369
+
370
+        return $response->getStatusCode() == 200;
371
+    }
372
+
373
+
374
+    /**
375
+     * This private function do a http request
376
+     * @param string $url the url to fetch
377
+     * @param boolean $isJsonFile the request must be finish by '.json' ?
378
+     * @return array               return the result of the request in array format
379
+     * @throws InvalidArgumentException
380
+     * @throws BadRequestException
381
+     */
382
+    private function fetch(string $url, bool $isJsonFile = true) : array
383
+    {
384
+
385
+        $url        .= ($isJsonFile? '.json' : '');
386
+        $realUrl    = $url;
387
+        $cacheKey   = md5($realUrl);
388
+
389
+        if (!empty($this->cache) && $this->cache->has($cacheKey)) {
390
+            $cachedResult = $this->cache->get($cacheKey);
391
+            return $cachedResult;
392
+        }
393
+
394
+        $data = [
395
+            'on_stats' => function (TransferStats $stats) use (&$realUrl) {
396
+                // this function help to find redirection
397
+                // On redirect we lost some parameters like page
398
+                $realUrl= (string)$stats->getEffectiveUri();
399
+            }
400
+        ];
401
+        if ($this->auth) {
402
+            $data['auth'] = array_values($this->auth);
403
+        }
404
+
405
+        try {
406
+            $response = $this->httpClient->request('get', $url, $data);
407
+        } catch (GuzzleException $guzzleException) {
408
+            $this->logger->warning(sprintf('OpenFoodFact - fetch - failed - GET : %s', $url), ['exception' => $guzzleException]);
409
+            //TODO: What to do on a error? - return empty array?
410
+            $exception = new BadRequestException($guzzleException->getMessage(), $guzzleException->getCode(), $guzzleException);
411
+
412
+            throw $exception;
413
+        }
414
+        if ($realUrl !== $url) {
415
+            $this->logger->warning('OpenFoodFact - The url : '. $url . ' has been redirect to ' . $realUrl);
416
+            trigger_error('OpenFoodFact - Your request has been redirect');
417
+        }
418
+        $this->logger->info('OpenFoodFact - fetch - GET : ' . $url . ' - ' . $response->getStatusCode());
419
+
420
+        $jsonResult = json_decode($response->getBody(), true);
421
+
422
+        if (!empty($this->cache) && !empty($jsonResult)) {
423
+            $this->cache->set($cacheKey, $jsonResult);
424
+        }
425
+
426
+        return $jsonResult;
427
+    }
428
+
429
+    /**
430
+     * This function performs the same job of the "fetch" function except the call method and parameters
431
+     * @param string $url The url to fetch
432
+     * @param array $postData The post data
433
+     * @param boolean $isMultipart The data is multipart ?
434
+     * @return array               return the result of the request in array format
435
+     * @throws InvalidArgumentException
436
+     * @throws BadRequestException
437
+     */
438
+    private function fetchPost(string $url, array $postData, bool $isMultipart = false) : array
439
+    {
440
+        $data = [];
441
+        if ($this->auth) {
442
+            $data['auth'] = array_values($this->auth);
443
+        }
444
+        if ($isMultipart) {
445
+            foreach ($postData as $key => $value) {
446
+                $data['multipart'][] = [
447
+                    'name'      => $key,
448
+                    'contents'  => $value
449
+                ];
450
+            }
451
+        } else {
452
+            $data['form_params'] = $postData;
453
+        }
454
+
455
+        $cacheKey = md5($url . json_encode($data));
456
+
457
+        if (!empty($this->cache) && $this->cache->has($cacheKey)) {
458
+            return $this->cache->get($cacheKey);
459
+        }
460
+
461
+        try {
462
+            $response = $this->httpClient->request('post', $url, $data);
463
+        }catch (GuzzleException $guzzleException){
464
+            $exception = new BadRequestException($guzzleException->getMessage(), $guzzleException->getCode(), $guzzleException);
465
+
466
+            throw $exception;
467
+        }
468
+
469
+        $this->logger->info('OpenFoodFact - fetch - GET : ' . $url . ' - ' . $response->getStatusCode());
470
+
471
+        $jsonResult = json_decode($response->getBody(), true);
472
+
473
+        if (!empty($this->cache) && !empty($jsonResult)) {
474
+            $this->cache->set($cacheKey, $jsonResult);
475
+        }
476
+
477
+        return $jsonResult;
478
+    }
479
+
480
+    /**
481
+     * This private function generates an url according to the parameters
482
+     * @param  string|null $service
483
+     * @param  string|array|null $resourceType
484
+     * @param  string|array|null $parameters
485
+     * @return string               the generated url
486
+     */
487
+    private function buildUrl(string $service = null, $resourceType = null, $parameters = null) : string
488
+    {
489
+        $baseUrl = null;
490
+        switch ($service) {
491
+            case 'api':
492
+                $baseUrl = implode('/', [
493
+                  $this->geoUrl,
494
+                  $service,
495
+                  'v0',
496
+                  $resourceType,
497
+                  $parameters
498
+                ]);
499
+                break;
500
+            case 'data':
501
+                $baseUrl = implode('/', [
502
+                  $this->geoUrl,
503
+                  $service,
504
+                  $resourceType
505
+                ]);
506
+                break;
507
+            case 'cgi':
508
+                $baseUrl = implode('/', [
509
+                  $this->geoUrl,
510
+                  $service,
511
+                  $resourceType
512
+                ]);
513
+                $baseUrl .= '?' . http_build_query($parameters);
514
+                break;
515
+            case null:
516
+            default:
517
+                if (is_array($resourceType)) {
518
+                    $resourceType = implode('/', $resourceType);
519
+                }
520
+                if ($resourceType == 'ingredients') {
521
+                    //need test
522
+                    $resourceType = implode('/', ["state",  "complete", $resourceType]);
523
+                    $parameters   = 1;
524
+                }
525
+                $baseUrl = implode('/', array_filter([
526
+                    $this->geoUrl,
527
+                    $resourceType,
528
+                    $parameters
529
+                ], function ($value) {
530
+                    return !empty($value);
531
+                }));
532
+                break;
533
+        }
534
+        return $baseUrl;
535
+    }
536
+}

+ 133
- 0
openfoodfacts-php-0.2.3/src/Collection.php View File

@@ -0,0 +1,133 @@
1
+<?php
2
+
3
+namespace OpenFoodFacts;
4
+
5
+use Iterator;
6
+
7
+class Collection implements Iterator
8
+{
9
+
10
+    private $listDocuments  = null;
11
+    private $count          = null;
12
+    private $page           = null;
13
+    private $skip           = null;
14
+    private $pageSize       = null;
15
+
16
+    /**
17
+     * initialization of the collection
18
+     * @param array|null $data the raw data
19
+     * @param string|null $api  this information help to type the collection  (not use yet)
20
+     */
21
+    public function __construct(array $data = null, string $api = null)
22
+    {
23
+        $data = $data ?? [
24
+            'products'  => [],
25
+            'count'     => 0,
26
+            'page'      => 0,
27
+            'skip'      => 0,
28
+            'page_size' => 0,
29
+        ];
30
+        $this->listDocuments = [];
31
+
32
+        if (!empty($data['products'])) {
33
+            $currentApi = '';
34
+            if (null !== $api) {
35
+                $currentApi = $api;
36
+            }
37
+            foreach ($data['products'] as $document) {
38
+                if($document instanceof Document){
39
+                    $this->listDocuments[] = $document;
40
+                }elseif (is_array($document)){
41
+                    $this->listDocuments[] = Document::createSpecificDocument($currentApi, $document);
42
+                }else {
43
+                    throw new \InvalidArgumentException(sprintf('Would expect an OpenFoodFacts\Document Interface or Array here. Got: %s', gettype($document)));
44
+                }
45
+
46
+            }
47
+        }
48
+
49
+        $this->count    = $data['count'];
50
+        $this->page     = $data['page'];
51
+        $this->skip     = $data['skip'];
52
+        $this->pageSize = $data['page_size'];
53
+    }
54
+
55
+    /**
56
+     * @return int get the current page
57
+     */
58
+    public function getPage() : int
59
+    {
60
+        return $this->page;
61
+    }
62
+    /**
63
+     * @return int get the number of element skipped
64
+     */
65
+    public function getSkip() : int
66
+    {
67
+        return $this->skip;
68
+    }
69
+    /**
70
+     * @return int get the number of element by page for this collection
71
+     */
72
+    public function getPageSize() : int
73
+    {
74
+        return $this->pageSize;
75
+    }
76
+
77
+    /**
78
+     * @return int the number of element in this Collection
79
+     */
80
+    public function pageCount() : int
81
+    {
82
+        return count($this->listDocuments);
83
+    }
84
+
85
+    /**
86
+     * @return int the number of element for this search
87
+     */
88
+    public function searchCount() : int
89
+    {
90
+        return $this->count;
91
+    }
92
+
93
+    /**
94
+     * Implementation of Iterator
95
+     */
96
+
97
+    /**
98
+     * @inheritDoc
99
+     */
100
+    public function rewind()
101
+    {
102
+        reset($this->listDocuments);
103
+    }
104
+    /**
105
+     * @inheritDoc
106
+     */
107
+    public function current()
108
+    {
109
+        return current($this->listDocuments);
110
+    }
111
+    /**
112
+     * @inheritDoc
113
+     */
114
+    public function key()
115
+    {
116
+        return key($this->listDocuments);
117
+    }
118
+    /**
119
+     * @inheritDoc
120
+     */
121
+    public function next()
122
+    {
123
+        return next($this->listDocuments);
124
+    }
125
+    /**
126
+     * @inheritDoc
127
+     */
128
+    public function valid()
129
+    {
130
+        $key = key($this->listDocuments);
131
+        return ($key !== null && $key !== false);
132
+    }
133
+}

+ 83
- 0
openfoodfacts-php-0.2.3/src/Document.php View File

@@ -0,0 +1,83 @@
1
+<?php
2
+
3
+namespace OpenFoodFacts;
4
+
5
+/**
6
+ * In mongoDB all element are object, it not possible to define property.
7
+ * All property of the mongodb entity are store in one property of this class and the magic call try to access to it
8
+ */
9
+class Document
10
+{
11
+    use RecursiveSortingTrait;
12
+
13
+    /**
14
+     * the whole data
15
+     * @var array
16
+     */
17
+    private $data;
18
+
19
+    /**
20
+     * the whole data
21
+     * @var array
22
+     */
23
+    private $api;
24
+
25
+    /**
26
+     * Initialization the document and specify from which API it was extract
27
+     * @param array $data the whole data
28
+     * @param string $api the api name
29
+     */
30
+    public function __construct(array $data, string $api = null)
31
+    {
32
+        $this->data = $data;
33
+        $this->api  = $api;
34
+    }
35
+
36
+    /**
37
+     * @inheritDoc
38
+     */
39
+    public function __get(string $name)
40
+    {
41
+        return $this->data[$name];
42
+    }
43
+    /**
44
+     * @inheritDoc
45
+     */
46
+    public function __isset(string $name):bool
47
+    {
48
+        return isset($this->data[$name]);
49
+    }
50
+
51
+    /**
52
+     * Returns a sorted representation of the complete Document Data
53
+     * @return array
54
+     */
55
+    public function getData(): array
56
+    {
57
+        $this->recursiveSortArray($this->data);
58
+        return $this->data;
59
+    }
60
+
61
+    /**
62
+     * Returns a Document in the type regarding to the API used.
63
+     * May be a Child of "Document" e.g.: FoodDocument or ProductDocument
64
+     * @param string $apiIdentifier
65
+     * @param array  $data
66
+     * @return Document
67
+     */
68
+    public static function createSpecificDocument(string $apiIdentifier, array $data): Document
69
+    {
70
+        if ($apiIdentifier === '') {
71
+            return new Document($data, $apiIdentifier);
72
+        }
73
+
74
+        $className = "OpenFoodFacts\Document\\" . ucfirst($apiIdentifier) . 'Document';
75
+
76
+        if (class_exists($className) && is_subclass_of($className, Document::class)) {
77
+            return new $className($data, $apiIdentifier);
78
+        }
79
+
80
+        return new Document($data, $apiIdentifier);
81
+    }
82
+
83
+}

+ 10
- 0
openfoodfacts-php-0.2.3/src/Document/BeautyDocument.php View File

@@ -0,0 +1,10 @@
1
+<?php
2
+
3
+namespace OpenFoodFacts\Document;
4
+
5
+use OpenFoodFacts\Document;
6
+
7
+class BeautyDocument extends Document
8
+{
9
+
10
+}

+ 10
- 0
openfoodfacts-php-0.2.3/src/Document/FoodDocument.php View File

@@ -0,0 +1,10 @@
1
+<?php
2
+
3
+namespace OpenFoodFacts\Document;
4
+
5
+use OpenFoodFacts\Document;
6
+
7
+class FoodDocument extends Document
8
+{
9
+
10
+}

+ 10
- 0
openfoodfacts-php-0.2.3/src/Document/PetDocument.php View File

@@ -0,0 +1,10 @@
1
+<?php
2
+
3
+namespace OpenFoodFacts\Document;
4
+
5
+use OpenFoodFacts\Document;
6
+
7
+class PetDocument extends Document
8
+{
9
+
10
+}

+ 10
- 0
openfoodfacts-php-0.2.3/src/Document/ProductDocument.php View File

@@ -0,0 +1,10 @@
1
+<?php
2
+
3
+namespace OpenFoodFacts\Document;
4
+
5
+use OpenFoodFacts\Document;
6
+
7
+class ProductDocument extends Document
8
+{
9
+
10
+}

+ 14
- 0
openfoodfacts-php-0.2.3/src/Exception/BadRequestException.php View File

@@ -0,0 +1,14 @@
1
+<?php
2
+
3
+namespace OpenFoodFacts\Exception;
4
+
5
+
6
+use Exception;
7
+
8
+/**
9
+ * Just an exception class for the try catch
10
+ */
11
+class BadRequestException extends Exception
12
+{
13
+
14
+}

+ 14
- 0
openfoodfacts-php-0.2.3/src/Exception/ProductNotFoundException.php View File

@@ -0,0 +1,14 @@
1
+<?php
2
+
3
+namespace OpenFoodFacts\Exception;
4
+
5
+
6
+use Exception;
7
+
8
+/**
9
+ * Just an exception class for the try catch
10
+ */
11
+class ProductNotFoundException extends Exception
12
+{
13
+
14
+}

+ 23
- 0
openfoodfacts-php-0.2.3/src/FilesystemTrait.php View File

@@ -0,0 +1,23 @@
1
+<?php
2
+
3
+namespace OpenFoodFacts;
4
+
5
+trait FilesystemTrait
6
+{
7
+    function recursiveDeleteDirectory($dir)
8
+    {
9
+        if (is_dir($dir)) {
10
+            $objects = scandir($dir);
11
+            foreach ($objects as $object) {
12
+                if ($object != "." && $object != "..") {
13
+                    if (is_dir($dir . "/" . $object)) {
14
+                        $this->recursiveDeleteDirectory($dir . "/" . $object);
15
+                    } else {
16
+                        unlink($dir . "/" . $object);
17
+                    }
18
+                }
19
+            }
20
+            rmdir($dir);
21
+        }
22
+    }
23
+}

+ 38
- 0
openfoodfacts-php-0.2.3/src/RecursiveSortingTrait.php View File

@@ -0,0 +1,38 @@
1
+<?php
2
+
3
+namespace OpenFoodFacts;
4
+
5
+/**
6
+ * Trait RecursiveSortingTrait
7
+ */
8
+trait RecursiveSortingTrait
9
+{
10
+    /**
11
+     * @param array $arr
12
+     * @return bool
13
+     */
14
+    private function isAssoc(array $arr): bool
15
+    {
16
+        return array_keys($arr) !== range(0, count($arr) - 1);
17
+    }
18
+
19
+    /**
20
+     * Sorts referenced array of arrays in a recursive way for better understandability
21
+     * @param array $arr
22
+     * @see ksort
23
+     * @see asort
24
+     */
25
+    public function recursiveSortArray(array &$arr): void
26
+    {
27
+        if ($this->isAssoc($arr)) {
28
+            ksort($arr);
29
+        } else {
30
+            asort($arr);
31
+        }
32
+        foreach ($arr as &$a) {
33
+            if (is_array($a)) {
34
+                $this->recursiveSortArray($a);
35
+            }
36
+        }
37
+    }
38
+}

+ 178
- 0
openfoodfacts-php-0.2.3/tests/ApiFoodCacheTest.php View File

@@ -0,0 +1,178 @@
1
+<?php
2
+
3
+use GuzzleHttp\Exception\ServerException;
4
+use OpenFoodFacts\FilesystemTrait;
5
+use PHPUnit\Framework\TestCase;
6
+
7
+use OpenFoodFacts\Api;
8
+use OpenFoodFacts\Collection;
9
+use OpenFoodFacts\Document\FoodDocument;
10
+use OpenFoodFacts\Document;
11
+use OpenFoodFacts\Exception\{
12
+    ProductNotFoundException,
13
+    BadRequestException
14
+};
15
+
16
+
17
+use Monolog\Logger;
18
+use Monolog\Handler\StreamHandler;
19
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
20
+use Symfony\Component\Cache\Psr16Cache;
21
+use Symfony\Component\Console\Logger\ConsoleLogger;
22
+
23
+class ApiFoodCacheTest extends TestCase
24
+{
25
+    use FilesystemTrait;
26
+
27
+    /**
28
+     * @var Api
29
+     */
30
+    private $api;
31
+
32
+
33
+    protected function setUp()
34
+    {
35
+        @rmdir('tests/tmp');
36
+        @mkdir('tests/tmp');
37
+        @mkdir('tests/tmp/cache');
38
+        $log = new Logger('name');
39
+        $log->pushHandler(new StreamHandler('log/test.log'));
40
+        $psr6Cache = new FilesystemAdapter(sprintf('testrun_%u', rand(0, 1000)), 10, 'tests/tmp/cache');
41
+        $cache     = new Psr16Cache($psr6Cache);
42
+
43
+        $httpClient = new GuzzleHttp\Client([
44
+//            "http_errors" => false, // MUST not use as it crashes error handling
45
+            'Connection' => 'close',
46
+            CURLOPT_FORBID_REUSE => true,
47
+            CURLOPT_FRESH_CONNECT => true,
48
+            'defaults' => [
49
+                'headers' => [
50
+                    'CURLOPT_USERAGENT' => 'OFF - PHP - SDK - Unit Test',
51
+                ],
52
+            ],
53
+        ]);
54
+
55
+        $api = new Api('food', 'fr-en', $log, $httpClient, $cache);
56
+        $this->assertInstanceOf(Api::class, $api);
57
+        $this->api = $api;
58
+
59
+    }
60
+
61
+    public function testApi(): void
62
+    {
63
+
64
+        $prd = $this->api->getProduct('3057640385148');
65
+
66
+        $this->assertInstanceOf(FoodDocument::class, $prd);
67
+        $this->assertInstanceOf(Document::class, $prd);
68
+
69
+        $this->assertTrue(isset($prd->product_name));
70
+        $this->assertNotEmpty($prd->product_name);
71
+
72
+        try {
73
+            $product = $this->api->getProduct('305764038514800');
74
+            $this->assertTrue(false);
75
+        } catch (ProductNotFoundException $e) {
76
+            $this->assertTrue(true);
77
+        }
78
+
79
+        try {
80
+            $result = $this->api->downloadData('tests/mongodb', 'nopeFile');
81
+            $this->assertTrue(false);
82
+        } catch (BadRequestException $e) {
83
+            $this->assertEquals($e->getMessage(), 'File type not recognized!');
84
+        }
85
+
86
+        // $result = $this->api->downloadData('tests/tmp/mongodb');
87
+        // $this->assertTrue(true);
88
+    }
89
+
90
+    public function testApiCollection(): void
91
+    {
92
+
93
+        $collection = $this->api->getByFacets([]);
94
+        $this->assertInstanceOf(Collection::class, $collection);
95
+        $this->assertEquals($collection->pageCount(), 0);
96
+
97
+        try {
98
+            $collection = $this->api->getByFacets(['trace' => 'egg', 'country' => 'france'], 3);
99
+            $this->assertTrue(false);
100
+        } catch (\PHPUnit\Framework\Error\Notice $e) {
101
+            $this->assertEquals($e->getMessage(), 'OpenFoodFact - Your request has been redirect');
102
+        }
103
+
104
+        $collection = $this->api->getByFacets(['trace' => 'eggs', 'country' => 'france'], 3);
105
+        $this->assertInstanceOf(Collection::class, $collection);
106
+        $this->assertEquals($collection->pageCount(), 20);
107
+        $this->assertEquals($collection->getPage(), 3);
108
+        $this->assertEquals($collection->getSkip(), 40);
109
+        $this->assertEquals($collection->getPageSize(), 20);
110
+        $this->assertGreaterThan(1000, $collection->searchCount());
111
+
112
+        foreach ($collection as $key => $doc) {
113
+            if ($key > 1) {
114
+                break;
115
+            }
116
+
117
+            $this->assertInstanceOf(FoodDocument::class, $doc);
118
+            $this->assertInstanceOf(Document::class, $doc);
119
+
120
+        }
121
+
122
+    }
123
+
124
+    public function testApiSearch(): void
125
+    {
126
+
127
+        $collection = $this->api->search('volvic', 3, 30);
128
+        $this->assertInstanceOf(Collection::class, $collection);
129
+        $this->assertEquals($collection->pageCount(), 30);
130
+        $this->assertGreaterThan(100, $collection->searchCount());
131
+
132
+    }
133
+
134
+
135
+    public function testFacets(): void
136
+    {
137
+
138
+        $collection = $this->api->getIngredients();
139
+        $this->assertInstanceOf(Collection::class, $collection);
140
+        $this->assertEquals($collection->pageCount(), 20);
141
+        $this->assertEquals($collection->getPageSize(), 20);
142
+        $this->assertGreaterThan(70000, $collection->searchCount());
143
+
144
+        try {
145
+            $collection = $this->api->getIngredient();
146
+            $this->assertInstanceOf(Collection::class, $collection);
147
+            $this->assertTrue(false);
148
+        } catch (BadRequestException $e) {
149
+            $this->assertEquals($e->getMessage(), 'Facet "ingredient" not found');
150
+        }
151
+
152
+        $collection = $this->api->getPurchase_places();
153
+        $this->assertInstanceOf(Collection::class, $collection);
154
+        $collection = $this->api->getPackaging_codes();
155
+        $this->assertInstanceOf(Collection::class, $collection);
156
+        $collection = $this->api->getEntry_dates();
157
+        $this->assertInstanceOf(Collection::class, $collection);
158
+
159
+        try {
160
+            $collection = $this->api->getIngredient();
161
+            $this->assertTrue(false);
162
+        } catch (BadRequestException $e) {
163
+            $this->assertEquals($e->getMessage(), 'Facet "ingredient" not found');
164
+        }
165
+
166
+        try {
167
+            $collection = $this->api->nope();
168
+        } catch (\Exception $e) {
169
+            $this->assertTrue(true);
170
+        }
171
+    }
172
+
173
+    protected function tearDown()
174
+    {
175
+        $this->recursiveDeleteDirectory('tests/tmp');
176
+    }
177
+
178
+}

+ 248
- 0
openfoodfacts-php-0.2.3/tests/ApiFoodTest.php View File

@@ -0,0 +1,248 @@
1
+<?php
2
+
3
+use GuzzleHttp\Exception\ServerException;
4
+use OpenFoodFacts\FilesystemTrait;
5
+use PHPUnit\Framework\TestCase;
6
+
7
+use OpenFoodFacts\Api;
8
+use OpenFoodFacts\Collection;
9
+use OpenFoodFacts\Document\FoodDocument;
10
+use OpenFoodFacts\Document;
11
+use OpenFoodFacts\Exception\{
12
+    ProductNotFoundException,
13
+    BadRequestException
14
+};
15
+
16
+
17
+use Monolog\Logger;
18
+use Monolog\Handler\StreamHandler;
19
+
20
+class ApiFoodTest extends TestCase
21
+{
22
+
23
+    use FilesystemTrait;
24
+
25
+    private $api;
26
+
27
+    protected function setUp()
28
+    {
29
+        $log = new Logger('name');
30
+        $log->pushHandler(new StreamHandler('log/test.log'));
31
+
32
+        $this->api = new Api('food', 'fr-en', $log);
33
+        @rmdir('tests/tmp');
34
+        @mkdir('tests/tmp');
35
+    }
36
+
37
+    public function testApi(): void
38
+    {
39
+
40
+        $prd = $this->api->getProduct('3057640385148');
41
+
42
+        $this->assertInstanceOf(FoodDocument::class, $prd);
43
+        $this->assertInstanceOf(Document::class, $prd);
44
+        $this->assertTrue(isset($prd->product_name));
45
+        $this->assertNotEmpty($prd->product_name);
46
+
47
+        try {
48
+            $product = $this->api->getProduct('305764038514800');
49
+            $this->assertTrue(false);
50
+        } catch (ProductNotFoundException $e) {
51
+            $this->assertTrue(true);
52
+        }
53
+
54
+        try {
55
+            $result = $this->api->downloadData('tests/mongodb', 'nopeFile');
56
+            $this->assertTrue(false);
57
+        } catch (BadRequestException $e) {
58
+            $this->assertEquals($e->getMessage(), 'File type not recognized!');
59
+        }
60
+
61
+        // $result = $this->api->downloadData('tests/tmp/mongodb');
62
+        // $this->assertTrue(true);
63
+    }
64
+
65
+    public function testApiCollection(): void
66
+    {
67
+
68
+        $collection = $this->api->getByFacets([]);
69
+        $this->assertInstanceOf(Collection::class, $collection);
70
+        $this->assertEquals($collection->pageCount(), 0);
71
+
72
+        try {
73
+            $collection = $this->api->getByFacets(['trace' => 'egg', 'country' => 'france'], 3);
74
+            $this->assertTrue(false);
75
+        } catch (\PHPUnit\Framework\Error\Notice $e) {
76
+            $this->assertEquals($e->getMessage(), 'OpenFoodFact - Your request has been redirect');
77
+        }
78
+
79
+        $collection = $this->api->getByFacets(['trace' => 'eggs', 'country' => 'france'], 3);
80
+        $this->assertInstanceOf(Collection::class, $collection);
81
+        $this->assertEquals($collection->pageCount(), 20);
82
+        $this->assertEquals($collection->getPage(), 3);
83
+        $this->assertEquals($collection->getSkip(), 40);
84
+        $this->assertEquals($collection->getPageSize(), 20);
85
+        $this->assertGreaterThan(1000, $collection->searchCount());
86
+
87
+        foreach ($collection as $key => $doc) {
88
+            if ($key > 1) {
89
+                break;
90
+            }
91
+            $this->assertInstanceOf(FoodDocument::class, $doc);
92
+            $this->assertInstanceOf(Document::class, $doc);
93
+
94
+        }
95
+
96
+    }
97
+
98
+    public function testApiAddProduct(): void
99
+    {
100
+        $this->api->activeTestMode();
101
+        try {
102
+            $prd = $this->api->getProduct('3057640385148');
103
+            $this->assertInstanceOf(FoodDocument::class, $prd);
104
+            $this->assertInstanceOf(Document::class, $prd);
105
+        } catch (Exception $exception) {
106
+            if ($exception->getPrevious() instanceof ServerException && $exception->getPrevious()->getCode() === 503) {
107
+                $this->markTestSkipped(
108
+                    'Testing API currently not available.'
109
+                );
110
+            }
111
+        }
112
+
113
+        $postData = ['code' => $prd->code, 'product_name' => $prd->product_name];
114
+
115
+        $result = $this->api->addNewProduct($postData);
116
+        $this->assertTrue(is_bool($result));
117
+
118
+
119
+        $postData = ['product_name' => $prd->product_name];
120
+
121
+        try {
122
+            $result = $this->api->addNewProduct($postData);
123
+            $this->assertTrue(false);
124
+        } catch (BadRequestException $e) {
125
+            $this->assertTrue(true);
126
+        }
127
+        $postData = ['code' => '', 'product_name' => $prd->product_name];
128
+        $result   = $this->api->addNewProduct($postData);
129
+        $this->assertTrue(is_string($result));
130
+        $this->assertEquals($result, 'no code or invalid code');
131
+
132
+    }
133
+
134
+    public function testApiAddImage(): void
135
+    {
136
+
137
+        $this->api->activeTestMode();
138
+        try {
139
+            $prd = $this->api->getProduct('3057640385148');
140
+            $this->assertInstanceOf(Collection::class, $prd);
141
+        } catch (Exception $exception) {
142
+            if ($exception->getPrevious() instanceof ServerException && $exception->getPrevious()->getCode() === 503) {
143
+                $this->markTestSkipped(
144
+                    'Testing API currently not available.'
145
+                );
146
+            }
147
+        }
148
+
149
+        try {
150
+            $this->api->uploadImage('3057640385148', 'fronts', 'nothing');
151
+            $this->assertTrue(false);
152
+        } catch (BadRequestException $e) {
153
+            $this->assertEquals($e->getMessage(), 'ImageField not valid!');
154
+        }
155
+        try {
156
+            $this->api->uploadImage('3057640385148', 'front', 'nothing');
157
+            $this->assertTrue(false);
158
+        } catch (BadRequestException $e) {
159
+            $this->assertEquals($e->getMessage(), 'Image not found');
160
+        }
161
+        $file1 = $this->createRandomImage();
162
+
163
+        $result = $this->api->uploadImage('3057640385148', 'front', $file1);
164
+        $this->assertEquals($result['status'], 'status ok');
165
+        $this->assertTrue(isset($result['imagefield']));
166
+        $this->assertTrue(isset($result['image']));
167
+        $this->assertTrue(isset($result['image']['imgid']));
168
+
169
+
170
+    }
171
+
172
+    public function testApiSearch(): void
173
+    {
174
+
175
+        $collection = $this->api->search('volvic', 3, 30);
176
+        $this->assertInstanceOf(Collection::class, $collection);
177
+        $this->assertEquals($collection->pageCount(), 30);
178
+        $this->assertGreaterThan(100, $collection->searchCount());
179
+
180
+    }
181
+
182
+
183
+    public function testFacets(): void
184
+    {
185
+
186
+        $collection = $this->api->getIngredients();
187
+        $this->assertInstanceOf(Collection::class, $collection);
188
+        $this->assertEquals($collection->pageCount(), 20);
189
+        $this->assertEquals($collection->getPageSize(), 20);
190
+        $this->assertGreaterThan(70000, $collection->searchCount());
191
+
192
+        try {
193
+            $collection = $this->api->getIngredient();
194
+            $this->assertInstanceOf(Collection::class, $collection);
195
+            $this->assertTrue(false);
196
+        } catch (BadRequestException $e) {
197
+            $this->assertEquals($e->getMessage(), 'Facet "ingredient" not found');
198
+        }
199
+
200
+        $collection = $this->api->getPurchase_places();
201
+        $this->assertInstanceOf(Collection::class, $collection);
202
+        $collection = $this->api->getPackaging_codes();
203
+        $this->assertInstanceOf(Collection::class, $collection);
204
+        $collection = $this->api->getEntry_dates();
205
+        $this->assertInstanceOf(Collection::class, $collection);
206
+
207
+        try {
208
+            $collection = $this->api->getIngredient();
209
+            $this->assertTrue(false);
210
+        } catch (BadRequestException $e) {
211
+            $this->assertEquals($e->getMessage(), 'Facet "ingredient" not found');
212
+        }
213
+
214
+        try {
215
+            $collection = $this->api->nope();
216
+        } catch (\Exception $e) {
217
+            $this->assertTrue(true);
218
+        }
219
+    }
220
+
221
+
222
+    private function createRandomImage(): string
223
+    {
224
+
225
+        $width  = 400;
226
+        $height = 200;
227
+
228
+        $imageRes = imagecreatetruecolor($width, $height);
229
+        for ($row = 0; $row <= $height; $row++) {
230
+            for ($column = 0; $column <= $width; $column++) {
231
+                $colour = imagecolorallocate($imageRes, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255));
232
+                imagesetpixel($imageRes, $column, $row, $colour);
233
+            }
234
+        }
235
+        $path = 'tests/tmp/image_' . time() . '.jpg';
236
+        if (imagejpeg($imageRes, $path)) {
237
+            return $path;
238
+        }
239
+        throw new \Exception("Error Processing Request", 1);
240
+
241
+    }
242
+
243
+    protected function tearDown()
244
+    {
245
+        $this->recursiveDeleteDirectory('tests/tmp');
246
+    }
247
+
248
+}

+ 80
- 0
openfoodfacts-php-0.2.3/tests/ApiPetTest.php View File

@@ -0,0 +1,80 @@
1
+<?php
2
+
3
+use OpenFoodFacts\FilesystemTrait;
4
+use PHPUnit\Framework\TestCase;
5
+
6
+use OpenFoodFacts\Api;
7
+use OpenFoodFacts\Collection;
8
+use OpenFoodFacts\Document\PetDocument;
9
+use OpenFoodFacts\Document;
10
+use OpenFoodFacts\Exception\{
11
+    ProductNotFoundException,
12
+    BadRequestException
13
+};
14
+
15
+
16
+use Monolog\Logger;
17
+use Monolog\Handler\StreamHandler;
18
+
19
+class ApiPetTest extends TestCase
20
+{
21
+
22
+    use FilesystemTrait;
23
+
24
+    private $api;
25
+
26
+    protected function setUp()
27
+    {
28
+        $log = new Logger('name');
29
+        $log->pushHandler(new StreamHandler('log/test.log'));
30
+
31
+        $this->api = new Api('pet', 'fr', $log);
32
+
33
+        foreach (glob('tests/images/*') as $file) {
34
+            unlink($file);
35
+        }
36
+    }
37
+
38
+    public function testApi()
39
+    {
40
+
41
+        $prd = $this->api->getProduct('7613035799738');
42
+
43
+        $this->assertInstanceOf(PetDocument::class, $prd);
44
+        $this->assertInstanceOf(Document::class, $prd);
45
+        $this->assertTrue(isset($prd->product_name));
46
+        $this->assertNotEmpty($prd->product_name);
47
+
48
+    }
49
+
50
+    public function testApiAddImage()
51
+    {
52
+        try {
53
+            $this->api->uploadImage('7613035799738', 'fronts', 'nothing');
54
+            $this->assertTrue(false);
55
+        } catch (BadRequestException $e) {
56
+            $this->assertEquals($e->getMessage(), 'not Available yet');
57
+            $this->markTestSkipped(
58
+                $e->getMessage()
59
+            );
60
+        }
61
+
62
+    }
63
+
64
+    public function testApiSearch()
65
+    {
66
+
67
+        $collection = $this->api->search('chat', 3, 30);
68
+
69
+        $this->assertInstanceOf(Collection::class, $collection);
70
+        $this->assertEquals($collection->pageCount(), 30);
71
+        $this->assertGreaterThan(100, $collection->searchCount());
72
+
73
+    }
74
+
75
+    protected function tearDown()
76
+    {
77
+        $this->recursiveDeleteDirectory('tests/tmp');
78
+    }
79
+
80
+}

Loading…
Cancel
Save