Introduce an experimental GraphQL API.
- Uses the webonyx/graphql-php framework. - Must be enabled at Settings - Advanced - GraphQL API. - Entry point is POST at /wp-json/wc/graphql/api - Input is a JSON object with "query" and optional "variables" fields. - Code lives at src/Internal/GraphQL. - Same authentication token/secrets as the REST API are used. - Introduces a new "woocommerce_graphql_check_permissions" filter. - Add ?verbose_errors to the url to get the exception message and a stack trace in case of internal error (for this to work either the user must be in the administrators role or WP_DEBUG must be set)
This commit is contained in:
parent
54edb576d5
commit
d6118841c8
|
@ -33,6 +33,7 @@
|
|||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"vimeo/psalm": "^4.4"
|
||||
},
|
||||
"default-branch": true,
|
||||
"bin": [
|
||||
"bin/mozart"
|
||||
],
|
||||
|
@ -53,6 +54,10 @@
|
|||
}
|
||||
],
|
||||
"description": "Composes all dependencies as a package inside a WordPress plugin",
|
||||
"support": {
|
||||
"issues": "https://github.com/coenjacobs/mozart/issues",
|
||||
"source": "https://github.com/coenjacobs/mozart/tree/master"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/coenjacobs",
|
||||
|
@ -144,6 +149,10 @@
|
|||
"sftp",
|
||||
"storage"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/thephpleague/flysystem/issues",
|
||||
"source": "https://github.com/thephpleague/flysystem/tree/1.x"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://offset.earth/frankdejonge",
|
||||
|
@ -192,6 +201,10 @@
|
|||
}
|
||||
],
|
||||
"description": "Mime-type detection for Flysystem",
|
||||
"support": {
|
||||
"issues": "https://github.com/thephpleague/mime-type-detection/issues",
|
||||
"source": "https://github.com/thephpleague/mime-type-detection/tree/1.7.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/frankdejonge",
|
||||
|
@ -246,20 +259,24 @@
|
|||
"container-interop",
|
||||
"psr"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-fig/container/issues",
|
||||
"source": "https://github.com/php-fig/container/tree/1.1.1"
|
||||
},
|
||||
"time": "2021-03-05T17:36:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v5.2.4",
|
||||
"version": "v5.2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "d6d0cc30d8c0fda4e7b213c20509b0159a8f4556"
|
||||
"reference": "938ebbadae1b0a9c9d1ec313f87f9708609f1b79"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/d6d0cc30d8c0fda4e7b213c20509b0159a8f4556",
|
||||
"reference": "d6d0cc30d8c0fda4e7b213c20509b0159a8f4556",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/938ebbadae1b0a9c9d1ec313f87f9708609f1b79",
|
||||
"reference": "938ebbadae1b0a9c9d1ec313f87f9708609f1b79",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -326,6 +343,9 @@
|
|||
"console",
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v5.2.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
|
@ -340,7 +360,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-02-23T10:08:49+00:00"
|
||||
"time": "2021-03-06T13:42:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
|
@ -384,6 +404,9 @@
|
|||
],
|
||||
"description": "Finds files and directories via an intuitive fluent interface",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/finder/tree/v5.2.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
|
@ -460,6 +483,9 @@
|
|||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
|
@ -538,6 +564,9 @@
|
|||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
|
@ -619,6 +648,9 @@
|
|||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
|
@ -696,6 +728,9 @@
|
|||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
|
@ -772,6 +807,9 @@
|
|||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
|
@ -852,6 +890,9 @@
|
|||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
|
@ -928,6 +969,9 @@
|
|||
"interoperability",
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/service-contracts/tree/master"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
|
@ -1008,6 +1052,9 @@
|
|||
"utf-8",
|
||||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v5.2.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
|
@ -1037,5 +1084,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "7.3"
|
||||
},
|
||||
"plugin-api-version": "1.1.0"
|
||||
"plugin-api-version": "2.0.0"
|
||||
}
|
||||
|
|
|
@ -71,6 +71,10 @@
|
|||
"stylecheck",
|
||||
"tests"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues",
|
||||
"source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer"
|
||||
},
|
||||
"time": "2020-06-25T14:57:39+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -129,6 +133,10 @@
|
|||
"phpcs",
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues",
|
||||
"source": "https://github.com/PHPCompatibility/PHPCompatibility"
|
||||
},
|
||||
"time": "2019-12-27T09:44:58+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -181,6 +189,10 @@
|
|||
"polyfill",
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues",
|
||||
"source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie"
|
||||
},
|
||||
"time": "2021-02-15T10:24:51+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -231,6 +243,10 @@
|
|||
"standards",
|
||||
"wordpress"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues",
|
||||
"source": "https://github.com/PHPCompatibility/PHPCompatibilityWP"
|
||||
},
|
||||
"time": "2019-08-28T14:22:28+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -282,6 +298,11 @@
|
|||
"phpcs",
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
|
||||
"source": "https://github.com/squizlabs/PHP_CodeSniffer",
|
||||
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
|
||||
},
|
||||
"time": "2020-10-23T02:01:07+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -322,6 +343,10 @@
|
|||
"woocommerce",
|
||||
"wordpress"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/woocommerce/woocommerce-sniffs/issues",
|
||||
"source": "https://github.com/woocommerce/woocommerce-sniffs/tree/master"
|
||||
},
|
||||
"time": "2020-08-06T18:23:45+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -368,6 +393,11 @@
|
|||
"standards",
|
||||
"wordpress"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues",
|
||||
"source": "https://github.com/WordPress/WordPress-Coding-Standards",
|
||||
"wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki"
|
||||
},
|
||||
"time": "2020-05-13T23:57:56+00:00"
|
||||
}
|
||||
],
|
||||
|
@ -381,5 +411,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "7.0"
|
||||
},
|
||||
"plugin-api-version": "1.1.0"
|
||||
"plugin-api-version": "2.0.0"
|
||||
}
|
||||
|
|
|
@ -59,6 +59,10 @@
|
|||
"constructor",
|
||||
"instantiate"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/instantiator/issues",
|
||||
"source": "https://github.com/doctrine/instantiator/tree/master"
|
||||
},
|
||||
"time": "2015-06-14T21:17:01+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -104,6 +108,10 @@
|
|||
"object",
|
||||
"object graph"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/myclabs/DeepCopy/issues",
|
||||
"source": "https://github.com/myclabs/DeepCopy/tree/1.x"
|
||||
},
|
||||
"time": "2017-10-19T19:58:43+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -159,6 +167,10 @@
|
|||
}
|
||||
],
|
||||
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
|
||||
"support": {
|
||||
"issues": "https://github.com/phar-io/manifest/issues",
|
||||
"source": "https://github.com/phar-io/manifest/tree/master"
|
||||
},
|
||||
"time": "2017-03-05T18:14:27+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -206,6 +218,10 @@
|
|||
}
|
||||
],
|
||||
"description": "Library for handling version information and constraints",
|
||||
"support": {
|
||||
"issues": "https://github.com/phar-io/version/issues",
|
||||
"source": "https://github.com/phar-io/version/tree/master"
|
||||
},
|
||||
"time": "2017-03-05T17:38:23+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -260,6 +276,10 @@
|
|||
"reflection",
|
||||
"static analysis"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
|
||||
"source": "https://github.com/phpDocumentor/ReflectionCommon/tree/master"
|
||||
},
|
||||
"time": "2017-09-11T18:02:19+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -312,6 +332,10 @@
|
|||
}
|
||||
],
|
||||
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
|
||||
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/release/4.x"
|
||||
},
|
||||
"time": "2019-12-28T18:55:12+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -357,6 +381,10 @@
|
|||
"email": "me@mikevanriel.com"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
|
||||
"source": "https://github.com/phpDocumentor/TypeResolver/tree/master"
|
||||
},
|
||||
"time": "2017-12-30T13:23:38+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -420,6 +448,10 @@
|
|||
"spy",
|
||||
"stub"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpspec/prophecy/issues",
|
||||
"source": "https://github.com/phpspec/prophecy/tree/v1.10.3"
|
||||
},
|
||||
"time": "2020-03-05T15:02:03+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -483,6 +515,10 @@
|
|||
"testing",
|
||||
"xunit"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/5.3"
|
||||
},
|
||||
"time": "2018-04-06T15:36:58+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -530,6 +566,11 @@
|
|||
"filesystem",
|
||||
"iterator"
|
||||
],
|
||||
"support": {
|
||||
"irc": "irc://irc.freenode.net/phpunit",
|
||||
"issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
|
||||
"source": "https://github.com/sebastianbergmann/php-file-iterator/tree/1.4.5"
|
||||
},
|
||||
"time": "2017-11-27T13:52:08+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -571,6 +612,10 @@
|
|||
"keywords": [
|
||||
"template"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/php-text-template/issues",
|
||||
"source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1"
|
||||
},
|
||||
"time": "2015-06-21T13:50:34+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -620,6 +665,10 @@
|
|||
"keywords": [
|
||||
"timer"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/php-timer/issues",
|
||||
"source": "https://github.com/sebastianbergmann/php-timer/tree/master"
|
||||
},
|
||||
"time": "2017-02-26T11:10:40+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -669,6 +718,10 @@
|
|||
"keywords": [
|
||||
"tokenizer"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/php-token-stream/issues",
|
||||
"source": "https://github.com/sebastianbergmann/php-token-stream/tree/master"
|
||||
},
|
||||
"abandoned": true,
|
||||
"time": "2017-11-27T05:48:46+00:00"
|
||||
},
|
||||
|
@ -754,6 +807,10 @@
|
|||
"testing",
|
||||
"xunit"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/6.5.14"
|
||||
},
|
||||
"time": "2019-02-01T05:22:47+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -813,6 +870,10 @@
|
|||
"mock",
|
||||
"xunit"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit-mock-objects/issues",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit-mock-objects/tree/5.0.10"
|
||||
},
|
||||
"abandoned": true,
|
||||
"time": "2018-08-09T05:50:03+00:00"
|
||||
},
|
||||
|
@ -859,6 +920,10 @@
|
|||
],
|
||||
"description": "Looks up which function or method a line of code belongs to",
|
||||
"homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
|
||||
"source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
|
@ -929,6 +994,10 @@
|
|||
"compare",
|
||||
"equality"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/comparator/issues",
|
||||
"source": "https://github.com/sebastianbergmann/comparator/tree/master"
|
||||
},
|
||||
"time": "2018-02-01T13:46:46+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -981,6 +1050,10 @@
|
|||
"keywords": [
|
||||
"diff"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/diff/issues",
|
||||
"source": "https://github.com/sebastianbergmann/diff/tree/master"
|
||||
},
|
||||
"time": "2017-08-03T08:09:46+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -1031,6 +1104,10 @@
|
|||
"environment",
|
||||
"hhvm"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/environment/issues",
|
||||
"source": "https://github.com/sebastianbergmann/environment/tree/master"
|
||||
},
|
||||
"time": "2017-07-01T08:51:00+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -1098,6 +1175,10 @@
|
|||
"export",
|
||||
"exporter"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/exporter/issues",
|
||||
"source": "https://github.com/sebastianbergmann/exporter/tree/3.1.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
|
@ -1155,6 +1236,10 @@
|
|||
"keywords": [
|
||||
"global state"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/global-state/issues",
|
||||
"source": "https://github.com/sebastianbergmann/global-state/tree/2.0.0"
|
||||
},
|
||||
"time": "2017-04-27T15:39:26+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -1202,6 +1287,10 @@
|
|||
],
|
||||
"description": "Traverses array structures and object graphs to enumerate all referenced objects",
|
||||
"homepage": "https://github.com/sebastianbergmann/object-enumerator/",
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
|
||||
"source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
|
@ -1253,6 +1342,10 @@
|
|||
],
|
||||
"description": "Allows reflection of object attributes, including inherited and non-public ones",
|
||||
"homepage": "https://github.com/sebastianbergmann/object-reflector/",
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/object-reflector/issues",
|
||||
"source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
|
@ -1312,6 +1405,10 @@
|
|||
],
|
||||
"description": "Provides functionality to recursively process PHP variables",
|
||||
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
|
||||
"source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
|
@ -1360,6 +1457,10 @@
|
|||
],
|
||||
"description": "Provides a list of PHP built-in functions that operate on resources",
|
||||
"homepage": "https://www.github.com/sebastianbergmann/resource-operations",
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/resource-operations/issues",
|
||||
"source": "https://github.com/sebastianbergmann/resource-operations/tree/master"
|
||||
},
|
||||
"time": "2015-07-28T20:34:47+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -1403,6 +1504,10 @@
|
|||
],
|
||||
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
|
||||
"homepage": "https://github.com/sebastianbergmann/version",
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/version/issues",
|
||||
"source": "https://github.com/sebastianbergmann/version/tree/master"
|
||||
},
|
||||
"time": "2016-10-03T07:35:21+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -1465,6 +1570,9 @@
|
|||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.19.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
|
@ -1519,6 +1627,10 @@
|
|||
}
|
||||
],
|
||||
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
|
||||
"support": {
|
||||
"issues": "https://github.com/theseer/tokenizer/issues",
|
||||
"source": "https://github.com/theseer/tokenizer/tree/master"
|
||||
},
|
||||
"time": "2019-06-13T22:48:21+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -1568,6 +1680,10 @@
|
|||
"check",
|
||||
"validate"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/webmozarts/assert/issues",
|
||||
"source": "https://github.com/webmozarts/assert/tree/1.9.1"
|
||||
},
|
||||
"time": "2020-07-08T17:02:28+00:00"
|
||||
}
|
||||
],
|
||||
|
@ -1581,5 +1697,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "7.0"
|
||||
},
|
||||
"plugin-api-version": "1.1.0"
|
||||
"plugin-api-version": "2.0.0"
|
||||
}
|
||||
|
|
|
@ -9,16 +9,16 @@
|
|||
"packages-dev": [
|
||||
{
|
||||
"name": "gettext/gettext",
|
||||
"version": "v4.8.3",
|
||||
"version": "v4.8.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-gettext/Gettext.git",
|
||||
"reference": "57ff4fb16647e78e80a5909fe3c190f1c3110321"
|
||||
"reference": "58bc0f7f37e78efb0f9758f93d4a0f669f0f84a1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-gettext/Gettext/zipball/57ff4fb16647e78e80a5909fe3c190f1c3110321",
|
||||
"reference": "57ff4fb16647e78e80a5909fe3c190f1c3110321",
|
||||
"url": "https://api.github.com/repos/php-gettext/Gettext/zipball/58bc0f7f37e78efb0f9758f93d4a0f669f0f84a1",
|
||||
"reference": "58bc0f7f37e78efb0f9758f93d4a0f669f0f84a1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -67,7 +67,26 @@
|
|||
"po",
|
||||
"translation"
|
||||
],
|
||||
"time": "2020-11-18T22:35:49+00:00"
|
||||
"support": {
|
||||
"email": "oom@oscarotero.com",
|
||||
"issues": "https://github.com/oscarotero/Gettext/issues",
|
||||
"source": "https://github.com/php-gettext/Gettext/tree/v4.8.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://paypal.me/oscarotero",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/oscarotero",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/misteroom",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2021-03-10T19:35:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "gettext/languages",
|
||||
|
@ -128,6 +147,10 @@
|
|||
"translations",
|
||||
"unicode"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-gettext/Languages/issues",
|
||||
"source": "https://github.com/php-gettext/Languages/tree/2.6.0"
|
||||
},
|
||||
"time": "2019-11-13T10:30:21+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -173,6 +196,10 @@
|
|||
}
|
||||
],
|
||||
"description": "Peast is PHP library that generates AST for JavaScript code",
|
||||
"support": {
|
||||
"issues": "https://github.com/mck89/peast/issues",
|
||||
"source": "https://github.com/mck89/peast/tree/v1.12.0"
|
||||
},
|
||||
"time": "2021-01-08T15:16:19+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -219,6 +246,10 @@
|
|||
"mustache",
|
||||
"templating"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/bobthecow/mustache.php/issues",
|
||||
"source": "https://github.com/bobthecow/mustache.php/tree/master"
|
||||
},
|
||||
"time": "2019-11-23T21:40:31+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -268,6 +299,10 @@
|
|||
"iri",
|
||||
"sockets"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/rmccue/Requests/issues",
|
||||
"source": "https://github.com/rmccue/Requests/tree/master"
|
||||
},
|
||||
"time": "2016-10-13T00:11:37+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -317,6 +352,9 @@
|
|||
],
|
||||
"description": "Symfony Finder Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/finder/tree/3.3"
|
||||
},
|
||||
"time": "2017-06-01T21:01:25+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -374,6 +412,10 @@
|
|||
],
|
||||
"description": "Provides internationalization tools for WordPress projects.",
|
||||
"homepage": "https://github.com/wp-cli/i18n-command",
|
||||
"support": {
|
||||
"issues": "https://github.com/wp-cli/i18n-command/issues",
|
||||
"source": "https://github.com/wp-cli/i18n-command/tree/v2.2.6"
|
||||
},
|
||||
"time": "2020-12-07T19:28:27+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -422,6 +464,9 @@
|
|||
],
|
||||
"description": "A simple YAML loader/dumper class for PHP (WP-CLI fork)",
|
||||
"homepage": "https://github.com/mustangostang/spyc/",
|
||||
"support": {
|
||||
"source": "https://github.com/wp-cli/spyc/tree/autoload"
|
||||
},
|
||||
"time": "2017-04-25T11:26:20+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -472,6 +517,10 @@
|
|||
"cli",
|
||||
"console"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/wp-cli/php-cli-tools/issues",
|
||||
"source": "https://github.com/wp-cli/php-cli-tools/tree/v0.11.12"
|
||||
},
|
||||
"time": "2021-03-03T12:43:49+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -534,6 +583,11 @@
|
|||
"cli",
|
||||
"wordpress"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://make.wordpress.org/cli/handbook/",
|
||||
"issues": "https://github.com/wp-cli/wp-cli/issues",
|
||||
"source": "https://github.com/wp-cli/wp-cli"
|
||||
},
|
||||
"time": "2020-02-18T08:15:37+00:00"
|
||||
}
|
||||
],
|
||||
|
@ -547,5 +601,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "7.0"
|
||||
},
|
||||
"plugin-api-version": "1.1.0"
|
||||
"plugin-api-version": "2.0.0"
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"maxmind-db/reader": "1.6.0",
|
||||
"pelago/emogrifier": "3.1.0",
|
||||
"psr/container": "1.0.0",
|
||||
"webonyx/graphql-php": "^0.12.6",
|
||||
"woocommerce/action-scheduler": "3.1.6",
|
||||
"woocommerce/woocommerce-admin": "2.0.3",
|
||||
"woocommerce/woocommerce-blocks": "4.4.3"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "d60ef90fa87857445878bb83ba4c592c",
|
||||
"content-hash": "09922bdf7cf1ab57bc0e69b8d480a93e",
|
||||
"packages": [
|
||||
{
|
||||
"name": "automattic/jetpack-autoloader",
|
||||
|
@ -44,6 +44,9 @@
|
|||
"GPL-2.0-or-later"
|
||||
],
|
||||
"description": "Creates a custom autoloader for a plugin or theme.",
|
||||
"support": {
|
||||
"source": "https://github.com/Automattic/jetpack-autoloader/tree/v2.9.1"
|
||||
},
|
||||
"time": "2021-02-05T19:07:06+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -75,6 +78,9 @@
|
|||
"GPL-2.0-or-later"
|
||||
],
|
||||
"description": "A wrapper for defining constants in a more testable way.",
|
||||
"support": {
|
||||
"source": "https://github.com/Automattic/jetpack-constants/tree/v1.5.1"
|
||||
},
|
||||
"time": "2020-10-28T19:00:31+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -205,6 +211,10 @@
|
|||
"zend",
|
||||
"zikula"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/composer/installers/issues",
|
||||
"source": "https://github.com/composer/installers/tree/v1.10.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://packagist.com",
|
||||
|
@ -279,6 +289,10 @@
|
|||
"geolocation",
|
||||
"maxmind"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues",
|
||||
"source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.6.0"
|
||||
},
|
||||
"time": "2019-12-19T22:59:03+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -353,6 +367,10 @@
|
|||
"email",
|
||||
"pre-processing"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/MyIntervals/emogrifier/issues",
|
||||
"source": "https://github.com/MyIntervals/emogrifier"
|
||||
},
|
||||
"time": "2019-12-26T19:37:31+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -402,6 +420,10 @@
|
|||
"container-interop",
|
||||
"psr"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-fig/container/issues",
|
||||
"source": "https://github.com/php-fig/container/tree/master"
|
||||
},
|
||||
"time": "2017-02-14T16:28:37+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -455,8 +477,60 @@
|
|||
],
|
||||
"description": "Symfony CssSelector Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/css-selector/tree/master"
|
||||
},
|
||||
"time": "2017-05-01T15:01:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webonyx/graphql-php",
|
||||
"version": "v0.12.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/webonyx/graphql-php.git",
|
||||
"reference": "4c545e5ec4fc37f6eb36c19f5a0e7feaf5979c95"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/webonyx/graphql-php/zipball/4c545e5ec4fc37f6eb36c19f5a0e7feaf5979c95",
|
||||
"reference": "4c545e5ec4fc37f6eb36c19f5a0e7feaf5979c95",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=5.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8",
|
||||
"psr/http-message": "^1.0",
|
||||
"react/promise": "2.*"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/http-message": "To use standard GraphQL server",
|
||||
"react/promise": "To leverage async resolving on React PHP platform"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GraphQL\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "A PHP port of GraphQL reference implementation",
|
||||
"homepage": "https://github.com/webonyx/graphql-php",
|
||||
"keywords": [
|
||||
"api",
|
||||
"graphql"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/webonyx/graphql-php/issues",
|
||||
"source": "https://github.com/webonyx/graphql-php/tree/0.12.x"
|
||||
},
|
||||
"time": "2018-09-02T14:59:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "woocommerce/action-scheduler",
|
||||
"version": "3.1.6",
|
||||
|
@ -490,6 +564,10 @@
|
|||
],
|
||||
"description": "Action Scheduler for WordPress and WooCommerce",
|
||||
"homepage": "https://actionscheduler.org/",
|
||||
"support": {
|
||||
"issues": "https://github.com/woocommerce/action-scheduler/issues",
|
||||
"source": "https://github.com/woocommerce/action-scheduler/tree/master"
|
||||
},
|
||||
"time": "2020-05-12T16:22:33+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -535,6 +613,10 @@
|
|||
],
|
||||
"description": "A modern, javascript-driven WooCommerce Admin experience.",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce-admin",
|
||||
"support": {
|
||||
"issues": "https://github.com/woocommerce/woocommerce-admin/issues",
|
||||
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.0.3"
|
||||
},
|
||||
"time": "2021-03-10T02:58:43+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -582,6 +664,10 @@
|
|||
"gutenberg",
|
||||
"woocommerce"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues",
|
||||
"source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v4.4.3"
|
||||
},
|
||||
"time": "2021-02-11T18:07:48+00:00"
|
||||
}
|
||||
],
|
||||
|
@ -630,6 +716,10 @@
|
|||
"isolation",
|
||||
"tool"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/bamarni/composer-bin-plugin/issues",
|
||||
"source": "https://github.com/bamarni/composer-bin-plugin/tree/master"
|
||||
},
|
||||
"time": "2020-05-03T08:27:20+00:00"
|
||||
}
|
||||
],
|
||||
|
@ -645,5 +735,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "7.0"
|
||||
},
|
||||
"plugin-api-version": "1.1.0"
|
||||
"plugin-api-version": "2.0.0"
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ class WC_Settings_Advanced extends WC_Settings_Page {
|
|||
'keys' => __( 'REST API', 'woocommerce' ),
|
||||
'webhooks' => __( 'Webhooks', 'woocommerce' ),
|
||||
'legacy_api' => __( 'Legacy API', 'woocommerce' ),
|
||||
'graphql_api' => __( 'GraphQL API', 'woocommerce' ),
|
||||
'woocommerce_com' => __( 'WooCommerce.com', 'woocommerce' ),
|
||||
);
|
||||
|
||||
|
@ -385,11 +386,12 @@ class WC_Settings_Advanced extends WC_Settings_Page {
|
|||
'id' => 'legacy_api_options',
|
||||
),
|
||||
array(
|
||||
'title' => __( 'Legacy API', 'woocommerce' ),
|
||||
'desc' => __( 'Enable the legacy REST API', 'woocommerce' ),
|
||||
'id' => 'woocommerce_api_enabled',
|
||||
'type' => 'checkbox',
|
||||
'default' => 'no',
|
||||
'title' => __( 'Legacy API', 'woocommerce' ),
|
||||
'desc' => __( 'Enable the legacy REST API', 'woocommerce' ),
|
||||
'desc_tip' => __( 'Enable the legacy REST API', 'woocommerce' ),
|
||||
'id' => 'woocommerce_api_enabled',
|
||||
'type' => 'checkbox',
|
||||
'default' => 'no',
|
||||
),
|
||||
array(
|
||||
'type' => 'sectionend',
|
||||
|
@ -397,6 +399,34 @@ class WC_Settings_Advanced extends WC_Settings_Page {
|
|||
),
|
||||
)
|
||||
);
|
||||
} elseif ( 'graphql_api' === $current_section ) {
|
||||
$graphql_entrypoint = get_rest_url( null, 'wc/graphql/api' );
|
||||
|
||||
// phpcs:disable WordPress.WP.I18n.InterpolatedVariableText
|
||||
$settings = apply_filters(
|
||||
'woocommerce_settings_graphql_api',
|
||||
array(
|
||||
array(
|
||||
'title' => '',
|
||||
'type' => 'title',
|
||||
'desc' => '',
|
||||
'id' => 'graphql_api_options',
|
||||
),
|
||||
array(
|
||||
'title' => __( 'GraphQL API', 'woocommerce' ),
|
||||
'desc' => __( 'Enable the GraphQL API', 'woocommerce' ),
|
||||
'desc_tip' => __( "<b>The GraphQL API is experimental and not intended for production use.</b><br/>GraphQL API entry point: <code>$graphql_entrypoint</code><br/>Accepts <code>POST</code> requests with a JSON object containing a <code>query</code> field and optionally a <code>variables</code> field.", 'woocommerce' ),
|
||||
'id' => 'woocommerce_graphql_api_enabled',
|
||||
'type' => 'checkbox',
|
||||
'default' => 'no',
|
||||
),
|
||||
array(
|
||||
'type' => 'sectionend',
|
||||
'id' => 'graphql_api_options',
|
||||
),
|
||||
)
|
||||
);
|
||||
// phpcs:enable WordPress.WP.I18n.InterpolatedVariableText
|
||||
}
|
||||
|
||||
return apply_filters( 'woocommerce_get_settings_' . $this->id, $settings, $current_section );
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* @since 2.6.0
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Utilities\StringUtil;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
|
@ -34,6 +36,16 @@ class WC_REST_Authentication {
|
|||
*/
|
||||
protected $auth_method = '';
|
||||
|
||||
private static $_instance;
|
||||
|
||||
public static function instance() {
|
||||
if(is_null(self::$_instance)) {
|
||||
self::$_instance = new self();
|
||||
}
|
||||
|
||||
return self::$_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize authentication actions.
|
||||
*/
|
||||
|
@ -556,6 +568,10 @@ class WC_REST_Authentication {
|
|||
private function check_permissions( $method ) {
|
||||
$permissions = $this->user->permissions;
|
||||
|
||||
if($this->is_graphql_request()) {
|
||||
return 'POST' === $method;
|
||||
}
|
||||
|
||||
switch ( $method ) {
|
||||
case 'HEAD':
|
||||
case 'GET':
|
||||
|
@ -635,6 +651,28 @@ class WC_REST_Authentication {
|
|||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function is_graphql_request()
|
||||
{
|
||||
return StringUtil::ends_with($_SERVER['PHP_SELF'], '/wc/v4/api');
|
||||
}
|
||||
|
||||
public function current_user_has_permission(string $permission) {
|
||||
if(!$this->user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$actual_permission = $this->user->permissions;
|
||||
|
||||
switch($permission) {
|
||||
case 'read':
|
||||
return 'read' === $actual_permission || 'read_write' === $actual_permission;
|
||||
case 'write':
|
||||
return 'write' === $actual_permission || 'read_write' === $actual_permission;
|
||||
}
|
||||
|
||||
throw new Exception("Unknown permission: " . $permission);
|
||||
}
|
||||
}
|
||||
|
||||
new WC_REST_Authentication();
|
||||
WC_REST_Authentication::instance();
|
||||
|
|
|
@ -167,6 +167,7 @@ final class WooCommerce {
|
|||
$this->define_tables();
|
||||
$this->includes();
|
||||
$this->init_hooks();
|
||||
Automattic\WooCommerce\Internal\GraphQL\Main::initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,6 +7,8 @@ namespace Automattic\WooCommerce;
|
|||
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\DownloadPermissionsAdjusterServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\GraphqlInfrastructureServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\GraphqlTypesServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProxiesServiceProvider;
|
||||
|
||||
/**
|
||||
|
@ -33,8 +35,10 @@ final class Container implements \Psr\Container\ContainerInterface {
|
|||
* @var string[]
|
||||
*/
|
||||
private $service_providers = array(
|
||||
ProxiesServiceProvider::class,
|
||||
DownloadPermissionsAdjusterServiceProvider::class,
|
||||
GraphqlInfrastructureServiceProvider::class,
|
||||
GraphqlTypesServiceProvider::class,
|
||||
ProxiesServiceProvider::class
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
/**
|
||||
* GraphqlInfrastructureServiceProvider class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\RootMutationType;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\RootQueryType;
|
||||
|
||||
/**
|
||||
* Service provider for the infrastructure classes in the Automattic\WooCommerce\Internal\GraphQL namespace.
|
||||
*/
|
||||
class GraphqlInfrastructureServiceProvider extends AbstractServiceProvider {
|
||||
|
||||
/**
|
||||
* The classes/interfaces that are serviced by this service provider.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $provides = array(
|
||||
RootQueryType::class,
|
||||
RootMutationType::class,
|
||||
);
|
||||
|
||||
/**
|
||||
* Register the classes.
|
||||
*/
|
||||
public function register() {
|
||||
foreach ( $this->provides as $class_name ) {
|
||||
$this->share_with_auto_arguments( $class_name );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
/**
|
||||
* GraphqlTypesServiceProvider class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\EnumTypes\ProductAttributeOrderBy;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\InputTypes\AddProductAttributeTermInputType;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\MutationTypes\AddProductAttributeTerm;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\QueryTypes\ProductAttribute;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\QueryTypes\ProductAttributes;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\QueryTypes\ProductAttributeTerm;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\QueryTypes\ProductAttributeTerms;
|
||||
|
||||
/**
|
||||
* Service provider for the type definition classes in the Automattic\WooCommerce\Internal\GraphQL namespace.
|
||||
*/
|
||||
class GraphqlTypesServiceProvider extends AbstractServiceProvider {
|
||||
|
||||
/**
|
||||
* The classes/interfaces that are serviced by this service provider.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $provides = array(
|
||||
ProductAttribute::class,
|
||||
ProductAttributes::class,
|
||||
ProductAttributeOrderBy::class,
|
||||
ProductAttributeTerm::class,
|
||||
ProductAttributeTerms::class,
|
||||
AddProductAttributeTerm::class,
|
||||
AddProductAttributeTermInputType::class,
|
||||
);
|
||||
|
||||
/**
|
||||
* Register the classes.
|
||||
*/
|
||||
public function register() {
|
||||
foreach ( $this->provides as $class_name ) {
|
||||
$this->share( $class_name );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL;
|
||||
|
||||
use GraphQL\Error\ClientAware;
|
||||
|
||||
/**
|
||||
* ApiException class.
|
||||
*
|
||||
* Queries and mutations should throw an exception of this type when something prevents the completion of the
|
||||
* operation. The supplied message will be returned in the response, and the HTTP status of the response
|
||||
* will depend on the specified category (note that the category must be one of the keys of
|
||||
* Main::$status_codes_by_error_category)
|
||||
*
|
||||
* For authorization errors the query/mutation code can do just "throw ApiException::Unauthorized()".
|
||||
*
|
||||
* @package Automattic\WooCommerce\Internal\GraphQL
|
||||
*/
|
||||
class ApiException extends \Exception implements ClientAware {
|
||||
|
||||
/**
|
||||
* The category of the error.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $category;
|
||||
|
||||
/**
|
||||
* ApiException constructor.
|
||||
*
|
||||
* @param string $message The error message to be included in the response.
|
||||
* @param string $category The error category. Must be one of the keys of Main::$status_codes_by_error_category.
|
||||
* @param int $code The error code, unused by the GraphQL infrastructure.
|
||||
* @param Throwable|null $previous The previous exception, unused by the GraphQL infrastructure.
|
||||
*/
|
||||
public function __construct( $message = '', $category = 'request', $code = 0, Throwable $previous = null ) {
|
||||
parent::__construct( $message, $code, $previous );
|
||||
$this->category = $category;
|
||||
}
|
||||
|
||||
/**
|
||||
* This must be true for the error message to be included in the response.
|
||||
*
|
||||
* @return bool True, so the error message will be included in the response.
|
||||
*/
|
||||
public function isClientSafe() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the category of the error.
|
||||
*
|
||||
* @return string The category of the error.
|
||||
*/
|
||||
public function getCategory() {
|
||||
return $this->category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new ApiException object with the "authorization" category.
|
||||
*
|
||||
* @param null|string $message The error message to be included in the response.
|
||||
* @return ApiException The new instance of the exception.
|
||||
*/
|
||||
public static function Unauthorized( $message = null ) {
|
||||
return new ApiException( is_null( $message ) ? "You don't have permission for the requested operation" : $message, 'authorization' );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL;
|
||||
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
|
||||
/**
|
||||
* Base class for the GraphQL enumeration types.
|
||||
*
|
||||
To create a new enumeration type:
|
||||
*
|
||||
* 1. Add a new class inheriting this one in the "EnumerationTypes" folder/namespace.
|
||||
* The name of the class should be the same of the mutation plus "InputType".
|
||||
* 2. Add the class name to "$provides" in "GraphqlTypesServiceProvider"
|
||||
* so that it can be resolved with the dependency injection container.
|
||||
*/
|
||||
abstract class BaseEnumType extends EnumType {
|
||||
|
||||
/**
|
||||
* An instance of the dependency injection container.
|
||||
* Can be used by derived classes.
|
||||
*
|
||||
* @var \Psr\Container\ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the class.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->container = wc_get_container();
|
||||
|
||||
$config = array(
|
||||
'name' => $this->get_name(),
|
||||
'description' => $this->get_description(),
|
||||
'values' => $this->get_enum_values(),
|
||||
);
|
||||
|
||||
parent::__construct( $config );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GraphQL name of the type.
|
||||
*
|
||||
* The default name is the one of the class without the "Type" suffix.
|
||||
* Derived classes can override this but they shouldn't without a good reason.
|
||||
*
|
||||
* @return string The GraphQL name of the type.
|
||||
*/
|
||||
public function get_name() {
|
||||
return $this->tryInferName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comma separated list of the value names, each enclosed in "`".
|
||||
* Derived classes can use this for field descriptions.
|
||||
*
|
||||
* @return string A comma separated list of the value names, each enclosed in "`".
|
||||
*/
|
||||
public function get_comma_separated_value_names() {
|
||||
return '`' . implode( '`, `', $this->get_enum_value_names() ) . '`';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the names of the enumeration values.
|
||||
*
|
||||
* @return array The names of the enumeration values.
|
||||
*/
|
||||
public function get_enum_value_names() {
|
||||
$enum_values = $this->get_enum_values();
|
||||
$names = array();
|
||||
|
||||
foreach ( $enum_values as $key => $value ) {
|
||||
if ( is_string( $value ) ) {
|
||||
$names[] = $value;
|
||||
} elseif ( is_string( $key ) ) {
|
||||
$names[] = $key;
|
||||
} else {
|
||||
$names[] = $value['name'];
|
||||
}
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GraphQL description of the enumeration type.
|
||||
*
|
||||
* @return string The GraphQL description of the enumeration type.
|
||||
*/
|
||||
abstract public function get_description();
|
||||
|
||||
/**
|
||||
* Get the definition of the enumeration values.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* array(
|
||||
* 'value_1' => array(
|
||||
* 'description' => 'The meaning of the first value.',
|
||||
* ),
|
||||
* 'value_2' => array(
|
||||
* 'description' => 'The meaning of the second value.',
|
||||
* );
|
||||
*
|
||||
* See https://webonyx.github.io/graphql-php/type-system/enum-types/ for the full syntax.
|
||||
*
|
||||
* @return array The definition of the enumeration values.
|
||||
*/
|
||||
abstract public function get_enum_values();
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL;
|
||||
|
||||
use Automattic\WooCommerce\Utilities\StringUtil;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
|
||||
/**
|
||||
* Base class for the GraphQL input types for mutations.
|
||||
*
|
||||
* To create a new input type:
|
||||
*
|
||||
* 1. Add a new class inheriting this one in the "InputTypes" folder/namespace.
|
||||
* The name of the class should be the same of the mutation plus "InputType".
|
||||
* 2. Add the class name to "$provides" in "GraphqlTypesServiceProvider"
|
||||
* so that it can be resolved with the dependency injection container.
|
||||
*/
|
||||
abstract class BaseInputType extends InputObjectType {
|
||||
|
||||
/**
|
||||
* An instance of the dependency injection container.
|
||||
* Can be used by derived classes.
|
||||
*
|
||||
* @var \Psr\Container\ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the class.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->container = wc_get_container();
|
||||
|
||||
$config = array(
|
||||
'name' => $this->get_name(),
|
||||
'description' => $this->get_description(),
|
||||
'fields' => $this->get_fields(),
|
||||
);
|
||||
|
||||
parent::__construct( $config );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GraphQL name of the input type.
|
||||
*
|
||||
* The default name is the one of the class without the "Type" suffix.
|
||||
* Derived classes can override this but they shouldn't without a good reason.
|
||||
*
|
||||
* @return string The GraphQL name of the input type.
|
||||
*/
|
||||
public function get_name() {
|
||||
return $this->tryInferName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GraphQL description of the input type.
|
||||
*
|
||||
* By default the name is "Input type for the (class name without InputType suffix) mutation."
|
||||
* Derived classes can override this if necessary.
|
||||
*
|
||||
* @return string The GraphQL description of the enumeration type.
|
||||
*/
|
||||
public function get_description() {
|
||||
$my_class_name = StringUtil::class_name_without_namespace( get_class( $this ) );
|
||||
$mutation_name = preg_replace( '~InputType$~', '', $my_class_name );
|
||||
return "Input type for the {$mutation_name} mutation.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields of the input type.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* array(
|
||||
* 'name' => array(
|
||||
* 'type' => Type::nonNull( Type::string() ),
|
||||
* 'description' => 'Name of the object being created.',
|
||||
* ),
|
||||
* 'kind' => array(
|
||||
* 'type' => $this->container->get( KindEnumType::class ),
|
||||
* 'description' => 'Kind of the object being created. Possible types: ' . $this->container->get( KindEnumType::class )->get_enum_value_names(),
|
||||
* )
|
||||
* );
|
||||
*
|
||||
* See "Input object type" in https://webonyx.github.io/graphql-php/type-system/input-types/ for the full syntax.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function get_fields();
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL;
|
||||
|
||||
use Automattic\WooCommerce\Utilities\StringUtil;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
/**
|
||||
* Base class for mutation types.
|
||||
*
|
||||
* To create a new mutation:
|
||||
*
|
||||
* 1. Add a new class inheriting this one in the "MutationTypes" folder/namespace.
|
||||
* 2. Add the class name to "get_object_type_classes" in "RootMutationType".
|
||||
* 3. Add the class name to "$provides" in "GraphqlTypesServiceProvider"
|
||||
* so that it can be resolved with the dependency injection container.
|
||||
*/
|
||||
abstract class BaseMutationType extends ObjectType {
|
||||
|
||||
/**
|
||||
* An instance of the dependency injection container.
|
||||
* Can be used by derived classes.
|
||||
*
|
||||
* @var \Psr\Container\ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the class.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->container = wc_get_container();
|
||||
|
||||
$config = array(
|
||||
'name' => $this->get_name(),
|
||||
'description' => $this->get_description(),
|
||||
'fields' => $this->get_fields(),
|
||||
'args' => $this->get_args(),
|
||||
);
|
||||
|
||||
parent::__construct( $config );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GraphQL name of the mutation type.
|
||||
*
|
||||
* The default name is the one of the class without the "Type" suffix.
|
||||
* Derived classes can override this but they shouldn't without a good reason.
|
||||
*
|
||||
* @return string The GraphQL name of the mutation type.
|
||||
*/
|
||||
public function get_name() {
|
||||
return $this->tryInferName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the definition of the GraphQL arguments that the mutation will accept for its execution.
|
||||
*
|
||||
* By default only one argument will be accepted, its name will be "input"
|
||||
* and its type will be defined by a class with the same name plus the "InputType" suffix,
|
||||
* derived classes can add more arguments or replace the "input" argument as appropriate.
|
||||
*
|
||||
* See "Field arguments" in https://webonyx.github.io/graphql-php/type-system/object-types/ for the full syntax.
|
||||
*
|
||||
* @return array Definition of the GraphQL arguments that the mutation accepts for its execution.
|
||||
*/
|
||||
public function get_args() {
|
||||
$my_class_name = StringUtil::class_name_without_namespace( get_class( $this ) );
|
||||
$input_type_name = $my_class_name . 'InputType';
|
||||
$input_type = Main::resolve_type( $input_type_name );
|
||||
|
||||
return array(
|
||||
'input' => $input_type,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the definition GraphQL fields of the mutation type
|
||||
* (the fields of the object that will be returned when the mutation completes successfully).
|
||||
*
|
||||
* By default only the "id" field of the created object will be returned,
|
||||
* derived classes can override this as needed.
|
||||
*
|
||||
* See "Mutations" in https://webonyx.github.io/graphql-php/type-system/input-types/ for the full syntax.
|
||||
*
|
||||
* @return array The definition fields of the object returned by the mutation.
|
||||
*/
|
||||
public function get_fields() {
|
||||
return array(
|
||||
'id' => array(
|
||||
'type' => Type::nonNull( Type::int() ),
|
||||
'description' => 'The unique identifier of the resource created.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GraphQL description of the mutation type.
|
||||
*
|
||||
* @return string The GraphQL description of the mutation type.
|
||||
*/
|
||||
abstract public function get_description();
|
||||
|
||||
/**
|
||||
* Execute the operation defined for the mutation.
|
||||
*
|
||||
* The method will receive an array with argument values consistent with the definition returned by "get_args"
|
||||
* (for the default implementation it will only have one key, "input", which will in turn hold an array of data)
|
||||
* and must return an object/array with public properties/keys consistent with the definition returned by "get_fields"
|
||||
* (for the default implementation this will have one single property/key, "id", holding a number).
|
||||
*
|
||||
* Values for individual fields in the output can alternatively be obtained from the generated object if they define
|
||||
* a "resolve" callback, e.g.:
|
||||
*
|
||||
* 'id' => array(
|
||||
* 'type' => Type::nonNull( Type::id() ),
|
||||
* 'description' => 'Unique identifier for the resource created.',
|
||||
* 'resolve' => function( $output_from_execute ) {
|
||||
* return $output_from_execute['item_id'];
|
||||
* },
|
||||
* ),
|
||||
*
|
||||
* If the operation can't be completed the method must throw an ApiException. For authorization errors
|
||||
* it can be just "throw ApiException::Unauthorized()".
|
||||
*
|
||||
* @param array $args Arguments for the mutation operation.
|
||||
* @param mixed $context Context for the mutation operation, currently unused.
|
||||
* @param ResolveInfo $info The resolve info passed from the GraphQL engine.
|
||||
* @return array The result of the mutation execution.
|
||||
*/
|
||||
abstract public function execute( $args, $context, ResolveInfo $info);
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL;
|
||||
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
/**
|
||||
* Base class for queries that return a list of items.
|
||||
*
|
||||
* The arguments for the query will always be "offset" and "count" (more can be added via "get_extra_args")
|
||||
* and the returned object will always have two fields: "total_count" (a number)
|
||||
* and "items" (a list of items whose fields will be defined by the type specified by "get_item_type_class_name").
|
||||
*
|
||||
* Derived classes should be a name in plural, and the type name returned by "get_item_type_class_name"
|
||||
* should be the same name in singular, unless there's a good reason to follow a different approach
|
||||
* in some particular case.
|
||||
*
|
||||
* To create a new list query, proceed as when inheriting from BaseQueryType.
|
||||
*/
|
||||
abstract class BaseQueryListType extends BaseQueryType {
|
||||
|
||||
/**
|
||||
* Maximum amount of results that will be returned if no "count" argument is supplied.
|
||||
*/
|
||||
const MAX_RESULTS_PER_QUERY = 100;
|
||||
|
||||
/**
|
||||
* Holds the instance of the class defining the type of the objects returned.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $object_type_instance;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the class.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->container = wc_get_container();
|
||||
|
||||
$this->object_type_instance = $this->container->get( $this->get_item_type_class_name() );
|
||||
parent::__construct( $this->get_args() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GraphQL description of the query.
|
||||
*
|
||||
* By default it will be "A collection of (object type name)", derived classes can override this if necesessary.
|
||||
*
|
||||
* @return string The GraphQL description of the query.
|
||||
*/
|
||||
public function get_description() {
|
||||
$object_type_name = $this->container->get( $this->get_item_type_class_name() )->get_name();
|
||||
return 'A collection of ' . $object_type_name . '.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the definition GraphQL fields of the query type
|
||||
* (the fields of the object that will be returned when the query completes successfully).
|
||||
* These are always "total_count" (a number) and "items" (a list of objects whose fields will be defined
|
||||
* by the object specified by "get_item_type_class_name").
|
||||
*
|
||||
* See "get_fields" in "BaseQueryType" for an example.
|
||||
* See "Field configuration options" in https://webonyx.github.io/graphql-php/type-system/object-types/ for the full syntax.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function get_fields() {
|
||||
return array(
|
||||
'total_count' => array(
|
||||
'type' => Type::int(),
|
||||
'description' => 'The total count.',
|
||||
),
|
||||
'items' => array(
|
||||
'type' => Type::listOf( $this->object_type_instance ),
|
||||
'description' => 'The items themselves.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the definition of the GraphQL arguments that the query will accept for its resolution.
|
||||
*
|
||||
* The arguments will always include "offset" (how many items to skip from the data store before
|
||||
* including items in the output) and "count" (the maximum number of items to return).
|
||||
* Extra arguments can be added via "get_extra_args".
|
||||
*
|
||||
* @return array Definition of the GraphQL arguments that the query accepts for its resolution.
|
||||
*/
|
||||
public function get_args() {
|
||||
$args = array(
|
||||
'offset' => array(
|
||||
'type' => Type::int(),
|
||||
'description' => 'Specifies how many items to skip from the data store before including items in the output.',
|
||||
'defaultValue' => 0,
|
||||
),
|
||||
'count' => array(
|
||||
'type' => Type::int(),
|
||||
'description' => 'Specifies the maximum number of items to return.',
|
||||
'defaultValue' => self::MAX_RESULTS_PER_QUERY,
|
||||
),
|
||||
);
|
||||
|
||||
return array_merge( $args, $this->get_extra_args() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies any extra arguments that the query will accept besides "offset" and "count",
|
||||
* none by default.
|
||||
*
|
||||
* See "get_args" in "BaseQueryType" for an example.
|
||||
* See "Field arguments" in https://webonyx.github.io/graphql-php/type-system/object-types/ for the full syntax.
|
||||
*
|
||||
* @return array Extra arguments that the query will accept besides "offset" and "count".
|
||||
*/
|
||||
public function get_extra_args() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the query defined by the type.
|
||||
*
|
||||
* The actual resolution will be delegated to the "resolve_total_count" and "resolve_items" methods.
|
||||
*
|
||||
* @param array $args Arguments for the query operation.
|
||||
* @param mixed $context Context for the query operation, currently unused.
|
||||
* @param ResolveInfo $info The resolve info passed from the GraphQL engine.
|
||||
* @return array The result of the query execution.
|
||||
*/
|
||||
public function resolve( $args, $context, ResolveInfo $info ) {
|
||||
$extra_args = $args;
|
||||
unset( $extra_args['offset'] );
|
||||
unset( $extra_args['count'] );
|
||||
|
||||
$result = array();
|
||||
|
||||
$field_selection = $info->getFieldSelection();
|
||||
if ( array_key_exists( 'total_count', $field_selection ) ) {
|
||||
$result['total_count'] = $this->resolve_total_count( $extra_args );
|
||||
}
|
||||
if ( array_key_exists( 'items', $field_selection ) ) {
|
||||
$result['items'] = $this->resolve_items(
|
||||
$args['offset'],
|
||||
$args['count'],
|
||||
$extra_args,
|
||||
$field_selection['items']
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the class defining the type of the items that the query will return
|
||||
* (must be a query type class implementing BaseQueryType).
|
||||
*
|
||||
* That name should be the same as the class but in singular, unless there's a good reason
|
||||
* to follow a different approach in some particular case.
|
||||
*
|
||||
* @return string Full (namespaced) name of the class defining the type of the items that the query will return.
|
||||
*/
|
||||
abstract public function get_item_type_class_name();
|
||||
|
||||
/**
|
||||
* Get the count of existing items of the type defined by "get_item_type_class_name".
|
||||
* This method will be executed only if the query requests the "total_count" field.
|
||||
*
|
||||
* The method will receive an array with argument values consistent with the definition returned by "get_extra_args"
|
||||
* (for the default implementation it will just be an empty array) and must return a count of items according
|
||||
* to these arguments (or the absolute total of existing items if there are none).
|
||||
*
|
||||
* @param array $extra_args Any arguments for counting the existing items.
|
||||
* @return int The count of existing items.
|
||||
*/
|
||||
abstract public function resolve_total_count( $extra_args );
|
||||
|
||||
/**
|
||||
* Resolve the query defined by the type.
|
||||
* This method will be executed only if the query requests the "items" field.
|
||||
*
|
||||
* This works pretty much as BaseObjectType::resolve, except that it returns an array of objects/arrays
|
||||
* (of the type defined by "get_item_type_class_name") according to the offset, count and extra arguments
|
||||
* supplied.
|
||||
*
|
||||
* @param int $offset How many items to skip from the data store before including items in the output.
|
||||
* @param int $count Maximum amount of items to return.
|
||||
* @param array $extra_args Any extra arguments for the query, consistent with the definition specified by "get_extra_args".
|
||||
* @param array $field_selection The result from executing $info->getFieldSelection()['items'] in "resolve".
|
||||
* @return array An array of objects/arrays, each representing an item defined by the type specified by "get_item_type_class_name".
|
||||
*/
|
||||
abstract public function resolve_items( $offset, $count, $extra_args, $field_selection );
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL;
|
||||
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
|
||||
/**
|
||||
* Base class for query types.
|
||||
*
|
||||
* To create a new query:
|
||||
*
|
||||
* 1. Add a new class inheriting this one in the "QueryTypes" folder/namespace.
|
||||
* 2. Add the class name to "get_object_type_classes" in "RootQueryType".
|
||||
* 3. Add the class name to "$provides" in "GraphqlTypesServiceProvider"
|
||||
* so that it can be resolved with the dependency injection container.
|
||||
*/
|
||||
abstract class BaseQueryType extends ObjectType {
|
||||
|
||||
/**
|
||||
* An instance of the dependency injection container.
|
||||
* Can be used by derived classes.
|
||||
*
|
||||
* @var \Psr\Container\ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the class.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->container = wc_get_container();
|
||||
|
||||
$config = array(
|
||||
'name' => $this->get_name(),
|
||||
'description' => $this->get_description(),
|
||||
'fields' => $this->get_fields(),
|
||||
'args' => $this->get_args(),
|
||||
);
|
||||
|
||||
parent::__construct( $config );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GraphQL name of the query type.
|
||||
*
|
||||
* The default name is the one of the class without the "Type" suffix.
|
||||
* Derived classes can override this but they shouldn't without a good reason.
|
||||
*
|
||||
* @return string The GraphQL name of the query type.
|
||||
*/
|
||||
public function get_name() {
|
||||
return $this->tryInferName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the definition of the GraphQL arguments that the query will accept for its resolution.
|
||||
* By default no arguments will be accepted.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* array(
|
||||
* 'id' => array(
|
||||
* 'type' => Type::nonNull( Type::id() ),
|
||||
* 'description' => 'Unique identifier of the resource to be obtained.',
|
||||
* )
|
||||
* );
|
||||
*
|
||||
* See "Field arguments" in https://webonyx.github.io/graphql-php/type-system/object-types/ for the full syntax.
|
||||
*
|
||||
* @return array Definition of the GraphQL arguments that the query accepts for its resolution.
|
||||
*/
|
||||
public function get_args() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the definition GraphQL fields of the query type
|
||||
* (the fields of the object that will be returned when the query completes successfully).
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* array(
|
||||
* 'id' => array(
|
||||
* 'type' => Type::nonNull( Type::id() ),
|
||||
* 'description' => 'Unique identifier of the resource.',
|
||||
* ),
|
||||
* 'name' => array(
|
||||
* 'type' => Type::nonNull( Type::string() ),
|
||||
* 'description' => 'Name of the resource.',
|
||||
* ),
|
||||
* );
|
||||
*
|
||||
* See "Field configuration options" in https://webonyx.github.io/graphql-php/type-system/object-types/ for the full syntax.
|
||||
*
|
||||
* @return array The definition fields of the object returned by the query.
|
||||
*/
|
||||
abstract public function get_fields();
|
||||
|
||||
/**
|
||||
* Get the GraphQL description of the query type.
|
||||
*
|
||||
* @return string The GraphQL description of the query type.
|
||||
*/
|
||||
abstract public function get_description();
|
||||
|
||||
/**
|
||||
* Resolve the query defined by the type.
|
||||
*
|
||||
* The method will receive an array with argument values consistent with the definition returned by "get_args"
|
||||
* (for the default implementation it will just be an empty array)
|
||||
* and must return an object/array with public properties/keys consistent with the definition returned by "get_fields".
|
||||
*
|
||||
* Values for individual fields in the output can alternatively be obtained from the generated object if they define
|
||||
* a "resolve" callback, e.g.:
|
||||
*
|
||||
* 'id' => array(
|
||||
* 'type' => Type::nonNull( Type::id() ),
|
||||
* 'description' => 'Unique identifier of the resource.',
|
||||
* 'resolve' => function( $output_from_resolve ) {
|
||||
* return $output_from_resolve['item_id'];
|
||||
* },
|
||||
* ),
|
||||
*
|
||||
* If the query can't be completed the method must throw an ApiException. For authorization errors
|
||||
* it can be just "throw ApiException::Unauthorized()".
|
||||
*
|
||||
* @param array $args Arguments for the query operation.
|
||||
* @param mixed $context Context for the query operation, currently unused.
|
||||
* @param ResolveInfo $info The resolve info passed from the GraphQL engine.
|
||||
* @return array The result of the query execution.
|
||||
*/
|
||||
abstract public function resolve( $args, $context, ResolveInfo $info);
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL;
|
||||
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
|
||||
/**
|
||||
* Base class for defining the query and mutation root types for the API.
|
||||
*/
|
||||
abstract class BaseRootType extends ObjectType {
|
||||
|
||||
/**
|
||||
* An instance of the dependency injection container.
|
||||
*
|
||||
* @var \Psr\Container\ContainerInterface
|
||||
*/
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the class.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->container = wc_get_container();
|
||||
|
||||
$config = array(
|
||||
'name' => $this->get_name(),
|
||||
'description' => $this->get_description(),
|
||||
'fields' => $this->get_fields(),
|
||||
'resolveField' => function( $value, $args, $context, ResolveInfo $info ) {
|
||||
return $this->resolve_field( $value, $args, $context, $info );
|
||||
},
|
||||
);
|
||||
|
||||
parent::__construct( $config );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a given query or execute a given mutation.
|
||||
* This is done by first obtaining the instance of the type corresponding to the requested operation
|
||||
* (by asking the dependency injection container for a class with the same name), and then
|
||||
* executing the "resolve" or "execute" method on it.
|
||||
*
|
||||
* Authorization is done by checking that there's an authenticated user with read/write permissions as appropriate
|
||||
* (read for queries, write for mutations), the same mechanism of consumer key+consumer secret from the WooCommerce
|
||||
* REST API is used (the very same set of credentials, actually). Additionally there's a
|
||||
* "woocommerce_graphql_check_permissions" hook that works similarly to "woocommerce_rest_check_permissions".
|
||||
*
|
||||
* @param mixed $value Root value supplied to "GraphQL::executeQuery", currently unused.
|
||||
* @param array $args Arguments for the resolution/execution.
|
||||
* @param mixed $context Context object supplied to "GraphQL::executeQuery", currently unused.
|
||||
* @param ResolveInfo $info Resolve info generated by "GraphQL::executeQuery".
|
||||
* @return mixed Result of executing "resolve" or "execute" on the target query/mutation instance.
|
||||
* @throws ApiException User lacks required permissions, or exception thrown by "resolve"/"execute" on the target query/mutation instance.
|
||||
*/
|
||||
private function resolve_field( $value, $args, $context, ResolveInfo $info ) {
|
||||
$user_has_permission = \WC_REST_Authentication::instance()->current_user_has_permission( $this->get_required_permission() );
|
||||
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
|
||||
/**
|
||||
* Filter to give or remove the permission to execute a given GraphQL query/mutation to the current user.
|
||||
*
|
||||
* @since ?
|
||||
*
|
||||
* @param bool Whether the user has initially the permission or not based on the existing authorization data for the user.
|
||||
* @param string The name of the query or mutation that the user wants to execute.
|
||||
* @param string The type of operation to perform, "read" or "write".
|
||||
* @param array The GraphQL arguments supplied for the execution of the query or mutation.
|
||||
* @return bool True if the user should be allowed to perform the query or mutation, false otherwise.
|
||||
*/
|
||||
$user_has_permission = apply_filters( 'woocommerce_graphql_check_permissions', $user_has_permission, $info->fieldName, $this->get_required_permission(), $args );
|
||||
|
||||
if ( ! $user_has_permission ) {
|
||||
throw ApiException::Unauthorized();
|
||||
}
|
||||
|
||||
$resolve_method = $this->get_resolve_method_name();
|
||||
return Main::resolve_type( $info->fieldName )->$resolve_method( $args, $context, $info );
|
||||
|
||||
// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields for the root query/mutation.
|
||||
* There will be one for each instance of the classes returned by "get_object_type_classes".
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_fields() {
|
||||
$fields = array();
|
||||
$class_names = $this->get_object_type_classes();
|
||||
foreach ( $class_names as $class_name ) {
|
||||
$type_object = $this->container->get( $class_name );
|
||||
$fields[ $type_object->get_name() ] = array(
|
||||
'type' => $type_object,
|
||||
'description' => $type_object->get_description(),
|
||||
'args' => $type_object->get_args(),
|
||||
);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of this root query, will be either "Query" or "Mutation".
|
||||
*
|
||||
* @return string Name of this root query.
|
||||
*/
|
||||
abstract protected function get_name();
|
||||
|
||||
/**
|
||||
* Get the description of this root query.
|
||||
*
|
||||
* @return string Description of this root query.
|
||||
*/
|
||||
abstract protected function get_description();
|
||||
|
||||
/**
|
||||
* Get the required permission for the queries/mutations in this root query, will be either "read" or "write".
|
||||
*
|
||||
* @return string Required permission for the queries in this root query.
|
||||
*/
|
||||
abstract protected function get_required_permission();
|
||||
|
||||
/**
|
||||
* Get the method to execute when resolving a query or mutation, will be either "resolve" or "execute".
|
||||
*
|
||||
* @return string Method to execute when resolving a query or mutation.
|
||||
*/
|
||||
abstract protected function get_resolve_method_name();
|
||||
|
||||
/**
|
||||
* Get the full names of the classes defining the queries/mutations for this root query.
|
||||
*
|
||||
* @return array Full names of the classes defining the queries/mutations for this root query.
|
||||
*/
|
||||
abstract protected function get_object_type_classes();
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL\EnumTypes;
|
||||
|
||||
use Automattic\WooCommerce\Internal\GraphQL\BaseEnumType;
|
||||
|
||||
/**
|
||||
* Class for the ProductAttributeOrderBy enumeration type.
|
||||
*/
|
||||
class ProductAttributeOrderBy extends BaseEnumType {
|
||||
|
||||
/**
|
||||
* Get the description of the type.
|
||||
*
|
||||
* @return string The description of the type.
|
||||
*/
|
||||
public function get_description() {
|
||||
return 'Default sort order for product attribute terms.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the possible values for the enumeration.
|
||||
*
|
||||
* @return array The possible values for the enumeration.
|
||||
*/
|
||||
public function get_enum_values() {
|
||||
return array(
|
||||
'menu_order' => array(
|
||||
'description' => 'Order by name.',
|
||||
),
|
||||
'name' => array(
|
||||
'description' => 'Order by name.',
|
||||
),
|
||||
'name_num' => array(
|
||||
'description' => 'Order by name (numeric).',
|
||||
),
|
||||
'id' => array(
|
||||
'description' => 'Order by id.',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL\InputTypes;
|
||||
|
||||
use Automattic\WooCommerce\Internal\GraphQL\BaseInputType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
/**
|
||||
* Class for the AddProductAttributeTerm input type.
|
||||
*/
|
||||
class AddProductAttributeTermInputType extends BaseInputType {
|
||||
|
||||
/**
|
||||
* Get the fields of the type.
|
||||
*
|
||||
* @return array Fields of the type.
|
||||
*/
|
||||
public function get_fields() {
|
||||
return array(
|
||||
'attribute_id' => array(
|
||||
'type' => Type::nonNull( Type::id() ),
|
||||
'description' => 'Unique identifier of the product attribute the term will be added to.',
|
||||
),
|
||||
'name' => array(
|
||||
'type' => Type::nonNull( Type::string() ),
|
||||
'description' => 'Term name.',
|
||||
),
|
||||
'slug' => array(
|
||||
'type' => Type::string(),
|
||||
'description' => 'An alphanumeric identifier for the resource unique to its type.',
|
||||
),
|
||||
'description' => array(
|
||||
'type' => Type::string(),
|
||||
'description' => 'HTML description of the resource.',
|
||||
),
|
||||
|
||||
// TODO: Add 'menu order' when implementing insertion in AddProductAttributeTerm.
|
||||
|
||||
/*
|
||||
'menu_order' => [
|
||||
'type' => Type::int(),
|
||||
'description' => 'Menu order, used to custom sort the resource.'
|
||||
]
|
||||
*/
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL;
|
||||
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Error\ClientAware;
|
||||
use GraphQL\Error\Debug;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* The main entry point class for the GraphQL API.
|
||||
*
|
||||
* The GraphQL API is enabled when the "woocommerce_graphql_api_enabled" option has a value of "yes",
|
||||
* this option can be set via UI from Woo settings - Advanced - GraphQL API.
|
||||
*
|
||||
* Once "initialize" is called the API is available via POST at endpoint /wp-json/wc/graphql/api.
|
||||
* Authorization is done in the "RootQueryType" and "RootMutationType" classes.
|
||||
*
|
||||
* Request bodies must consist of a JSON object having a "query" key and optionally a "variables" key.
|
||||
*
|
||||
* By default unexpected exceptions (those that are not ApiException) will be returned as a generic
|
||||
* "Internal server error" message, see "get_debug_config" method for how to enable verbose errors mode.
|
||||
*/
|
||||
class Main {
|
||||
|
||||
/**
|
||||
* An instance of the dependency injection container.
|
||||
*
|
||||
* @var \Psr\Container\ContainerInterface
|
||||
*/
|
||||
private static $container;
|
||||
|
||||
/**
|
||||
* Namespaces (relative to the one of this class) that contain GraphQL related classes
|
||||
* to be resolved via dependency injection container.
|
||||
*
|
||||
* @var string List of namespaces.
|
||||
*/
|
||||
private static $subnamespaces = array( 'EnumTypes', 'QueryTypes', 'MutationTypes', 'InputTypes' );
|
||||
|
||||
/**
|
||||
* Array mapping "ApiException" categories to HTTP response status codes.
|
||||
* Thrown "ApiException"s should always have a category matching one of the keys of this array.
|
||||
*
|
||||
* @var array Array of "ApiException" category => HTTP response status code.
|
||||
*/
|
||||
private static $status_codes_by_error_category = array(
|
||||
'request' => 400,
|
||||
'graphql' => 400,
|
||||
'authorization' => 401,
|
||||
'internal' => 500,
|
||||
);
|
||||
|
||||
/**
|
||||
* Initialize the GraphQL API entry point.
|
||||
*/
|
||||
public static function initialize() {
|
||||
if ( 'yes' !== get_option( 'woocommerce_graphql_api_enabled' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$container = wc_get_container();
|
||||
|
||||
add_action(
|
||||
'rest_api_init',
|
||||
function () {
|
||||
register_rest_route(
|
||||
'wc/graphql',
|
||||
'api',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => function( $request ) {
|
||||
return call_user_func( self::class . '::handle_request', $request );
|
||||
},
|
||||
'permission_callback' => '__return_true',
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of a class given its non-namespaced name.
|
||||
*
|
||||
* @param string $name Non-namespaced ame of the class to get an instance from.
|
||||
* @return mixed Instance of the requested class.
|
||||
* @throws \Exception The type can't be resolved.
|
||||
*/
|
||||
public static function resolve_type( $name ) {
|
||||
foreach ( self::$subnamespaces as $namespace ) {
|
||||
$full_name = __NAMESPACE__ . '\\' . $namespace . '\\' . $name;
|
||||
if ( self::$container->has( $full_name ) ) {
|
||||
return self::$container->get( $full_name );
|
||||
}
|
||||
}
|
||||
throw new \Exception( "There's no way to resolve the type '" . $name . "'." );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a HTTP request for the GraphQL API entry point.
|
||||
*
|
||||
* @param \WP_REST_Request $request Request to handle.
|
||||
* @return array|\WP_REST_Response Response to return to the client.
|
||||
* @throws ApiException Does not actually throw any exception, but phpcs needs this comment.
|
||||
*/
|
||||
private static function handle_request( \WP_REST_Request $request ) {
|
||||
$error_category = null;
|
||||
|
||||
try {
|
||||
$input = json_decode( $request->get_body(), true );
|
||||
if ( is_null( $input ) ) {
|
||||
throw new ApiException( 'Invalid input JSON: ' . json_last_error_msg() );
|
||||
}
|
||||
if ( ! isset( $input['query'] ) ) {
|
||||
throw new ApiException( "Invalid input JSON: no 'query' element" );
|
||||
}
|
||||
|
||||
$query = $input['query'];
|
||||
$variable_values = isset( $input['variables'] ) ? $input['variables'] : null;
|
||||
|
||||
$schema = new Schema(
|
||||
array(
|
||||
'query' => self::$container->get( RootQueryType::class ),
|
||||
'mutation' => self::$container->get( RootMutationType::class ),
|
||||
'typeLoader' => function( $name ) {
|
||||
return self::resolve_type( $name );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
$result = GraphQL::executeQuery( $schema, $query, null, null, $variable_values );
|
||||
if ( ! empty( $result->errors ) ) {
|
||||
$error_category = current( $result->errors )->getCategory();
|
||||
}
|
||||
|
||||
$output = $result->toArray( self::get_debug_config() );
|
||||
} catch ( \Exception $e ) {
|
||||
$error_category = $e instanceof ClientAware ? $e->getCategory() : 'internal';
|
||||
|
||||
if ( self::get_debug_config() ) {
|
||||
$output = array(
|
||||
'errors' => array(
|
||||
array(
|
||||
'message' => $e->getMessage(),
|
||||
'category' => $error_category,
|
||||
'trace' => $e->getTrace(),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
$output = array(
|
||||
'errors' => array(
|
||||
array(
|
||||
'message' => 'Internal server error',
|
||||
'category' => $error_category,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( $error_category ) {
|
||||
return new \WP_REST_Response( $output, self::$status_codes_by_error_category[ $error_category ] );
|
||||
} else {
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get debug flags for GraphQL::executeQuery()->toArray.
|
||||
*
|
||||
* When debug flags are returned error output will always include the exception error message
|
||||
* (even for exceptions that are not ApiException) and the output will also include
|
||||
* a full stack trace.
|
||||
*
|
||||
* Debug flags are returned when "?verbose_errors" is added to the query string AND
|
||||
* either the user is in the "administrator" role OR the WP_DEBUG constant is set.
|
||||
*
|
||||
* @return false|int Debug flags or false.
|
||||
*/
|
||||
private static function get_debug_config() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! isset( $_GET['verbose_errors'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( wc_current_user_has_role( 'administrator' ) || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) {
|
||||
return Debug::INCLUDE_DEBUG_MESSAGE | Debug::INCLUDE_TRACE;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL\MutationTypes;
|
||||
|
||||
use Automattic\WooCommerce\Internal\GraphQL\ApiException;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\BaseMutationType;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
|
||||
/**
|
||||
* Class for the AddProductAttributeTerm mutation type.
|
||||
*/
|
||||
class AddProductAttributeTerm extends BaseMutationType {
|
||||
|
||||
/**
|
||||
* Get the description of the type.
|
||||
*
|
||||
* @return string The description of the type.
|
||||
*/
|
||||
public function get_description() {
|
||||
return 'Creates a new product attribute term.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the mutation.
|
||||
*
|
||||
* @param array $args Arguments for the mutation operation.
|
||||
* @param mixed $context Context passed from the caller, currently unused.
|
||||
* @param ResolveInfo $info The resolve info passed from the GraphQL engine.
|
||||
* @return array The result of the mutation execution.
|
||||
* @throws ApiException Can't execute the mutation/error when executing the mutation.
|
||||
*/
|
||||
public function execute( $args, $context, ResolveInfo $info ) {
|
||||
|
||||
// TODO: Implement inserting 'menu order' too.
|
||||
|
||||
$input = $args['input'];
|
||||
|
||||
$attribute = wc_get_attribute( $input['attribute_id'] );
|
||||
if ( is_null( $attribute ) ) {
|
||||
throw new ApiException( 'Invalid attribute id' );
|
||||
}
|
||||
|
||||
$insert_args = array();
|
||||
if ( isset( $input['description'] ) ) {
|
||||
$insert_args['description'] = $input['description'];
|
||||
}
|
||||
if ( isset( $input['slug'] ) ) {
|
||||
$insert_args['slug'] = $input['slug'];
|
||||
}
|
||||
|
||||
$term = wp_insert_term( $input['name'], $attribute->slug, $insert_args );
|
||||
if ( is_wp_error( $term ) ) {
|
||||
throw new ApiException( "Can't create term: " . $term->get_error_message() );
|
||||
}
|
||||
|
||||
return array( 'id' => $term['term_id'] );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL\QueryTypes;
|
||||
|
||||
use Automattic\WooCommerce\Internal\GraphQL\ApiException;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\BaseQueryType;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\QueryTypes\ProductAttributeTerms;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\EnumTypes\ProductAttributeOrderBy;
|
||||
|
||||
/**
|
||||
* Class for the ProductAttribute query type.
|
||||
*/
|
||||
class ProductAttribute extends BaseQueryType {
|
||||
|
||||
/**
|
||||
* Get the description of the query.
|
||||
*
|
||||
* @return string The description of the query.
|
||||
*/
|
||||
public function get_description() {
|
||||
return 'A product attribute.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields of the query.
|
||||
*
|
||||
* @return array The fields of the query.
|
||||
*/
|
||||
public function get_fields() {
|
||||
$terms_args = $this->container->get( ProductAttributeTerms::class )->get_args();
|
||||
unset( $terms_args['taxonomy'] );
|
||||
|
||||
$order_by_instance = $this->container->get( ProductAttributeOrderBy::class );
|
||||
$order_by_comma_separated = $order_by_instance->get_comma_separated_value_names();
|
||||
$order_by_default = $order_by_instance->get_enum_value_names();
|
||||
|
||||
return array(
|
||||
'id' => array(
|
||||
'type' => Type::nonNull( Type::id() ),
|
||||
'description' => 'Unique identifier for the resource.',
|
||||
),
|
||||
'name' => array(
|
||||
'type' => Type::nonNull( Type::string() ),
|
||||
'description' => 'Attribute name.',
|
||||
),
|
||||
'slug' => array(
|
||||
'type' => Type::string(),
|
||||
'description' => 'An alphanumeric identifier for the resource unique to its type.',
|
||||
),
|
||||
'type' => array(
|
||||
'type' => Type::string(),
|
||||
'description' => 'Type of attribute. By default only `select` is supported.',
|
||||
),
|
||||
'order_by' => array(
|
||||
'type' => $this->container->get( ProductAttributeOrderBy::class ),
|
||||
'description' => 'Default sort order. Options: ' . $order_by_comma_separated . '. Default is `' . $order_by_default . '`.',
|
||||
),
|
||||
'has_archives' => array(
|
||||
'type' => Type::boolean(),
|
||||
'description' => 'Enable/Disable attribute archives. Default is `false`.',
|
||||
),
|
||||
'terms' => array(
|
||||
'type' => $this->container->get( ProductAttributeTerms::class ),
|
||||
'description' => 'The terms for this attribute.',
|
||||
'args' => $terms_args,
|
||||
'resolve' => function( $resolved_attribute, $args, $context, ResolveInfo $info ) {
|
||||
$args['taxonomy'] = $resolved_attribute['slug'];
|
||||
return $this->container->get( ProductAttributeTerms::class )->resolve( $args, $context, $info );
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments for the query.
|
||||
*
|
||||
* @return array The arguments for the query.
|
||||
*/
|
||||
public function get_args() {
|
||||
return array(
|
||||
'id' => array(
|
||||
'type' => Type::nonNull( Type::id() ),
|
||||
'description' => 'Unique identifier for the resource.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query.
|
||||
*
|
||||
* @param array $args Arguments for the query.
|
||||
* @param mixed $context Context passed from the caller, currently unused.
|
||||
* @param ResolveInfo $info The resolve info passed from the GraphQL engine.
|
||||
* @return array The result of the query execution.
|
||||
* @throws ApiException Can't execute the query/error when executing the query.
|
||||
*/
|
||||
public function resolve( $args, $context, ResolveInfo $info ) {
|
||||
$attribute = (array) wc_get_attribute( $args['id'] );
|
||||
if ( is_null( $attribute ) ) {
|
||||
throw new ApiException( "Can't get this term" );
|
||||
}
|
||||
|
||||
if ( array_key_exists( 'terms', $info->getFieldSelection() ) ) {
|
||||
$terms = get_terms(
|
||||
$attribute->slug,
|
||||
array(
|
||||
'hide_empty' => false,
|
||||
'fields' => 'all',
|
||||
'count' => true,
|
||||
)
|
||||
);
|
||||
$attribute['terms'] = array_map(
|
||||
function( $term ) {
|
||||
return (array) $term;
|
||||
},
|
||||
$terms
|
||||
);
|
||||
}
|
||||
|
||||
return $attribute;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL\QueryTypes;
|
||||
|
||||
use Automattic\WooCommerce\Internal\GraphQL\ApiException;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\BaseQueryType;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
/**
|
||||
* Class for the ProductAttributeTerm query type.
|
||||
*/
|
||||
class ProductAttributeTerm extends BaseQueryType {
|
||||
|
||||
/**
|
||||
* Get the description of the query.
|
||||
*
|
||||
* @return string The description of the query.
|
||||
*/
|
||||
public function get_description() {
|
||||
return 'A term of a product attribute.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fields of the query.
|
||||
*
|
||||
* @return array The fields of the query.
|
||||
*/
|
||||
public function get_fields() {
|
||||
return array(
|
||||
'id' => array(
|
||||
'type' => Type::nonNull( Type::id() ),
|
||||
'description' => 'Unique identifier for the resource.',
|
||||
'resolve' => function( $resolved_term ) {
|
||||
return $resolved_term['term_id'];
|
||||
},
|
||||
),
|
||||
'name' => array(
|
||||
'type' => Type::nonNull( Type::string() ),
|
||||
'description' => 'Term name',
|
||||
),
|
||||
'slug' => array(
|
||||
'type' => Type::string(),
|
||||
'description' => 'An alphanumeric identifier for the resource unique to its type.',
|
||||
),
|
||||
'description' => array(
|
||||
'type' => Type::string(),
|
||||
'description' => 'HTML description of the resource.',
|
||||
),
|
||||
'menu_order' => array(
|
||||
'type' => Type::int(),
|
||||
'description' => 'Menu order, used to custom sort the resource.',
|
||||
),
|
||||
'count' => array(
|
||||
'type' => Type::int(),
|
||||
'description' => 'Number of published products for the resource.',
|
||||
),
|
||||
'taxonomy' => array(
|
||||
'type' => Type::string(),
|
||||
'description' => 'The taxonomy this term belongs to.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments for the query.
|
||||
*
|
||||
* @return array The arguments for the query.
|
||||
*/
|
||||
public function get_args() {
|
||||
return array(
|
||||
'id' => array(
|
||||
'type' => Type::nonNull( Type::id() ),
|
||||
'description' => 'Unique identifier for the resource.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query.
|
||||
*
|
||||
* @param array $args Arguments for the query.
|
||||
* @param mixed $context Context passed from the caller, currently unused.
|
||||
* @param ResolveInfo $info The resolve info passed from the GraphQL engine.
|
||||
* @return array The result of the query execution.
|
||||
* @throws ApiException Can't execute the query/error when executing the query.
|
||||
*/
|
||||
public function resolve( $args, $context, ResolveInfo $info ) {
|
||||
$term = get_term( $args['id'], '', ARRAY_A );
|
||||
if ( ! is_array( $term ) ) {
|
||||
throw new ApiException( "Can't get this term" );
|
||||
}
|
||||
|
||||
return $term;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL\QueryTypes;
|
||||
|
||||
use Automattic\WooCommerce\Internal\GraphQL\ApiException;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\BaseQueryListType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
/**
|
||||
* Class for the ProductAttributeTerms list query type.
|
||||
*/
|
||||
class ProductAttributeTerms extends BaseQueryListType {
|
||||
|
||||
/**
|
||||
* Get the type of the items that the returned list will contain.
|
||||
*
|
||||
* @return string The type of the items that the returned list will contain.
|
||||
*/
|
||||
public function get_item_type_class_name() {
|
||||
return ProductAttributeTerm::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra arguments for the query (other than "offset" and "count").
|
||||
*
|
||||
* @return array $extra_args Any extra arguments for the query.
|
||||
*/
|
||||
public function get_extra_args() {
|
||||
return array(
|
||||
'taxonomy' => array(
|
||||
'type' => Type::string(),
|
||||
'description' => 'Return only the terms for the given taxonomy.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total count of available items.
|
||||
*
|
||||
* @param array $extra_args Any extra arguments passed to the query (other than "offset" and "count").
|
||||
* @return int The total count of available items.
|
||||
*/
|
||||
public function resolve_total_count( $extra_args ) {
|
||||
return $this->resolve_terms(
|
||||
array(
|
||||
'hide_empty' => false,
|
||||
'fields' => 'count',
|
||||
),
|
||||
$extra_args
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the query.
|
||||
*
|
||||
* @param int $offset How many items to skip.
|
||||
* @param int $count How many items to return.
|
||||
* @param array $extra_args Any extra arguments passed to the query (other than "offset" and "count").
|
||||
* @param array $field_selection The part of the ResolveInfo object wupplied by the GraphQL engine that corresponds to the 'items' object.
|
||||
* @return array The list of items resolved.
|
||||
* @throws ApiException When resolve_terms doesn't retun an array.
|
||||
*/
|
||||
public function resolve_items( $offset, $count, $extra_args, $field_selection ) {
|
||||
$result = $this->resolve_terms(
|
||||
array(
|
||||
'hide_empty' => false,
|
||||
'fields' => 'all',
|
||||
'offset' => $offset,
|
||||
'number' => $count,
|
||||
),
|
||||
$extra_args
|
||||
);
|
||||
if ( ! is_array( $result ) ) {
|
||||
throw new ApiException( "Can't get terms" );
|
||||
}
|
||||
return array_map(
|
||||
function( $term ) {
|
||||
return (array) $term;
|
||||
},
|
||||
$result
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Auxiliary method to fetch the terms according to the supplied arguments.
|
||||
*
|
||||
* @param array $args_for_get_terms Arguments for the get_terms call.
|
||||
* @param array $extra_args Extra arguments supplied for the query.
|
||||
* @return mixed The result of executing get_terms.
|
||||
* @throws ApiException When get_terms returns an error.
|
||||
*/
|
||||
private function resolve_terms( $args_for_get_terms, $extra_args ) {
|
||||
if ( isset( $extra_args['taxonomy'] ) ) {
|
||||
$args_for_get_terms['taxonomy'] = $extra_args['taxonomy'];
|
||||
}
|
||||
|
||||
$result = get_terms( $args_for_get_terms );
|
||||
if ( $result instanceof \WP_Error ) {
|
||||
throw new ApiException( $result->get_error_message() );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL\QueryTypes;
|
||||
|
||||
use Automattic\WooCommerce\Internal\GraphQL\BaseQueryListType;
|
||||
|
||||
/**
|
||||
* Class for the ProductAttributes list query type.
|
||||
*/
|
||||
class ProductAttributes extends BaseQueryListType {
|
||||
|
||||
/**
|
||||
* Get the type of the items that the returned list will contain.
|
||||
*
|
||||
* @return string The type of the items that the returned list will contain.
|
||||
*/
|
||||
public function get_item_type_class_name() {
|
||||
return ProductAttribute::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total count of available items.
|
||||
*
|
||||
* @param array $extra_args Any extra arguments passed to the query (other than "offset" and "count").
|
||||
* @return int The total count of available items.
|
||||
*/
|
||||
public function resolve_total_count( $extra_args ) {
|
||||
global $wpdb;
|
||||
|
||||
$sql = "SELECT COUNT(1) FROM {$wpdb->prefix}woocommerce_attribute_taxonomies WHERE attribute_name != '' ORDER BY attribute_name ASC";
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
return $wpdb->get_var( $sql );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the query.
|
||||
*
|
||||
* @param int $offset How many items to skip.
|
||||
* @param int $count How many items to return.
|
||||
* @param array $extra_args Any extra arguments passed to the query (other than "offset" and "count").
|
||||
* @param array $field_selection The part of the ResolveInfo object wupplied by the GraphQL engine that corresponds to the 'items' object.
|
||||
* @return array The list of items resolved.
|
||||
*/
|
||||
public function resolve_items( $offset, $count, $extra_args, $field_selection ) {
|
||||
global $wpdb;
|
||||
|
||||
$sql = $wpdb->prepare(
|
||||
"SELECT * FROM {$wpdb->prefix}woocommerce_attribute_taxonomies WHERE attribute_name != '' ORDER BY attribute_name ASC LIMIT %d OFFSET %d",
|
||||
$count,
|
||||
$offset
|
||||
);
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$rows = $wpdb->get_results( $sql, ARRAY_A );
|
||||
|
||||
return array_map(
|
||||
function( $row ) {
|
||||
return array(
|
||||
'id' => $row['attribute_id'],
|
||||
'name' => $row['attribute_label'],
|
||||
'slug' => wc_attribute_taxonomy_name( $row['attribute_name'] ),
|
||||
'type' => $row['attribute_type'],
|
||||
'order_by' => $row['attribute_orderby'],
|
||||
'has_archives' => (bool) $row['attribute_public'],
|
||||
);
|
||||
},
|
||||
$rows
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL;
|
||||
|
||||
use Automattic\WooCommerce\Internal\GraphQL\MutationTypes\AddProductAttributeTerm;
|
||||
|
||||
/**
|
||||
* Defines the root mutation type for the GraphQL API.
|
||||
*
|
||||
* All the work is actually done in the "BaseRootType" class,
|
||||
* see its code for details.
|
||||
*/
|
||||
class RootMutationType extends BaseRootType {
|
||||
|
||||
/**
|
||||
* Gets the classes that define mutation operations.
|
||||
*
|
||||
* @return string[] The classes that define mutation operations.
|
||||
*/
|
||||
protected function get_object_type_classes() {
|
||||
return array(
|
||||
AddProductAttributeTerm::class,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of this root query.
|
||||
*
|
||||
* @return string The name of this root query.
|
||||
*/
|
||||
protected function get_name() {
|
||||
return 'Mutation';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the description of this root query.
|
||||
*
|
||||
* @return string The description of this root query.
|
||||
*/
|
||||
protected function get_description() {
|
||||
return 'The root query for implementing GraphQL mutations.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the required permission for the mutations defined in this root query.
|
||||
*
|
||||
* @return string The required permission for the mutations defined in this root query.
|
||||
*/
|
||||
protected function get_required_permission() {
|
||||
return 'write';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the method to execute in the target mutation type classes.
|
||||
*
|
||||
* @return string The name of the method to execute in the target mutation type classes.
|
||||
*/
|
||||
protected function get_resolve_method_name() {
|
||||
return 'execute';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\GraphQL;
|
||||
|
||||
use Automattic\WooCommerce\Internal\GraphQL\QueryTypes\ProductAttribute;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\QueryTypes\ProductAttributes;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\QueryTypes\ProductAttributeTerm;
|
||||
use Automattic\WooCommerce\Internal\GraphQL\QueryTypes\ProductAttributeTerms;
|
||||
|
||||
/**
|
||||
* Defines the root query type for the GraphQL API.
|
||||
*
|
||||
* All the work is actually done in the "BaseRootType" class,
|
||||
* see its code for details.
|
||||
*/
|
||||
class RootQueryType extends BaseRootType {
|
||||
|
||||
/**
|
||||
* Gets the classes that define query operations.
|
||||
*
|
||||
* @return string[] The classes that define query operations.
|
||||
*/
|
||||
protected function get_object_type_classes() {
|
||||
return array(
|
||||
ProductAttribute::class,
|
||||
ProductAttributes::class,
|
||||
ProductAttributeTerm::class,
|
||||
ProductAttributeTerms::class,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of this root query.
|
||||
*
|
||||
* @return string The name of this root query.
|
||||
*/
|
||||
protected function get_name() {
|
||||
return 'Query';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the description of this root query.
|
||||
*
|
||||
* @return string The description of this root query.
|
||||
*/
|
||||
protected function get_description() {
|
||||
return 'The query root of WooCommerce GraphQL API.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the required permission for the queries defined in this root query.
|
||||
*
|
||||
* @return string The required permission for the queries defined in this root query.
|
||||
*/
|
||||
protected function get_required_permission() {
|
||||
return 'read';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the method to execute in the target query type classes.
|
||||
*
|
||||
* @return string The name of the method to execute in the target query type classes.
|
||||
*/
|
||||
protected function get_resolve_method_name() {
|
||||
return 'resolve';
|
||||
}
|
||||
}
|
|
@ -57,4 +57,15 @@ final class StringUtil {
|
|||
|
||||
return strcasecmp( $string, $ends_with ) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a class name without namespace from a full class name.
|
||||
*
|
||||
* @param string $full_class_name Full (namespaced) class name.
|
||||
* @return string Class name without the namespace.
|
||||
*/
|
||||
public static function class_name_without_namespace( string $full_class_name ) {
|
||||
$class_name_parts = explode( '\\', $full_class_name );
|
||||
return array_pop( $class_name_parts );
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue