From 7f23ccb69d1957e0f968acbc08b695656cf5f5ab Mon Sep 17 00:00:00 2001 From: paulkned Date: Mon, 29 Jun 2020 03:17:58 +0200 Subject: [PATCH] Added support for the WEBSERVICE function (#1409) Co-authored-by: Paul Kievits --- CHANGELOG.md | 1 + composer.json | 3 +- composer.lock | 294 +++++++++++++++++- .../Calculation/Calculation.php | 5 + src/PhpSpreadsheet/Calculation/Category.php | 1 + src/PhpSpreadsheet/Calculation/Web.php | 53 ++++ src/PhpSpreadsheet/Settings.php | 29 ++ .../Functions/Web/WebServiceTest.php | 54 ++++ .../DocumentGeneratorTest.php | 5 + tests/data/Calculation/Web/WEBSERVICE.php | 28 ++ 10 files changed, 471 insertions(+), 2 deletions(-) create mode 100644 src/PhpSpreadsheet/Calculation/Web.php create mode 100644 tests/PhpSpreadsheetTests/Calculation/Functions/Web/WebServiceTest.php create mode 100644 tests/data/Calculation/Web/WEBSERVICE.php diff --git a/CHANGELOG.md b/CHANGELOG.md index b97431c5..f667053f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Add Cell Address Helper to provide conversions between the R1C1 and A1 address formats [#1558](https://github.com/PHPOffice/PhpSpreadsheet/pull/1558) - Add ability to edit Html/Pdf before saving [#1499](https://github.com/PHPOffice/PhpSpreadsheet/pull/1499) - Add ability to set codepage explicitly for BIFF5 [#1018](https://github.com/PHPOffice/PhpSpreadsheet/issues/1018) +- Added support for the WEBSERVICE function ### Fixed diff --git a/composer.json b/composer.json index b480c082..d4810ce6 100644 --- a/composer.json +++ b/composer.json @@ -56,7 +56,8 @@ "maennchen/zipstream-php": "^2.1", "markbaker/complex": "^1.4", "markbaker/matrix": "^1.2", - "psr/simple-cache": "^1.0" + "psr/simple-cache": "^1.0", + "guzzlehttp/guzzle": "^7.0" }, "require-dev": { "dompdf/dompdf": "^0.8.5", diff --git a/composer.lock b/composer.lock index 9f5ae4aa..99de82bf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,211 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4103c8180a2e28881d3dbb45e835e863", + "content-hash": "b7ea4dea7ce2e1c2299029fe978d2173", "packages": [ + { + "name": "guzzlehttp/guzzle", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "2d9d3c186a6637a43193e66b097c50e4451eaab2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/2d9d3c186a6637a43193e66b097c50e4451eaab2", + "reference": "2d9d3c186a6637a43193e66b097c50e4451eaab2", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.6.1", + "php": "^7.2.5", + "psr/http-client": "^1.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.0", + "ext-curl": "*", + "php-http/client-integration-tests": "dev-phpunit8", + "phpunit/phpunit": "^8.5.5", + "psr/log": "^1.1" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "time": "2020-06-27T10:33:25+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" + }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2019-07-01T23:21:34+00:00" + }, { "name": "maennchen/zipstream-php", "version": "2.1.0", @@ -277,6 +480,55 @@ ], "time": "2020-02-14T08:15:52+00:00" }, + { + "name": "psr/http-client", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "496a823ef742b632934724bf769560c2a5c7c44e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/496a823ef742b632934724bf769560c2a5c7c44e", + "reference": "496a823ef742b632934724bf769560c2a5c7c44e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "time": "2018-10-30T23:29:13+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -375,6 +627,46 @@ ], "time": "2017-10-23T01:57:42+00:00" }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2019-03-08T08:55:37+00:00" + }, { "name": "symfony/polyfill-mbstring", "version": "v1.17.1", diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index ed630354..a8f93a88 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -2184,6 +2184,11 @@ class Calculation 'functionCall' => [LookupRef::class, 'VLOOKUP'], 'argumentCount' => '3,4', ], + 'WEBSERVICE' => [ + 'category' => Category::CATEGORY_WEB, + 'functionCall' => [Web::class, 'WEBSERVICE'], + 'argumentCount' => '1', + ], 'WEEKDAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, 'functionCall' => [DateTime::class, 'WEEKDAY'], diff --git a/src/PhpSpreadsheet/Calculation/Category.php b/src/PhpSpreadsheet/Calculation/Category.php index 7574cb47..96bb72ad 100644 --- a/src/PhpSpreadsheet/Calculation/Category.php +++ b/src/PhpSpreadsheet/Calculation/Category.php @@ -16,4 +16,5 @@ abstract class Category const CATEGORY_MATH_AND_TRIG = 'Math and Trig'; const CATEGORY_STATISTICAL = 'Statistical'; const CATEGORY_TEXT_AND_DATA = 'Text and Data'; + const CATEGORY_WEB = 'Web'; } diff --git a/src/PhpSpreadsheet/Calculation/Web.php b/src/PhpSpreadsheet/Calculation/Web.php new file mode 100644 index 00000000..9f0faf99 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Web.php @@ -0,0 +1,53 @@ + 2048) { + return Functions::VALUE(); // Invalid URL length + } + + if (!preg_match('/^http[s]?:\/\//', $url)) { + return Functions::VALUE(); // Invalid protocol + } + + // Get results from the the webservice + $client = Settings::getHttpClient(); + $request = new Request('GET', $url); + + try { + $response = $client->sendRequest($request); + } catch (ClientExceptionInterface $e) { + return Functions::VALUE(); // cURL error + } + + if ($response->getStatusCode() != 200) { + return Functions::VALUE(); // cURL error + } + + $output = (string) $response->getBody(); + if (strlen($output) > 32767) { + return Functions::VALUE(); // Output not a string or too long + } + + return $output; + } +} diff --git a/src/PhpSpreadsheet/Settings.php b/src/PhpSpreadsheet/Settings.php index 4e0c91ef..15218c72 100644 --- a/src/PhpSpreadsheet/Settings.php +++ b/src/PhpSpreadsheet/Settings.php @@ -2,9 +2,11 @@ namespace PhpOffice\PhpSpreadsheet; +use GuzzleHttp\Client; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Chart\Renderer\IRenderer; use PhpOffice\PhpSpreadsheet\Collection\Memory; +use Psr\Http\Client\ClientInterface; use Psr\SimpleCache\CacheInterface; class Settings @@ -42,6 +44,13 @@ class Settings */ private static $cache; + /** + * The HTTP client implementation to be used for network request. + * + * @var ClientInterface + */ + private static $client; + /** * Set the locale code to use for formula translations and any special formatting. * @@ -156,4 +165,24 @@ class Settings return self::$cache; } + + /** + * Set the HTTP client implementation to be used for network request. + */ + public static function setHttpClient(ClientInterface $httpClient): void + { + self::$client = $httpClient; + } + + /** + * Get the HTTP client implementation to be used for network request. + */ + public static function getHttpClient(): ClientInterface + { + if (!self::$client) { + self::$client = new Client(); + } + + return self::$client; + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Web/WebServiceTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Web/WebServiceTest.php new file mode 100644 index 00000000..acc83cff --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Web/WebServiceTest.php @@ -0,0 +1,54 @@ + $handlerStack]); + } + + protected function setUp(): void + { + Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); + } + + /** + * @dataProvider providerWEBSERVICE + */ + public function testWEBSERVICE(string $expectedResult, string $url): void + { + Settings::setHttpClient(self::$client); + $result = Web::WEBSERVICE($url); + self::assertEquals($expectedResult, $result); + } + + public function providerWEBSERVICE(): array + { + return require 'tests/data/Calculation/Web/WEBSERVICE.php'; + } +} diff --git a/tests/PhpSpreadsheetTests/DocumentGeneratorTest.php b/tests/PhpSpreadsheetTests/DocumentGeneratorTest.php index ac9af838..8a34f1c0 100644 --- a/tests/PhpSpreadsheetTests/DocumentGeneratorTest.php +++ b/tests/PhpSpreadsheetTests/DocumentGeneratorTest.php @@ -127,6 +127,11 @@ Excel Function | PhpSpreadsheet Function Excel Function | PhpSpreadsheet Function --------------------|------------------------------------------- +## CATEGORY_WEB + +Excel Function | PhpSpreadsheet Function +--------------------|------------------------------------------- + EXPECTED ], diff --git a/tests/data/Calculation/Web/WEBSERVICE.php b/tests/data/Calculation/Web/WEBSERVICE.php new file mode 100644 index 00000000..6d9934da --- /dev/null +++ b/tests/data/Calculation/Web/WEBSERVICE.php @@ -0,0 +1,28 @@ +