├── README.md
├── tests
├── Admin
│ ├── Media
│ │ ├── upload.gif
│ │ └── StandardTest.php
│ ├── Coupon
│ │ └── StandardTest.php
│ ├── Plugin
│ │ └── StandardTest.php
│ ├── Rule
│ │ └── StandardTest.php
│ ├── Customer
│ │ └── StandardTest.php
│ ├── Service
│ │ └── StandardTest.php
│ ├── Index
│ │ └── StandardTest.php
│ ├── StandardTest.php
│ ├── Order
│ │ └── StandardTest.php
│ ├── Locale
│ │ └── Site
│ │ │ └── StandardTest.php
│ ├── Product
│ │ └── StandardTest.php
│ └── Catalog
│ │ └── StandardTest.php
├── phpunit.xml
├── bootstrap.php
├── phpunit-coverage.xml
└── TestHelper.php
├── .gitignore
├── manifest.php
├── src
├── Admin
│ ├── Graphql
│ │ ├── Exception.php
│ │ ├── Rule
│ │ │ └── Standard.php
│ │ ├── Plugin
│ │ │ └── Standard.php
│ │ ├── Supplier
│ │ │ └── Standard.php
│ │ ├── Attribute
│ │ │ └── Standard.php
│ │ ├── Coupon
│ │ │ └── Standard.php
│ │ ├── ProviderTrait.php
│ │ ├── Product
│ │ │ └── Standard.php
│ │ ├── Service
│ │ │ └── Standard.php
│ │ ├── Index
│ │ │ └── Standard.php
│ │ ├── Standard.php
│ │ ├── Media
│ │ │ └── Standard.php
│ │ ├── Customer
│ │ │ └── Standard.php
│ │ ├── UpdateTrait.php
│ │ ├── Base.php
│ │ ├── Catalog
│ │ │ └── Standard.php
│ │ ├── Locale
│ │ │ └── Site
│ │ │ │ └── Standard.php
│ │ └── Order
│ │ │ └── Standard.php
│ └── Graphql.php
└── GraphQL
│ └── Type
│ └── Definition
│ ├── Json.php
│ └── Upload.php
├── composer.json
├── i18n
└── controller
│ ├── id.po
│ ├── bn.po
│ ├── da.po
│ ├── el.po
│ ├── gu.po
│ ├── hy.po
│ ├── az.po
│ ├── en_US.po
│ ├── et.po
│ ├── fr_LU.po
│ ├── ar.po
│ ├── hi.po
│ ├── he.po
│ ├── fa.po
│ ├── fi.po
│ ├── cs.po
│ ├── bg.po
│ ├── es.po
│ ├── de.po
│ ├── hr.po
│ ├── hu.po
│ └── fr.po
├── phing.xml
├── config
└── admin.php
└── .circleci
└── config.yml
/README.md:
--------------------------------------------------------------------------------
1 | # ai-admin-graphql
2 | Aimeos GraphQL API admin interface
3 |
--------------------------------------------------------------------------------
/tests/Admin/Media/upload.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aimeos/ai-admin-graphql/HEAD/tests/Admin/Media/upload.gif
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .phpunit.result.cache
2 | .phpunit.cache
3 | coveralls.json
4 | coverage.xml
5 | *.log
6 | *.ser
7 | tests/tmp
8 |
--------------------------------------------------------------------------------
/manifest.php:
--------------------------------------------------------------------------------
1 | 'ai-admin-graphql',
5 | 'config' => [
6 | 'config',
7 | ],
8 | 'depends' => [
9 | 'aimeos-core',
10 | ],
11 | 'include' => [
12 | 'src',
13 | ],
14 | ];
15 |
--------------------------------------------------------------------------------
/tests/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ./
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/Admin/Graphql/Exception.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ../src
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Admin/
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aimeos/ai-admin-graphql",
3 | "description": "Aimeos Admin GraphQL API extension",
4 | "keywords": ["aimeos", "extension", "graphql"],
5 | "type": "aimeos-extension",
6 | "license": "LGPL-3.0",
7 | "support": {
8 | "source": "https://github.com/aimeos/ai-admin-graphql",
9 | "issues": "https://github.com/aimeos/ai-admin-graphql/issues",
10 | "forum": "https://aimeos.org/help",
11 | "wiki": "https://aimeos.org/docs"
12 | },
13 | "prefer-stable": true,
14 | "minimum-stability": "dev",
15 | "require": {
16 | "php": "^8.0.11",
17 | "aimeos/aimeos-core": "dev-master",
18 | "webonyx/graphql-php": "~15.0",
19 | "nyholm/psr7": "~1.2"
20 | },
21 | "require-dev": {
22 | "phpunit/phpunit": "~10.0||~11.0"
23 | },
24 | "autoload": {
25 | "psr-4": {
26 | "Aimeos\\": "src"
27 | },
28 | "classmap": [
29 | "src"
30 | ]
31 | },
32 | "autoload-dev": {
33 | "psr-4": {
34 | "Aimeos\\": "tests"
35 | },
36 | "classmap": [
37 | "tests"
38 | ]
39 | }
40 | }
--------------------------------------------------------------------------------
/tests/Admin/Coupon/StandardTest.php:
--------------------------------------------------------------------------------
1 | context = \TestHelper::context();
21 | $this->context->config()->set( 'admin/graphql/debug', true );
22 | $this->context->setView( \TestHelper::view( 'unittest', $this->context->config() ) );
23 | }
24 |
25 |
26 | public function testGetCouponConfig()
27 | {
28 | $body = '{"query":"query {\n getCouponConfig(provider: \"Voucher,Basket\") {\n code\n type\n label\n }\n}\n","variables":{},"operationName":null}';
29 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
30 |
31 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
32 |
33 | $this->assertStringContainsString( '"code":"voucher.productcode"', (string) $response->getBody() );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Admin/Plugin/StandardTest.php:
--------------------------------------------------------------------------------
1 | context = \TestHelper::context();
21 | $this->context->config()->set( 'admin/graphql/debug', true );
22 | $this->context->setView( \TestHelper::view( 'unittest', $this->context->config() ) );
23 | }
24 |
25 |
26 | public function testGetPluginConfig()
27 | {
28 | $body = '{"query":"query {\n getPluginConfig(provider: \"Autofill,Log\", type: \"order\") {\n code\n type\n label\n }\n}\n","variables":{},"operationName":null}';
29 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
30 |
31 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
32 |
33 | $this->assertStringContainsString( '"code":"useorder"', (string) $response->getBody() );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Admin/Rule/StandardTest.php:
--------------------------------------------------------------------------------
1 | context = \TestHelper::context();
21 | $this->context->config()->set( 'admin/graphql/debug', true );
22 | $this->context->setView( \TestHelper::view( 'unittest', $this->context->config() ) );
23 | }
24 |
25 |
26 | public function testGetRuleConfig()
27 | {
28 | $body = '{"query":"query {\n getRuleConfig(provider: \"Percent,Category\", type: \"catalog\") {\n code\n type\n label\n }\n}\n","variables":{},"operationName":null}';
29 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
30 |
31 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
32 |
33 | $this->assertStringContainsString( '"code":"percent"', (string) $response->getBody() );
34 | $this->assertStringContainsString( '"code":"category.code"', (string) $response->getBody() );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Admin/Graphql/Supplier/Standard.php:
--------------------------------------------------------------------------------
1 | $this->types()->outputType( $domain ),
36 | 'args' => [
37 | ['name' => 'code', 'type' => Type::string(), 'description' => 'Unique code'],
38 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
39 | ],
40 | 'resolve' => $this->findItem( $domain ),
41 | ];
42 |
43 | return $list;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Admin/Customer/StandardTest.php:
--------------------------------------------------------------------------------
1 | context = \TestHelper::context();
21 | $this->context->config()->set( 'admin/graphql/debug', true );
22 | $this->context->setView( \TestHelper::view( 'unittest', $this->context->config() ) );
23 | }
24 |
25 |
26 | public function testFindCustomer()
27 | {
28 | $body = '{"query":"query {\n findCustomer(code: \"test@example.com\") {\n id\n code groups\n }\n}\n","variables":{},"operationName":null}';
29 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
30 |
31 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
32 |
33 | $this->assertStringContainsString( '"code":"test@example.com"', (string) $response->getBody() );
34 | }
35 |
36 |
37 | public function testAggregateCustomers()
38 | {
39 | $body = '{"query":"query {\n aggregateCustomers(key: [\"customer.status\"]) {\n aggregates\n }\n}\n","variables":{},"operationName":null}';
40 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
41 |
42 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
43 |
44 | $this->assertStringContainsString( '{\"0\":1,\"1\":2}', (string) $response->getBody() );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/i18n/controller/id.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: aimeos-core\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2023-09-26 15:47+0200\n"
11 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
12 | "Last-Translator: FULL NAME \n"
13 | "Language-Team: Indonesian (http://app.transifex.com/aimeos/aimeos-core/language/id/)\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Language: id\n"
18 | "Plural-Forms: nplurals=1; plural=0;\n"
19 |
20 | #, php-format
21 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
22 | msgstr ""
23 |
24 | #, php-format
25 | msgid "Class \"%1$s\" not available"
26 | msgstr ""
27 |
28 | #, php-format
29 | msgid "Class \"%1$s\" not found"
30 | msgstr ""
31 |
32 | #, php-format
33 | msgid "Downloading file \"%1$s\" failed"
34 | msgstr ""
35 |
36 | #, php-format
37 | msgid "File \"%1$s\" not found"
38 | msgstr ""
39 |
40 | #, php-format
41 | msgid "File type \"%1$s\" is not allowed"
42 | msgstr ""
43 |
44 | #, php-format
45 | msgid "Invalid characters in class name \"%1$s\""
46 | msgstr ""
47 |
48 | #, php-format
49 | msgid "No allowed image types configured for \"%1$s\""
50 | msgstr ""
51 |
52 | #, php-format
53 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
54 | msgstr ""
55 |
56 | #, php-format
57 | msgid "Unable to create directory \"%1$s\""
58 | msgstr ""
59 |
60 | #, php-format
61 | msgid "Unable to create file in \"%1$s\""
62 | msgstr ""
63 |
--------------------------------------------------------------------------------
/i18n/controller/bn.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: aimeos-core\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2023-09-26 15:47+0200\n"
11 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
12 | "Last-Translator: FULL NAME \n"
13 | "Language-Team: Bengali (http://app.transifex.com/aimeos/aimeos-core/language/bn/)\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Language: bn\n"
18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19 |
20 | #, php-format
21 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
22 | msgstr ""
23 |
24 | #, php-format
25 | msgid "Class \"%1$s\" not available"
26 | msgstr ""
27 |
28 | #, php-format
29 | msgid "Class \"%1$s\" not found"
30 | msgstr ""
31 |
32 | #, php-format
33 | msgid "Downloading file \"%1$s\" failed"
34 | msgstr ""
35 |
36 | #, php-format
37 | msgid "File \"%1$s\" not found"
38 | msgstr ""
39 |
40 | #, php-format
41 | msgid "File type \"%1$s\" is not allowed"
42 | msgstr ""
43 |
44 | #, php-format
45 | msgid "Invalid characters in class name \"%1$s\""
46 | msgstr ""
47 |
48 | #, php-format
49 | msgid "No allowed image types configured for \"%1$s\""
50 | msgstr ""
51 |
52 | #, php-format
53 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
54 | msgstr ""
55 |
56 | #, php-format
57 | msgid "Unable to create directory \"%1$s\""
58 | msgstr ""
59 |
60 | #, php-format
61 | msgid "Unable to create file in \"%1$s\""
62 | msgstr ""
63 |
--------------------------------------------------------------------------------
/i18n/controller/da.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: aimeos-core\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2023-09-26 15:47+0200\n"
11 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
12 | "Last-Translator: FULL NAME \n"
13 | "Language-Team: Danish (http://app.transifex.com/aimeos/aimeos-core/language/da/)\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Language: da\n"
18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19 |
20 | #, php-format
21 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
22 | msgstr ""
23 |
24 | #, php-format
25 | msgid "Class \"%1$s\" not available"
26 | msgstr ""
27 |
28 | #, php-format
29 | msgid "Class \"%1$s\" not found"
30 | msgstr ""
31 |
32 | #, php-format
33 | msgid "Downloading file \"%1$s\" failed"
34 | msgstr ""
35 |
36 | #, php-format
37 | msgid "File \"%1$s\" not found"
38 | msgstr ""
39 |
40 | #, php-format
41 | msgid "File type \"%1$s\" is not allowed"
42 | msgstr ""
43 |
44 | #, php-format
45 | msgid "Invalid characters in class name \"%1$s\""
46 | msgstr ""
47 |
48 | #, php-format
49 | msgid "No allowed image types configured for \"%1$s\""
50 | msgstr ""
51 |
52 | #, php-format
53 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
54 | msgstr ""
55 |
56 | #, php-format
57 | msgid "Unable to create directory \"%1$s\""
58 | msgstr ""
59 |
60 | #, php-format
61 | msgid "Unable to create file in \"%1$s\""
62 | msgstr ""
63 |
--------------------------------------------------------------------------------
/i18n/controller/el.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: aimeos-core\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2023-09-26 15:47+0200\n"
11 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
12 | "Last-Translator: FULL NAME \n"
13 | "Language-Team: Greek (http://app.transifex.com/aimeos/aimeos-core/language/el/)\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Language: el\n"
18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19 |
20 | #, php-format
21 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
22 | msgstr ""
23 |
24 | #, php-format
25 | msgid "Class \"%1$s\" not available"
26 | msgstr ""
27 |
28 | #, php-format
29 | msgid "Class \"%1$s\" not found"
30 | msgstr ""
31 |
32 | #, php-format
33 | msgid "Downloading file \"%1$s\" failed"
34 | msgstr ""
35 |
36 | #, php-format
37 | msgid "File \"%1$s\" not found"
38 | msgstr ""
39 |
40 | #, php-format
41 | msgid "File type \"%1$s\" is not allowed"
42 | msgstr ""
43 |
44 | #, php-format
45 | msgid "Invalid characters in class name \"%1$s\""
46 | msgstr ""
47 |
48 | #, php-format
49 | msgid "No allowed image types configured for \"%1$s\""
50 | msgstr ""
51 |
52 | #, php-format
53 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
54 | msgstr ""
55 |
56 | #, php-format
57 | msgid "Unable to create directory \"%1$s\""
58 | msgstr ""
59 |
60 | #, php-format
61 | msgid "Unable to create file in \"%1$s\""
62 | msgstr ""
63 |
--------------------------------------------------------------------------------
/i18n/controller/gu.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: aimeos-core\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2023-09-26 15:47+0200\n"
11 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
12 | "Last-Translator: FULL NAME \n"
13 | "Language-Team: Gujarati (http://app.transifex.com/aimeos/aimeos-core/language/gu/)\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Language: gu\n"
18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19 |
20 | #, php-format
21 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
22 | msgstr ""
23 |
24 | #, php-format
25 | msgid "Class \"%1$s\" not available"
26 | msgstr ""
27 |
28 | #, php-format
29 | msgid "Class \"%1$s\" not found"
30 | msgstr ""
31 |
32 | #, php-format
33 | msgid "Downloading file \"%1$s\" failed"
34 | msgstr ""
35 |
36 | #, php-format
37 | msgid "File \"%1$s\" not found"
38 | msgstr ""
39 |
40 | #, php-format
41 | msgid "File type \"%1$s\" is not allowed"
42 | msgstr ""
43 |
44 | #, php-format
45 | msgid "Invalid characters in class name \"%1$s\""
46 | msgstr ""
47 |
48 | #, php-format
49 | msgid "No allowed image types configured for \"%1$s\""
50 | msgstr ""
51 |
52 | #, php-format
53 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
54 | msgstr ""
55 |
56 | #, php-format
57 | msgid "Unable to create directory \"%1$s\""
58 | msgstr ""
59 |
60 | #, php-format
61 | msgid "Unable to create file in \"%1$s\""
62 | msgstr ""
63 |
--------------------------------------------------------------------------------
/i18n/controller/hy.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: aimeos-core\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2023-09-26 15:47+0200\n"
11 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
12 | "Last-Translator: FULL NAME \n"
13 | "Language-Team: Armenian (http://app.transifex.com/aimeos/aimeos-core/language/hy/)\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Language: hy\n"
18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19 |
20 | #, php-format
21 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
22 | msgstr ""
23 |
24 | #, php-format
25 | msgid "Class \"%1$s\" not available"
26 | msgstr ""
27 |
28 | #, php-format
29 | msgid "Class \"%1$s\" not found"
30 | msgstr ""
31 |
32 | #, php-format
33 | msgid "Downloading file \"%1$s\" failed"
34 | msgstr ""
35 |
36 | #, php-format
37 | msgid "File \"%1$s\" not found"
38 | msgstr ""
39 |
40 | #, php-format
41 | msgid "File type \"%1$s\" is not allowed"
42 | msgstr ""
43 |
44 | #, php-format
45 | msgid "Invalid characters in class name \"%1$s\""
46 | msgstr ""
47 |
48 | #, php-format
49 | msgid "No allowed image types configured for \"%1$s\""
50 | msgstr ""
51 |
52 | #, php-format
53 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
54 | msgstr ""
55 |
56 | #, php-format
57 | msgid "Unable to create directory \"%1$s\""
58 | msgstr ""
59 |
60 | #, php-format
61 | msgid "Unable to create file in \"%1$s\""
62 | msgstr ""
63 |
--------------------------------------------------------------------------------
/i18n/controller/az.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: aimeos-core\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2023-09-26 15:47+0200\n"
11 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
12 | "Last-Translator: FULL NAME \n"
13 | "Language-Team: Azerbaijani (http://app.transifex.com/aimeos/aimeos-core/language/az/)\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Language: az\n"
18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19 |
20 | #, php-format
21 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
22 | msgstr ""
23 |
24 | #, php-format
25 | msgid "Class \"%1$s\" not available"
26 | msgstr ""
27 |
28 | #, php-format
29 | msgid "Class \"%1$s\" not found"
30 | msgstr ""
31 |
32 | #, php-format
33 | msgid "Downloading file \"%1$s\" failed"
34 | msgstr ""
35 |
36 | #, php-format
37 | msgid "File \"%1$s\" not found"
38 | msgstr ""
39 |
40 | #, php-format
41 | msgid "File type \"%1$s\" is not allowed"
42 | msgstr ""
43 |
44 | #, php-format
45 | msgid "Invalid characters in class name \"%1$s\""
46 | msgstr ""
47 |
48 | #, php-format
49 | msgid "No allowed image types configured for \"%1$s\""
50 | msgstr ""
51 |
52 | #, php-format
53 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
54 | msgstr ""
55 |
56 | #, php-format
57 | msgid "Unable to create directory \"%1$s\""
58 | msgstr ""
59 |
60 | #, php-format
61 | msgid "Unable to create file in \"%1$s\""
62 | msgstr ""
63 |
--------------------------------------------------------------------------------
/i18n/controller/en_US.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: aimeos-core\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2023-09-26 15:47+0200\n"
11 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
12 | "Last-Translator: FULL NAME \n"
13 | "Language-Team: English (United States) (http://app.transifex.com/aimeos/aimeos-core/language/en_US/)\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Language: en_US\n"
18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19 |
20 | #, php-format
21 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
22 | msgstr ""
23 |
24 | #, php-format
25 | msgid "Class \"%1$s\" not available"
26 | msgstr ""
27 |
28 | #, php-format
29 | msgid "Class \"%1$s\" not found"
30 | msgstr ""
31 |
32 | #, php-format
33 | msgid "Downloading file \"%1$s\" failed"
34 | msgstr ""
35 |
36 | #, php-format
37 | msgid "File \"%1$s\" not found"
38 | msgstr ""
39 |
40 | #, php-format
41 | msgid "File type \"%1$s\" is not allowed"
42 | msgstr ""
43 |
44 | #, php-format
45 | msgid "Invalid characters in class name \"%1$s\""
46 | msgstr ""
47 |
48 | #, php-format
49 | msgid "No allowed image types configured for \"%1$s\""
50 | msgstr ""
51 |
52 | #, php-format
53 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
54 | msgstr ""
55 |
56 | #, php-format
57 | msgid "Unable to create directory \"%1$s\""
58 | msgstr ""
59 |
60 | #, php-format
61 | msgid "Unable to create file in \"%1$s\""
62 | msgstr ""
63 |
--------------------------------------------------------------------------------
/i18n/controller/et.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: aimeos-core\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2022-12-07 11:38+0100\n"
11 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
12 | "Last-Translator: FULL NAME \n"
13 | "Language-Team: Estonian (http://app.transifex.com/aimeos/aimeos-core/language/et/)\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Language: et\n"
18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19 |
20 | #, php-format
21 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
22 | msgstr ""
23 |
24 | #, php-format
25 | msgid "Class \"%1$s\" not available"
26 | msgstr ""
27 |
28 | #, php-format
29 | msgid "Class \"%1$s\" not found"
30 | msgstr ""
31 |
32 | #, php-format
33 | msgid "Downloading file \"%1$s\" failed"
34 | msgstr ""
35 |
36 | #, php-format
37 | msgid "File \"%1$s\" not found"
38 | msgstr ""
39 |
40 | #, php-format
41 | msgid "File type \"%1$s\" is not allowed"
42 | msgstr ""
43 |
44 | #, php-format
45 | msgid "Invalid characters in class name \"%1$s\""
46 | msgstr ""
47 |
48 | #, php-format
49 | msgid "No allowed image types configured for \"%1$s\""
50 | msgstr ""
51 |
52 | #, php-format
53 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
54 | msgstr "Kategooriast \"%3$s\" ei leitud ühtegi toodet nimega \"%1$s/%2$s\""
55 |
56 | #, php-format
57 | msgid "Unable to create directory \"%1$s\""
58 | msgstr ""
59 |
60 | #, php-format
61 | msgid "Unable to create file in \"%1$s\""
62 | msgstr ""
63 |
--------------------------------------------------------------------------------
/tests/Admin/Service/StandardTest.php:
--------------------------------------------------------------------------------
1 | context = \TestHelper::context();
21 | $this->context->config()->set( 'admin/graphql/debug', true );
22 | $this->context->setView( \TestHelper::view( 'unittest', $this->context->config() ) );
23 | }
24 |
25 |
26 | public function testFindService()
27 | {
28 | $body = '{"query":"query {\n findService(code: \"unitpaymentcode\") {\n id\n code\n }\n}\n","variables":{},"operationName":null}';
29 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
30 |
31 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
32 |
33 | $this->assertStringContainsString( '"code":"unitpaymentcode"', (string) $response->getBody() );
34 | }
35 |
36 |
37 | public function testGetServiceConfig()
38 | {
39 | $body = '{"query":"query {\n getServiceConfig(provider: \"Xml,BasketValues\", type: \"delivery\") {\n code\n type\n label\n }\n}\n","variables":{},"operationName":null}';
40 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
41 |
42 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
43 |
44 | $this->assertStringContainsString( '"code":"xml.exportpath"', (string) $response->getBody() );
45 | $this->assertStringContainsString( '"code":"basketvalues.total-value-min"', (string) $response->getBody() );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/i18n/controller/fr_LU.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: aimeos-core\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2023-09-26 15:47+0200\n"
11 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
12 | "Last-Translator: FULL NAME \n"
13 | "Language-Team: French (Luxembourg) (http://app.transifex.com/aimeos/aimeos-core/language/fr_LU/)\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Language: fr_LU\n"
18 | "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
19 |
20 | #, php-format
21 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
22 | msgstr ""
23 |
24 | #, php-format
25 | msgid "Class \"%1$s\" not available"
26 | msgstr ""
27 |
28 | #, php-format
29 | msgid "Class \"%1$s\" not found"
30 | msgstr ""
31 |
32 | #, php-format
33 | msgid "Downloading file \"%1$s\" failed"
34 | msgstr ""
35 |
36 | #, php-format
37 | msgid "File \"%1$s\" not found"
38 | msgstr ""
39 |
40 | #, php-format
41 | msgid "File type \"%1$s\" is not allowed"
42 | msgstr ""
43 |
44 | #, php-format
45 | msgid "Invalid characters in class name \"%1$s\""
46 | msgstr ""
47 |
48 | #, php-format
49 | msgid "No allowed image types configured for \"%1$s\""
50 | msgstr ""
51 |
52 | #, php-format
53 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
54 | msgstr ""
55 |
56 | #, php-format
57 | msgid "Unable to create directory \"%1$s\""
58 | msgstr ""
59 |
60 | #, php-format
61 | msgid "Unable to create file in \"%1$s\""
62 | msgstr ""
63 |
--------------------------------------------------------------------------------
/i18n/controller/ar.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: aimeos-core\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2023-09-26 15:47+0200\n"
11 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
12 | "Last-Translator: FULL NAME \n"
13 | "Language-Team: Arabic (http://app.transifex.com/aimeos/aimeos-core/language/ar/)\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Language: ar\n"
18 | "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
19 |
20 | #, php-format
21 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
22 | msgstr ""
23 |
24 | #, php-format
25 | msgid "Class \"%1$s\" not available"
26 | msgstr ""
27 |
28 | #, php-format
29 | msgid "Class \"%1$s\" not found"
30 | msgstr ""
31 |
32 | #, php-format
33 | msgid "Downloading file \"%1$s\" failed"
34 | msgstr ""
35 |
36 | #, php-format
37 | msgid "File \"%1$s\" not found"
38 | msgstr ""
39 |
40 | #, php-format
41 | msgid "File type \"%1$s\" is not allowed"
42 | msgstr ""
43 |
44 | #, php-format
45 | msgid "Invalid characters in class name \"%1$s\""
46 | msgstr ""
47 |
48 | #, php-format
49 | msgid "No allowed image types configured for \"%1$s\""
50 | msgstr ""
51 |
52 | #, php-format
53 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
54 | msgstr ""
55 |
56 | #, php-format
57 | msgid "Unable to create directory \"%1$s\""
58 | msgstr ""
59 |
60 | #, php-format
61 | msgid "Unable to create file in \"%1$s\""
62 | msgstr ""
63 |
--------------------------------------------------------------------------------
/i18n/controller/hi.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | # Dharit Maniyar, 2022
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: aimeos-core\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2022-12-07 11:38+0100\n"
12 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
13 | "Last-Translator: Dharit Maniyar, 2022\n"
14 | "Language-Team: Hindi (http://app.transifex.com/aimeos/aimeos-core/language/hi/)\n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Language: hi\n"
19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20 |
21 | #, php-format
22 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
23 | msgstr ""
24 |
25 | #, php-format
26 | msgid "Class \"%1$s\" not available"
27 | msgstr ""
28 |
29 | #, php-format
30 | msgid "Class \"%1$s\" not found"
31 | msgstr ""
32 |
33 | #, php-format
34 | msgid "Downloading file \"%1$s\" failed"
35 | msgstr "फ़ाइल \"%1$s\" डाउनलोड करना विफल रहा"
36 |
37 | #, php-format
38 | msgid "File \"%1$s\" not found"
39 | msgstr "फाइल \"%1$s \" नहीं मिली"
40 |
41 | #, php-format
42 | msgid "File type \"%1$s\" is not allowed"
43 | msgstr ""
44 |
45 | #, php-format
46 | msgid "Invalid characters in class name \"%1$s\""
47 | msgstr ""
48 |
49 | #, php-format
50 | msgid "No allowed image types configured for \"%1$s\""
51 | msgstr ""
52 |
53 | #, php-format
54 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
55 | msgstr ""
56 |
57 | #, php-format
58 | msgid "Unable to create directory \"%1$s\""
59 | msgstr ""
60 |
61 | #, php-format
62 | msgid "Unable to create file in \"%1$s\""
63 | msgstr ""
64 |
--------------------------------------------------------------------------------
/i18n/controller/he.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: aimeos-core\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2023-09-26 15:47+0200\n"
11 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
12 | "Last-Translator: FULL NAME \n"
13 | "Language-Team: Hebrew (http://app.transifex.com/aimeos/aimeos-core/language/he/)\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Language: he\n"
18 | "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n"
19 |
20 | #, php-format
21 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
22 | msgstr ""
23 |
24 | #, php-format
25 | msgid "Class \"%1$s\" not available"
26 | msgstr ""
27 |
28 | #, php-format
29 | msgid "Class \"%1$s\" not found"
30 | msgstr ""
31 |
32 | #, php-format
33 | msgid "Downloading file \"%1$s\" failed"
34 | msgstr ""
35 |
36 | #, php-format
37 | msgid "File \"%1$s\" not found"
38 | msgstr ""
39 |
40 | #, php-format
41 | msgid "File type \"%1$s\" is not allowed"
42 | msgstr ""
43 |
44 | #, php-format
45 | msgid "Invalid characters in class name \"%1$s\""
46 | msgstr ""
47 |
48 | #, php-format
49 | msgid "No allowed image types configured for \"%1$s\""
50 | msgstr ""
51 |
52 | #, php-format
53 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
54 | msgstr ""
55 |
56 | #, php-format
57 | msgid "Unable to create directory \"%1$s\""
58 | msgstr ""
59 |
60 | #, php-format
61 | msgid "Unable to create file in \"%1$s\""
62 | msgstr ""
63 |
--------------------------------------------------------------------------------
/i18n/controller/fa.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: aimeos-core\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2022-12-07 11:38+0100\n"
11 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
12 | "Last-Translator: FULL NAME \n"
13 | "Language-Team: Persian (http://app.transifex.com/aimeos/aimeos-core/language/fa/)\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Language: fa\n"
18 | "Plural-Forms: nplurals=2; plural=(n > 1);\n"
19 |
20 | #, php-format
21 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
22 | msgstr "کلاس \"%1$s\" از رابط کاربری \"%2$s\" پیاده سازی نشده است"
23 |
24 | #, php-format
25 | msgid "Class \"%1$s\" not available"
26 | msgstr "کلاس \"%1$s\" معتبر نمیباشد"
27 |
28 | #, php-format
29 | msgid "Class \"%1$s\" not found"
30 | msgstr "کلاس \"%1$s\" موجود نمیباشد"
31 |
32 | #, php-format
33 | msgid "Downloading file \"%1$s\" failed"
34 | msgstr ""
35 |
36 | #, php-format
37 | msgid "File \"%1$s\" not found"
38 | msgstr ""
39 |
40 | #, php-format
41 | msgid "File type \"%1$s\" is not allowed"
42 | msgstr ""
43 |
44 | #, php-format
45 | msgid "Invalid characters in class name \"%1$s\""
46 | msgstr "کاراکترهای نا معتبر در کلاس \"%1$s\""
47 |
48 | #, php-format
49 | msgid "No allowed image types configured for \"%1$s\""
50 | msgstr ""
51 |
52 | #, php-format
53 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
54 | msgstr "هیچ نوع آیتمی برای \"%1$s/%2$s\" در \"%3$s\" پیدا نشد"
55 |
56 | #, php-format
57 | msgid "Unable to create directory \"%1$s\""
58 | msgstr ""
59 |
60 | #, php-format
61 | msgid "Unable to create file in \"%1$s\""
62 | msgstr ""
63 |
--------------------------------------------------------------------------------
/i18n/controller/fi.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: aimeos-core\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2022-12-07 11:38+0100\n"
11 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
12 | "Last-Translator: FULL NAME \n"
13 | "Language-Team: Finnish (http://app.transifex.com/aimeos/aimeos-core/language/fi/)\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Language: fi\n"
18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19 |
20 | #, php-format
21 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
22 | msgstr "Luokka \"%1$s\" ei toteuta liitäntää \"%2$s\""
23 |
24 | #, php-format
25 | msgid "Class \"%1$s\" not available"
26 | msgstr "Luokka \"%1$s\" ei saatavilla"
27 |
28 | #, php-format
29 | msgid "Class \"%1$s\" not found"
30 | msgstr "Luokka \"%1$s\" ei löydy"
31 |
32 | #, php-format
33 | msgid "Downloading file \"%1$s\" failed"
34 | msgstr ""
35 |
36 | #, php-format
37 | msgid "File \"%1$s\" not found"
38 | msgstr ""
39 |
40 | #, php-format
41 | msgid "File type \"%1$s\" is not allowed"
42 | msgstr ""
43 |
44 | #, php-format
45 | msgid "Invalid characters in class name \"%1$s\""
46 | msgstr "Virheellisiä merkkejä luokkanimessä \"%1$s\""
47 |
48 | #, php-format
49 | msgid "No allowed image types configured for \"%1$s\""
50 | msgstr ""
51 |
52 | #, php-format
53 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
54 | msgstr "Minkään tyyppistä tavaraa ei löytynyt osiosta \"%3$s” haulla \"%1$s/%2$s\""
55 |
56 | #, php-format
57 | msgid "Unable to create directory \"%1$s\""
58 | msgstr ""
59 |
60 | #, php-format
61 | msgid "Unable to create file in \"%1$s\""
62 | msgstr ""
63 |
--------------------------------------------------------------------------------
/phing.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/Admin/Graphql/Attribute/Standard.php:
--------------------------------------------------------------------------------
1 | $this->types()->outputType( $domain ),
36 | 'args' => [
37 | ['name' => 'code', 'type' => Type::string(), 'description' => 'Unique code'],
38 | ['name' => 'domain', 'type' => Type::string(), 'description' => 'Domain of the attribute'],
39 | ['name' => 'type', 'type' => Type::string(), 'description' => 'Attribute type'],
40 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
41 | ],
42 | 'resolve' => $this->findItem( $domain ),
43 | ];
44 |
45 | return $list;
46 | }
47 |
48 |
49 | /**
50 | * Returns a closure for returning a single item by its code
51 | *
52 | * @param string $domain Domain path of the manager
53 | * @return \Closure Anonymous method returning one item
54 | */
55 | protected function findItem( string $domain ) : \Closure
56 | {
57 | return function( $root, $args, $context ) use ( $domain ) {
58 | return \Aimeos\MShop::create( $this->context(), $domain )->find( $args['code'], $args['include'], $args['domain'], $args['type'] );
59 | };
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/Admin/Media/StandardTest.php:
--------------------------------------------------------------------------------
1 | context = \TestHelper::context();
21 | $this->context->config()->set( 'admin/graphql/debug', true );
22 | $this->context->setView( \TestHelper::view( 'unittest', $this->context->config() ) );
23 | }
24 |
25 |
26 | public function testSaveMedia()
27 | {
28 | $content = file_get_contents( __DIR__ . '/upload.gif' );
29 | $stream = \Nyholm\Psr7\Stream::create( $content );
30 | $file = new \Nyholm\Psr7\UploadedFile( $stream, strlen( $content ), \UPLOAD_ERR_OK, 'upload.gif' );
31 |
32 | $stub = $this->getMockBuilder( '\\Aimeos\\MShop\\Media\\Manager\\Standard' )
33 | ->setConstructorArgs( [$this->context] )
34 | ->onlyMethods( ['save', 'type'] )
35 | ->getMock();
36 |
37 | $stub->method( 'type' )->willReturn( ['media'] );
38 | $stub->expects( $this->once() )->method( 'save' )->willReturnArgument( 0 );
39 |
40 | \Aimeos\MShop::inject( '\\Aimeos\\MShop\\Media\\Manager\\Standard', $stub );
41 |
42 | $body = '{"query":"mutation($file: Upload, $preview: Upload) {\n saveMedia(input: {\n domain: \"product\"\n file: $file\n filepreview: $preview\n }) {\n id\n label\n url\n preview\n }\n}\n","variables":{ "file": null, "preview": null },"operationName":null}';
43 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost' );
44 | $request = $request->withParsedBody( ['operations' => $body, 'map' => json_encode( [1 => ['variables.file']] )] );
45 | $request = $request->withUploadedFiles( [1 => $file] );
46 |
47 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
48 |
49 | $this->assertStringContainsString( '"label":"upload.gif"', (string) $response->getBody() );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/i18n/controller/cs.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | # Martin Vyšohlíd , 2016
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: aimeos-core\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2022-12-07 11:38+0100\n"
12 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
13 | "Last-Translator: Martin Vyšohlíd , 2016\n"
14 | "Language-Team: Czech (http://app.transifex.com/aimeos/aimeos-core/language/cs/)\n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Language: cs\n"
19 | "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
20 |
21 | #, php-format
22 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
23 | msgstr "Třída \"%1$s\" neimplementuje rozhraní \"%2$s\""
24 |
25 | #, php-format
26 | msgid "Class \"%1$s\" not available"
27 | msgstr "Třída \"%1$s\" není k dispozici"
28 |
29 | #, php-format
30 | msgid "Class \"%1$s\" not found"
31 | msgstr "Třída \"%1$s\" nenalezena"
32 |
33 | #, php-format
34 | msgid "Downloading file \"%1$s\" failed"
35 | msgstr ""
36 |
37 | #, php-format
38 | msgid "File \"%1$s\" not found"
39 | msgstr "Soubor %1$s nenalezen"
40 |
41 | #, php-format
42 | msgid "File type \"%1$s\" is not allowed"
43 | msgstr ""
44 |
45 | #, php-format
46 | msgid "Invalid characters in class name \"%1$s\""
47 | msgstr "Zakázané znaky ve jméně třídy \"%1$s\""
48 |
49 | #, php-format
50 | msgid "No allowed image types configured for \"%1$s\""
51 | msgstr "Pro \"%1$s\" je nastaven nedovolený typ obrázku"
52 |
53 | #, php-format
54 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
55 | msgstr "Nenalezen žádný typ položky pro \"%1$s/%2$s\" v \"%3$s\""
56 |
57 | #, php-format
58 | msgid "Unable to create directory \"%1$s\""
59 | msgstr ""
60 |
61 | #, php-format
62 | msgid "Unable to create file in \"%1$s\""
63 | msgstr ""
64 |
--------------------------------------------------------------------------------
/src/Admin/Graphql/Coupon/Standard.php:
--------------------------------------------------------------------------------
1 | Type::listOf( $this->types()->configOutputType( $domain ) ),
36 | 'args' => [
37 | ['name' => 'provider', 'type' => Type::string(), 'description' => 'Provider name with decorators separated by comma'],
38 | ],
39 | 'resolve' => $this->getConfig( $domain ),
40 | ];
41 |
42 | return $list;
43 | }
44 |
45 |
46 | /**
47 | * Returns a closure for returning the provider configuration
48 | *
49 | * @param string $domain Domain path of the manager
50 | * @return \Closure Anonymous method returning one item
51 | */
52 | protected function getConfig( string $domain ) : \Closure
53 | {
54 | return function( $root, $args, $context ) use ( $domain ) {
55 |
56 | $context = $this->context();
57 | $groups = $context->config()->get( 'admin/graphql/resource/' . $domain . '/get', [] );
58 |
59 | if( $context->view()->access( $groups ) !== true ) {
60 | throw new \Aimeos\Admin\Graphql\Exception( 'Forbidden', 403 );
61 | }
62 |
63 | $manager = \Aimeos\MShop::create( $context, $domain );
64 | $item = $manager->create()->setProvider( $args['provider'] );
65 |
66 | return $manager->getProvider( $item, '' )->getConfigBE();
67 | };
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/tests/Admin/Index/StandardTest.php:
--------------------------------------------------------------------------------
1 | context = \TestHelper::context();
21 | $this->context->config()->set( 'admin/graphql/debug', true );
22 | $this->context->setView( \TestHelper::view( 'unittest', $this->context->config() ) );
23 | }
24 |
25 |
26 | public function testAggregateIndex()
27 | {
28 | $body = '{"query":"query {\n aggregateIndex(key: [\"index.catalog.id\"]) {\n aggregates\n }\n}\n","variables":{},"operationName":null}';
29 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
30 |
31 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
32 | $result = json_decode( (string) $response->getBody(), true );
33 | $counts = json_decode( $result['data']['aggregateIndex']['aggregates'], true );
34 |
35 | $this->assertEquals( 4, count( $counts ) );
36 | $this->assertContains( 4, $counts );
37 | $this->assertContains( 3, $counts );
38 | $this->assertContains( 2, $counts );
39 | }
40 |
41 |
42 | public function testSearchIndex()
43 | {
44 | $id = \Aimeos\MShop::create( $this->context, 'catalog' )->find( 'internet' )->getId;
45 |
46 | $search = addslashes( addslashes( json_encode( ['==' => ['index.catalog.id' => $id]] ) ) );
47 | $body = '{"query":"query {\n searchIndex(filter: \"' . $search . '\", sort: [\"sort:index.catalog:position()\"]) {\n items {\n id\n code\n }\n total\n }\n}\n","variables":{},"operationName":null}';
48 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
49 |
50 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
51 |
52 | $this->assertStringContainsString( '"code":"EFGH"', (string) $response->getBody() );
53 | $this->assertStringContainsString( '"total":20', (string) $response->getBody() );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/i18n/controller/bg.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | # Plamen Petkov , 2016,2019
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: aimeos-core\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2022-12-07 11:38+0100\n"
12 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
13 | "Last-Translator: Plamen Petkov , 2016,2019\n"
14 | "Language-Team: Bulgarian (http://app.transifex.com/aimeos/aimeos-core/language/bg/)\n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Language: bg\n"
19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20 |
21 | #, php-format
22 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
23 | msgstr "Класът \"%1$s\" не изпълнява интерфейса \"%2$s\""
24 |
25 | #, php-format
26 | msgid "Class \"%1$s\" not available"
27 | msgstr "Класът \"%1$s\" не е намерен"
28 |
29 | #, php-format
30 | msgid "Class \"%1$s\" not found"
31 | msgstr "Класът \"%1$s\" не е намерен"
32 |
33 | #, php-format
34 | msgid "Downloading file \"%1$s\" failed"
35 | msgstr "Изтеглянето на файл \"%1$s\" неуспешно"
36 |
37 | #, php-format
38 | msgid "File \"%1$s\" not found"
39 | msgstr "Файл \"%1$s\" не е намерен"
40 |
41 | #, php-format
42 | msgid "File type \"%1$s\" is not allowed"
43 | msgstr ""
44 |
45 | #, php-format
46 | msgid "Invalid characters in class name \"%1$s\""
47 | msgstr "Недопустими символи в името на класа \"%1$s\""
48 |
49 | #, php-format
50 | msgid "No allowed image types configured for \"%1$s\""
51 | msgstr "Неразрешени типове изображения са конфигурирани за \\\"%1$s\\\""
52 |
53 | #, php-format
54 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
55 | msgstr "Не е намерен тип на елемент за \"%1$s/%2$s\" в \"%3$s\""
56 |
57 | #, php-format
58 | msgid "Unable to create directory \"%1$s\""
59 | msgstr "Не е възможно да се създаде директория \"%1$s\""
60 |
61 | #, php-format
62 | msgid "Unable to create file in \"%1$s\""
63 | msgstr "Не е възможно да се създаде файл в \"%1$s\""
64 |
--------------------------------------------------------------------------------
/src/GraphQL/Type/Definition/Json.php:
--------------------------------------------------------------------------------
1 | value, true, 512, JSON_THROW_ON_ERROR );
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Admin/Graphql/ProviderTrait.php:
--------------------------------------------------------------------------------
1 | Type::listOf( $this->types()->configOutputType( $domain ) ),
41 | 'args' => [
42 | ['name' => 'provider', 'type' => Type::string(), 'description' => 'Provider name with decorators separated by comma'],
43 | ['name' => 'type', 'type' => Type::string(), 'description' => 'Provider type'],
44 | ],
45 | 'resolve' => $this->getConfig( $domain ),
46 | ];
47 |
48 | return $list;
49 | }
50 |
51 |
52 | /**
53 | * Returns a closure for returning the provider configuration
54 | *
55 | * @param string $domain Domain path of the manager
56 | * @return \Closure Anonymous method returning one item
57 | */
58 | protected function getConfig( string $domain ) : \Closure
59 | {
60 | return function( $root, $args, $context ) use ( $domain ) {
61 |
62 | $this->access( $domain, 'get' );
63 | $manager = \Aimeos\MShop::create( $this->context(), $domain );
64 | $item = $manager->create()->setProvider( $args['provider'] );
65 |
66 | return $manager->getProvider( $item, $args['type'] )->getConfigBE();
67 | };
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/i18n/controller/es.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | # Lara Merlotto , 2020
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: aimeos-core\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2022-12-07 11:38+0100\n"
12 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
13 | "Last-Translator: Lara Merlotto , 2020\n"
14 | "Language-Team: Spanish (http://app.transifex.com/aimeos/aimeos-core/language/es/)\n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Language: es\n"
19 | "Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
20 |
21 | #, php-format
22 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
23 | msgstr "La clase \"%1$s\" no implementa la interfaz \"%2$s\""
24 |
25 | #, php-format
26 | msgid "Class \"%1$s\" not available"
27 | msgstr "Clase \"%1$s\" no disponible"
28 |
29 | #, php-format
30 | msgid "Class \"%1$s\" not found"
31 | msgstr "Clase \"%1$s\" no encontrada"
32 |
33 | #, php-format
34 | msgid "Downloading file \"%1$s\" failed"
35 | msgstr "Error en el descargo del archivo %1$s"
36 |
37 | #, php-format
38 | msgid "File \"%1$s\" not found"
39 | msgstr "Archivo \"%1$s\" no encontrado"
40 |
41 | #, php-format
42 | msgid "File type \"%1$s\" is not allowed"
43 | msgstr ""
44 |
45 | #, php-format
46 | msgid "Invalid characters in class name \"%1$s\""
47 | msgstr "Caracteres no válidos en el nombre de clase \"%1$s\""
48 |
49 | #, php-format
50 | msgid "No allowed image types configured for \"%1$s\""
51 | msgstr "No se ha configurado ningún tipo de imagen permitido para \"%1$s\""
52 |
53 | #, php-format
54 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
55 | msgstr "No se ha encontrado el tipo de elemento para \"%1$s/%2$s\" en \"%3$s\""
56 |
57 | #, php-format
58 | msgid "Unable to create directory \"%1$s\""
59 | msgstr "No se ha podido crear el directorio \"%1$s\""
60 |
61 | #, php-format
62 | msgid "Unable to create file in \"%1$s\""
63 | msgstr "No se ha podido crear el archivo en \"%1$s\""
64 |
--------------------------------------------------------------------------------
/i18n/controller/de.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | # Aimeos, 2015-2017,2022
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: aimeos-core\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2022-12-07 11:38+0100\n"
12 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
13 | "Last-Translator: Aimeos, 2015-2017,2022\n"
14 | "Language-Team: German (http://app.transifex.com/aimeos/aimeos-core/language/de/)\n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Language: de\n"
19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20 |
21 | #, php-format
22 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
23 | msgstr "Klasse \"%1$s\" implementiert nicht Interface \"%2$s\""
24 |
25 | #, php-format
26 | msgid "Class \"%1$s\" not available"
27 | msgstr "Die Klasse \"%1$s\" wurde nicht gefunden"
28 |
29 | #, php-format
30 | msgid "Class \"%1$s\" not found"
31 | msgstr "Die Klasse \"%1$s\" ist unbekannt"
32 |
33 | #, php-format
34 | msgid "Downloading file \"%1$s\" failed"
35 | msgstr "Herunterladen der Datei \"%1$s\" ist fehl geschlagen"
36 |
37 | #, php-format
38 | msgid "File \"%1$s\" not found"
39 | msgstr "Die Datei \"%1$s\" ist unbekannt"
40 |
41 | #, php-format
42 | msgid "File type \"%1$s\" is not allowed"
43 | msgstr "Der Dateityp \"%1$s\" ist nicht erlaubt"
44 |
45 | #, php-format
46 | msgid "Invalid characters in class name \"%1$s\""
47 | msgstr "Klassenname \"%1$s\" enthält unerlaubte Zeichen"
48 |
49 | #, php-format
50 | msgid "No allowed image types configured for \"%1$s\""
51 | msgstr "Es sind keine zulässigen Bildtypen für \"%1$s\" konfiguriert"
52 |
53 | #, php-format
54 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
55 | msgstr "Für den Eintrag \"%1$s/%2$s\" in \"%3$s\" wurde kein Typ gefunden"
56 |
57 | #, php-format
58 | msgid "Unable to create directory \"%1$s\""
59 | msgstr "Das Verzeichnis \"%1$s\" konnte nicht erstellt werden"
60 |
61 | #, php-format
62 | msgid "Unable to create file in \"%1$s\""
63 | msgstr "Die Datei \"%1$s\" konnte nicht erstellt werden"
64 |
--------------------------------------------------------------------------------
/i18n/controller/hr.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | # Miro Sertić , 2018
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: aimeos-core\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2022-12-07 11:38+0100\n"
12 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
13 | "Last-Translator: Miro Sertić , 2018\n"
14 | "Language-Team: Croatian (http://app.transifex.com/aimeos/aimeos-core/language/hr/)\n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Language: hr\n"
19 | "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
20 |
21 | #, php-format
22 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
23 | msgstr "Klasa \"%1$s\" ne implementira sučelje \"%2$s\""
24 |
25 | #, php-format
26 | msgid "Class \"%1$s\" not available"
27 | msgstr "Klasa \"%1$s\" nije dostupna"
28 |
29 | #, php-format
30 | msgid "Class \"%1$s\" not found"
31 | msgstr "Klasa \"%1$s\" nije pronađjena"
32 |
33 | #, php-format
34 | msgid "Downloading file \"%1$s\" failed"
35 | msgstr "Preuzimanje datoteke \"%1$s\" nije uspjelo"
36 |
37 | #, php-format
38 | msgid "File \"%1$s\" not found"
39 | msgstr "Dokument \"%1$s\" nije pronađjen"
40 |
41 | #, php-format
42 | msgid "File type \"%1$s\" is not allowed"
43 | msgstr ""
44 |
45 | #, php-format
46 | msgid "Invalid characters in class name \"%1$s\""
47 | msgstr "Nedozvoljen simbol u imenu klase \"%1$s\""
48 |
49 | #, php-format
50 | msgid "No allowed image types configured for \"%1$s\""
51 | msgstr "Dopuštene vrste slika nisu konfigurirane \"%1$s\""
52 |
53 | #, php-format
54 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
55 | msgstr "Nijedna stavka vrste \"%1$s/%2$s\" nije pronađena u \"%3$s\""
56 |
57 | #, php-format
58 | msgid "Unable to create directory \"%1$s\""
59 | msgstr "Nemože se stvoriti slijedeći direktorij \"%1$s\""
60 |
61 | #, php-format
62 | msgid "Unable to create file in \"%1$s\""
63 | msgstr "Nije moguće stvoriti datoteku \"%1$s\""
64 |
--------------------------------------------------------------------------------
/i18n/controller/hu.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | # nzed , 2020,2022
7 | # nzed , 2020
8 | msgid ""
9 | msgstr ""
10 | "Project-Id-Version: aimeos-core\n"
11 | "Report-Msgid-Bugs-To: \n"
12 | "POT-Creation-Date: 2022-12-07 11:38+0100\n"
13 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
14 | "Last-Translator: nzed , 2020,2022\n"
15 | "Language-Team: Hungarian (http://app.transifex.com/aimeos/aimeos-core/language/hu/)\n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | "Language: hu\n"
20 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
21 |
22 | #, php-format
23 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
24 | msgstr "A(z) \"%1$s\" osztály nem valósítja meg a(z) \"%2$s\" felület osztályt"
25 |
26 | #, php-format
27 | msgid "Class \"%1$s\" not available"
28 | msgstr "A/Az \"%1$s\" osztály nem elérhető."
29 |
30 | #, php-format
31 | msgid "Class \"%1$s\" not found"
32 | msgstr "A/Az \"%1$s\" osztály nem található."
33 |
34 | #, php-format
35 | msgid "Downloading file \"%1$s\" failed"
36 | msgstr "\"%1$s\" állomány letöltése nem sikerült"
37 |
38 | #, php-format
39 | msgid "File \"%1$s\" not found"
40 | msgstr "\"%1$s\" állomány nem található"
41 |
42 | #, php-format
43 | msgid "File type \"%1$s\" is not allowed"
44 | msgstr "\"%1$s\" állomány nem engedélyezett"
45 |
46 | #, php-format
47 | msgid "Invalid characters in class name \"%1$s\""
48 | msgstr "Érvénytelen karakter a/az \"%1$s\" osztály nevében"
49 |
50 | #, php-format
51 | msgid "No allowed image types configured for \"%1$s\""
52 | msgstr "Nincs beállítva képformátum ehhez: \"%1$s\""
53 |
54 | #, php-format
55 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
56 | msgstr "\"%1$s/%2$s\" számára a \"%3$s\"-ban/ben nincs fajtatétel"
57 |
58 | #, php-format
59 | msgid "Unable to create directory \"%1$s\""
60 | msgstr "Nem sikerült létrehozni a könyvtárat \"%1$s\""
61 |
62 | #, php-format
63 | msgid "Unable to create file in \"%1$s\""
64 | msgstr "Nem sikerült létrehozni a fájlt \"%1$s\""
65 |
--------------------------------------------------------------------------------
/i18n/controller/fr.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | # a270031086f2a0d3514bc0cb507b48f6, 2019
7 | # a270031086f2a0d3514bc0cb507b48f6, 2019
8 | msgid ""
9 | msgstr ""
10 | "Project-Id-Version: aimeos-core\n"
11 | "Report-Msgid-Bugs-To: \n"
12 | "POT-Creation-Date: 2022-12-07 11:38+0100\n"
13 | "PO-Revision-Date: 2015-09-29 12:30+0000\n"
14 | "Last-Translator: a270031086f2a0d3514bc0cb507b48f6, 2019\n"
15 | "Language-Team: French (http://app.transifex.com/aimeos/aimeos-core/language/fr/)\n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | "Language: fr\n"
20 | "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
21 |
22 | #, php-format
23 | msgid "Class \"%1$s\" does not implement interface \"%2$s\""
24 | msgstr "La classe \"%1$s\" n'implémente pas l'interface \"%2$s\""
25 |
26 | #, php-format
27 | msgid "Class \"%1$s\" not available"
28 | msgstr "Classe \"%1$s\" indisponible"
29 |
30 | #, php-format
31 | msgid "Class \"%1$s\" not found"
32 | msgstr "Classe \"%1$s\" non trouvée"
33 |
34 | #, php-format
35 | msgid "Downloading file \"%1$s\" failed"
36 | msgstr "Le téléchargement du fichier \"%1$s\" a échoué"
37 |
38 | #, php-format
39 | msgid "File \"%1$s\" not found"
40 | msgstr "Fichier \"%1$s\" non trouvé"
41 |
42 | #, php-format
43 | msgid "File type \"%1$s\" is not allowed"
44 | msgstr ""
45 |
46 | #, php-format
47 | msgid "Invalid characters in class name \"%1$s\""
48 | msgstr "Caractères invalides dans le nom de la catégorie \"%1$s\""
49 |
50 | #, php-format
51 | msgid "No allowed image types configured for \"%1$s\""
52 | msgstr "Aucun type d'image autorisé configuré pour \"%1$s\""
53 |
54 | #, php-format
55 | msgid "No type item for \"%1$s/%2$s\" in \"%3$s\" found"
56 | msgstr "Aucun type d'item trouvé pour \"%1$s/%2$s\" dans \"%3$s\""
57 |
58 | #, php-format
59 | msgid "Unable to create directory \"%1$s\""
60 | msgstr "Impossible de créer le répertoire \"%1$s\""
61 |
62 | #, php-format
63 | msgid "Unable to create file in \"%1$s\""
64 | msgstr "Impossible de créer le fichier dans \"%1$s\""
65 |
--------------------------------------------------------------------------------
/src/Admin/Graphql/Product/Standard.php:
--------------------------------------------------------------------------------
1 | $this->types()->outputType( $domain ),
36 | 'args' => [
37 | ['name' => 'code', 'type' => Type::string(), 'description' => 'Unique code'],
38 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
39 | ],
40 | 'resolve' => $this->findItem( $domain ),
41 | ];
42 |
43 | return $list;
44 | }
45 |
46 |
47 | /**
48 | * Updates the item
49 | *
50 | * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager object for the passed item
51 | * @param \Aimeos\MShop\Common\Item\AdddressRef\Iface $item Item to update
52 | * @param array $entry Associative list of key/value pairs of the item data
53 | * @return \Aimeos\MShop\Common\Item\Iface Updated item
54 | */
55 | protected function updateItem( \Aimeos\MShop\Common\Manager\Iface $manager,
56 | \Aimeos\MShop\Common\Item\Iface $item, array $entry ) : \Aimeos\MShop\Common\Item\Iface
57 | {
58 | $item = parent::updateItem( $manager, $item, $entry );
59 |
60 | if( isset( $entry['product.stock'] ) )
61 | {
62 | $stockItems = $item->getStockItems()->col( null, 'stock.type' );
63 |
64 | foreach( $entry['product.stock'] as $subentry )
65 | {
66 | $stockItem = $stockItems->get( $subentry['stock.type'] ?? null ) ?: $manager->createStockItem();
67 | $item->addStockItem( $stockItem->fromArray( $subentry ) );
68 | }
69 |
70 | $item->deleteStockItems( $stockItems );
71 | }
72 |
73 | return $item;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/config/admin.php:
--------------------------------------------------------------------------------
1 | [
5 | 'domains' => [
6 | 'attribute' => 'attribute',
7 | 'attribute/type' => 'attribute/type',
8 | 'attribute/lists/type' => 'attribute/lists/type',
9 | 'attribute/property/type' => 'attribute/property/type',
10 | 'catalog' => 'catalog',
11 | 'catalog/lists/type' => 'catalog/lists/type',
12 | 'coupon' => 'coupon',
13 | 'coupon/code' => 'coupon/code',
14 | 'customer' => 'customer',
15 | 'customer/lists/type' => 'customer/lists/type',
16 | 'customer/property/type' => 'customer/property/type',
17 | 'group' => 'group',
18 | 'index' => 'index',
19 | 'locale' => 'locale',
20 | 'locale/currency' => 'locale/currency',
21 | 'locale/language' => 'locale/language',
22 | 'locale/site' => 'locale/site',
23 | 'media' => 'media',
24 | 'media/type' => 'media/type',
25 | 'media/lists/type' => 'media/lists/type',
26 | 'media/property/type' => 'media/property/type',
27 | 'order' => 'order',
28 | 'order/status' => 'order/status',
29 | 'plugin' => 'plugin',
30 | 'plugin/type' => 'plugin/type',
31 | 'price' => 'price',
32 | 'price/type' => 'price/type',
33 | 'price/lists/type' => 'price/lists/type',
34 | 'price/property/type' => 'price/property/type',
35 | 'product' => 'product',
36 | 'product/type' => 'product/type',
37 | 'product/lists/type' => 'product/lists/type',
38 | 'product/property/type' => 'product/property/type',
39 | 'review' => 'review',
40 | 'rule' => 'rule',
41 | 'rule/type' => 'rule/type',
42 | 'service' => 'service',
43 | 'service/type' => 'service/type',
44 | 'service/lists/type' => 'service/lists/type',
45 | 'stock' => 'stock',
46 | 'stock/type' => 'stock/type',
47 | 'subscription' => 'subscription',
48 | 'supplier' => 'supplier',
49 | 'supplier/lists/type' => 'supplier/lists/type',
50 | 'tag' => 'tag',
51 | 'tag/type' => 'tag/type',
52 | 'text' => 'text',
53 | 'text/type' => 'text/type',
54 | 'text/lists/type' => 'text/lists/type',
55 | 'type' => 'type',
56 | ],
57 | 'lists-domains' => [
58 | 'attribute' => 'attribute',
59 | 'catalog' => 'catalog',
60 | 'customer' => 'customer',
61 | 'group' => 'group',
62 | 'media' => 'media',
63 | 'price' => 'price',
64 | 'product' => 'product',
65 | 'service' => 'service',
66 | 'supplier' => 'supplier',
67 | 'tag' => 'tag',
68 | 'text' => 'text',
69 | ]
70 | ],
71 | ];
72 |
--------------------------------------------------------------------------------
/tests/Admin/StandardTest.php:
--------------------------------------------------------------------------------
1 | context = \TestHelper::context();
21 | $this->context->config()->set( 'admin/graphql/debug', true );
22 | $this->context->setView( \TestHelper::view( 'unittest', $this->context->config() ) );
23 | }
24 |
25 |
26 | public function testFindAttribute()
27 | {
28 | $body = '{"query":"query {\n findAttribute(code: \"xs\",domain: \"product\",type: \"size\") {\n id\n code\n }\n}\n","variables":{},"operationName":null}';
29 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
30 |
31 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
32 |
33 | $this->assertStringContainsString( '"code":"xs"', (string) $response->getBody() );
34 | }
35 |
36 |
37 | public function testFindCustomer()
38 | {
39 | $body = '{"query":"query {\n findCustomer(code: \"test@example.com\") {\n id\n code\n }\n}\n","variables":{},"operationName":null}';
40 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
41 |
42 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
43 |
44 | $this->assertStringContainsString( '"code":"test@example.com"', (string) $response->getBody() );
45 | }
46 |
47 |
48 | public function testFindService()
49 | {
50 | $body = '{"query":"query {\n findService(code: \"unitdeliverycode\") {\n id\n code\n }\n}\n","variables":{},"operationName":null}';
51 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
52 |
53 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
54 |
55 | $this->assertStringContainsString( '"code":"unitdeliverycode"', (string) $response->getBody() );
56 | }
57 |
58 |
59 | public function testFindSupplier()
60 | {
61 | $body = '{"query":"query {\n findSupplier(code: \"unitSupplier001\") {\n id\n code\n }\n}\n","variables":{},"operationName":null}';
62 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
63 |
64 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
65 |
66 | $this->assertStringContainsString( '"code":"unitSupplier001"', (string) $response->getBody() );
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/GraphQL/Type/Definition/Upload.php:
--------------------------------------------------------------------------------
1 | $this->types()->outputType( $domain ),
36 | 'args' => [
37 | ['name' => 'code', 'type' => Type::string(), 'description' => 'Unique code'],
38 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
39 | ],
40 | 'resolve' => $this->findItem( $domain ),
41 | ];
42 |
43 | $list['getServiceConfig'] = [
44 | 'type' => Type::listOf( $this->types()->configOutputType( $domain ) ),
45 | 'args' => [
46 | ['name' => 'provider', 'type' => Type::string(), 'description' => 'Provider name with decorators separated by comma'],
47 | ['name' => 'type', 'type' => Type::string(), 'description' => 'Provider type ("delivery" or "payment")'],
48 | ],
49 | 'resolve' => $this->getConfig( $domain ),
50 | ];
51 |
52 | return $list;
53 | }
54 |
55 |
56 | /**
57 | * Returns a closure for returning the provider configuration
58 | *
59 | * @param string $domain Domain path of the manager
60 | * @return \Closure Anonymous method returning one item
61 | */
62 | protected function getConfig( string $domain ) : \Closure
63 | {
64 | return function( $root, $args, $context ) use ( $domain ) {
65 |
66 | $context = $this->context();
67 | $groups = $context->config()->get( 'admin/graphql/resource/' . $domain . '/get', [] );
68 |
69 | if( $context->view()->access( $groups ) !== true ) {
70 | throw new \Aimeos\Admin\Graphql\Exception( 'Forbidden', 403 );
71 | }
72 |
73 | $manager = \Aimeos\MShop::create( $context, $domain );
74 | $item = $manager->create()->setProvider( $args['provider'] );
75 |
76 | return $manager->getProvider( $item, $args['type'] )->getConfigBE();
77 | };
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Admin/Graphql/Index/Standard.php:
--------------------------------------------------------------------------------
1 | [
46 | 'type' => $this->types()->aggregateOutputType( $domain ),
47 | 'args' => [
48 | ['name' => 'key', 'type' => Type::listOf( Type::string() ), 'description' => 'Aggregation key to group results by, e.g. ["product.status", "index.catalog.id"]'],
49 | ['name' => 'value', 'type' => Type::string(), 'defaultValue' => null, 'description' => 'Aggregate values from that column, e.g "index.catalog.id" (optional, only if type is passed)'],
50 | ['name' => 'type', 'type' => Type::string(), 'defaultValue' => null, 'description' => 'Type of aggregation like "sum" or "avg" (default: null for count)'],
51 | ['name' => 'filter', 'type' => Type::string(), 'defaultValue' => '{}', 'description' => 'Filter conditions'],
52 | ['name' => 'sort', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Sort keys'],
53 | ['name' => 'limit', 'type' => Type::int(), 'defaultValue' => 10000, 'description' => 'Slice size'],
54 | ],
55 | 'resolve' => $this->aggregateItems( $domain ),
56 | ],
57 | 'searchIndex' => [
58 | 'type' => $this->types()->searchOutputType( 'product' ),
59 | 'args' => [
60 | ['name' => 'filter', 'type' => Type::string(), 'defaultValue' => '{}', 'description' => 'Filter conditions'],
61 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
62 | ['name' => 'sort', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Sort keys'],
63 | ['name' => 'offset', 'type' => Type::int(), 'defaultValue' => 0, 'description' => 'Slice offset'],
64 | ['name' => 'limit', 'type' => Type::int(), 'defaultValue' => 100, 'description' => 'Slice size'],
65 | ],
66 | 'resolve' => $this->searchItems( $domain ),
67 | ]
68 | ];
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Admin/Graphql/Standard.php:
--------------------------------------------------------------------------------
1 | [
34 | 'type' => Type::string(),
35 | 'args' => [
36 | ['name' => 'id', 'type' => Type::string(), 'description' => 'Item ID'],
37 | ],
38 | 'resolve' => $this->deleteItems( $domain ),
39 | ],
40 | 'delete' . str_replace( '/', '', ucwords( $domain, '/' ) ) . 's' => [
41 | 'type' => Type::listOf( Type::string() ),
42 | 'args' => [
43 | ['name' => 'id', 'type' => Type::listOf( Type::string() ), 'description' => 'List of item IDs'],
44 | ],
45 | 'resolve' => $this->deleteItems( $domain ),
46 | ],
47 | 'save' . str_replace( '/', '', ucwords( $domain, '/' ) ) => [
48 | 'type' => $this->types()->outputType( $domain ),
49 | 'args' => [
50 | ['name' => 'input', 'type' => $this->types()->inputType( $domain ), 'description' => 'Item object'],
51 | ],
52 | 'resolve' => $this->saveItem( $domain ),
53 | ],
54 | 'save' . str_replace( '/', '', ucwords( $domain, '/' ) ) . 's' => [
55 | 'type' => Type::listOf( $this->types()->outputType( $domain ) ),
56 | 'args' => [
57 | ['name' => 'input', 'type' => Type::listOf( $this->types()->inputType( $domain ) ), 'description' => 'Item objects'],
58 | ],
59 | 'resolve' => $this->saveItems( $domain ),
60 | ]
61 | ];
62 | }
63 |
64 |
65 | /**
66 | * Returns GraphQL schema definition for the available queries
67 | *
68 | * @param string $domain Domain name of the responsible manager
69 | * @return array GraphQL query schema definition
70 | */
71 | public function query( string $domain ) : array
72 | {
73 | $list = [
74 | 'get' . str_replace( '/', '', ucwords( $domain, '/' ) ) => [
75 | 'type' => $this->types()->outputType( $domain ),
76 | 'args' => [
77 | ['name' => 'id', 'type' => Type::string(), 'description' => 'Unique ID'],
78 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
79 | ],
80 | 'resolve' => $this->getItem( $domain ),
81 | ],
82 | 'search' . str_replace( '/', '', ucwords( $domain, '/' ) ) . 's' => [
83 | 'type' => $this->types()->searchOutputType( $domain ),
84 | 'args' => [
85 | ['name' => 'filter', 'type' => Type::string(), 'defaultValue' => '{}', 'description' => 'Filter conditions'],
86 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
87 | ['name' => 'sort', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Sort keys'],
88 | ['name' => 'offset', 'type' => Type::int(), 'defaultValue' => 0, 'description' => 'Slice offset'],
89 | ['name' => 'limit', 'type' => Type::int(), 'defaultValue' => 100, 'description' => 'Slice size'],
90 | ],
91 | 'resolve' => $this->searchItems( $domain ),
92 | ]
93 | ];
94 |
95 | if( !substr_compare( $domain, '/type', -5 ) )
96 | {
97 | $list['find' . str_replace( '/', '', ucwords( $domain, '/' ) )] = [
98 | 'type' => $this->types()->outputType( $domain ),
99 | 'args' => [
100 | ['name' => 'code', 'type' => Type::string(), 'description' => 'Unique code'],
101 | ['name' => 'domain', 'type' => Type::string(), 'description' => 'Domain of the type'],
102 | ],
103 | 'resolve' => $this->findTypeItem( $domain ),
104 | ];
105 | }
106 |
107 | return $list;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/Admin/Graphql/Media/Standard.php:
--------------------------------------------------------------------------------
1 | $this->types()->outputType( $domain ),
41 | 'args' => [
42 | ['name' => 'input', 'type' => $this->mediaInputType( $domain ), 'description' => 'Item object'],
43 | ],
44 | 'resolve' => $this->saveItem( $domain ),
45 | ];
46 | $list['saveMedias'] = [
47 | 'type' => Type::listOf( $this->types()->outputType( $domain ) ),
48 | 'args' => [
49 | ['name' => 'input', 'type' => Type::listOf( $this->mediaInputType( $domain ) ), 'description' => 'Item objects'],
50 | ],
51 | 'resolve' => $this->saveItems( $domain ),
52 | ];
53 |
54 | return $list;
55 | }
56 |
57 |
58 | /**
59 | * Defines the GraphQL media input type
60 | *
61 | * @param string $path Path of the domain manager
62 | * @return \GraphQL\Type\Definition\InputObjectType Input type definition
63 | */
64 | public function mediaInputType( string $path ) : InputObjectType
65 | {
66 | $name = 'mediaInput';
67 |
68 | if( isset( $this->type ) ) {
69 | return $this->type;
70 | }
71 |
72 | return $this->type = new InputObjectType( [
73 | 'name' => $name,
74 | 'fields' => function() use ( $path ) {
75 |
76 | $manager = \Aimeos\MShop::create( $this->context(), $path );
77 | $list = $this->types()->fields( $manager->getSearchAttributes( false ) );
78 | $item = $manager->create();
79 |
80 | if( $item instanceof \Aimeos\MShop\Common\Item\ListsRef\Iface ) {
81 | $list['lists'] = $this->types()->listsInputType( $path . '/lists' );
82 | }
83 |
84 | if( $item instanceof \Aimeos\MShop\Common\Item\PropertyRef\Iface ) {
85 | $list['property'] = Type::listOf( $this->types()->inputType( $path . '/property' ) );
86 | }
87 |
88 | $list['file'] = ['type' => Upload::type(), 'description' => 'File upload'];
89 | $list['filepreview'] = ['type' => Upload::type(), 'description' => 'Preview file upload'];
90 |
91 | return $list;
92 | },
93 | 'parseValue' => function( array $values ) use ( $path ) {
94 | return $this->types()->prefix( $path, $values );
95 | }
96 | ] );
97 | }
98 |
99 |
100 | /**
101 | * Updates the item
102 | *
103 | * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager object for the passed item
104 | * @param \Aimeos\MShop\Common\Item\AdddressRef\Iface $item Item to update
105 | * @param array $entry Associative list of key/value pairs of the item data
106 | * @return \Aimeos\MShop\Common\Item\Iface Updated item
107 | */
108 | protected function updateItem( \Aimeos\MShop\Common\Manager\Iface $manager,
109 | \Aimeos\MShop\Common\Item\Iface $item, array $entry ) : \Aimeos\MShop\Common\Item\Iface
110 | {
111 | $item = $item->fromArray( $entry, true );
112 |
113 | if( isset( $entry['media.file'] ) ) {
114 | $item = $manager->upload( $item, $entry['media.file'], $entry['media.filepreview'] ?? null );
115 | }
116 |
117 | if( isset( $entry['lists'] ) && $item instanceof \Aimeos\MShop\Common\Item\ListsRef\Iface ) {
118 | $item = $this->updateLists( $manager, $item, $entry['lists'] );
119 | }
120 |
121 | if( isset( $entry['property'] ) && $item instanceof \Aimeos\MShop\Common\Item\PropertyRef\Iface ) {
122 | $item = $this->updateProperties( $manager, $item, $entry['property'] );
123 | }
124 |
125 | return $item;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Admin/Graphql/Customer/Standard.php:
--------------------------------------------------------------------------------
1 | $this->types()->aggregateOutputType( $domain ),
42 | 'args' => [
43 | ['name' => 'key', 'type' => Type::listOf( Type::string() ), 'description' => 'Aggregation key to group results by, e.g. "customer.status"'],
44 | ['name' => 'value', 'type' => Type::string(), 'defaultValue' => null, 'description' => 'Aggregate values from that column, e.g "customer.status" (optional, only if type is passed)'],
45 | ['name' => 'type', 'type' => Type::string(), 'defaultValue' => null, 'description' => 'Type of aggregation like "sum" or "avg" (default: null for count)'],
46 | ['name' => 'filter', 'type' => Type::string(), 'defaultValue' => '{}', 'description' => 'Filter conditions'],
47 | ['name' => 'sort', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Sort keys'],
48 | ['name' => 'limit', 'type' => Type::int(), 'defaultValue' => 10000, 'description' => 'Slice size'],
49 | ],
50 | 'resolve' => $this->aggregateItems( $domain ),
51 | ];
52 |
53 | $list['findCustomer'] = [
54 | 'type' => $this->types()->outputType( $domain ),
55 | 'args' => [
56 | ['name' => 'code', 'type' => Type::string(), 'description' => 'Unique code'],
57 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
58 | ],
59 | 'resolve' => $this->findItem( $domain ),
60 | ];
61 |
62 | return $list;
63 | }
64 |
65 |
66 | /**
67 | * Updates the item
68 | *
69 | * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager object for the passed item
70 | * @param \Aimeos\MShop\Common\Item\AdddressRef\Iface $item Item to update
71 | * @param array $entry Associative list of key/value pairs of the item data
72 | * @return \Aimeos\MShop\Common\Item\Iface Updated item
73 | */
74 | protected function updateItem( \Aimeos\MShop\Common\Manager\Iface $manager,
75 | \Aimeos\MShop\Common\Item\Iface $item, array $entry ) : \Aimeos\MShop\Common\Item\Iface
76 | {
77 | $view = $this->context()->view();
78 | $siteId = (string) $this->context()->user()?->getSiteId();
79 |
80 | if( $view->access( ['super'] ) || strlen( $siteId ) > 0 && !strncmp( $item->getSiteId(), $siteId, strlen( $siteId ) ) )
81 | {
82 | $item = $item->fromArray( $entry );
83 |
84 | if( $view->access( ['super', 'admin'] ) ) {
85 | $item->setGroups( array_unique( $entry['groups'] ?? [] ) );
86 | }
87 |
88 | if( $view->access( ['super', 'admin'] ) || $item->getId() === $this->context()->user() )
89 | {
90 | !isset( $entry['customer.password'] ) ?: $item->setPassword( $entry['customer.password'] );
91 | !isset( $entry['customer.code'] ) ?: $item->setCode( $entry['customer.code'] );
92 | }
93 |
94 | if( isset( $entry['address'] ) && $item instanceof \Aimeos\MShop\Common\Item\AddressRef\Iface ) {
95 | $item = $this->updateAddresses( $manager, $item, $entry['address'] );
96 | }
97 |
98 | if( isset( $entry['lists'] ) && $item instanceof \Aimeos\MShop\Common\Item\ListsRef\Iface ) {
99 | $item = $this->updateLists( $manager, $item, $entry['lists'] );
100 | }
101 |
102 | if( isset( $entry['property'] ) && $item instanceof \Aimeos\MShop\Common\Item\PropertyRef\Iface ) {
103 | $item = $this->updateProperties( $manager, $item, $entry['property'] );
104 | }
105 | }
106 |
107 | return $item;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/tests/TestHelper.php:
--------------------------------------------------------------------------------
1 | config();
45 | }
46 |
47 | $view = new \Aimeos\Base\View\Standard();
48 |
49 | $helper = new \Aimeos\Base\View\Helper\Access\All( $view );
50 | $view->addHelper( 'access', $helper );
51 |
52 | $trans = new \Aimeos\Base\Translation\None( 'de_DE' );
53 | $helper = new \Aimeos\Base\View\Helper\Translate\Standard( $view, $trans );
54 | $view->addHelper( 'translate', $helper );
55 |
56 | $helper = new \Aimeos\Base\View\Helper\Url\Standard( $view, 'http://baseurl' );
57 | $view->addHelper( 'url', $helper );
58 |
59 | $helper = new \Aimeos\Base\View\Helper\Session\Standard( $view, self::context( $site )->session() );
60 | $view->addHelper( 'session', $helper );
61 |
62 | $helper = new \Aimeos\Base\View\Helper\Number\Standard( $view, '.', '' );
63 | $view->addHelper( 'number', $helper );
64 |
65 | $helper = new \Aimeos\Base\View\Helper\Date\Standard( $view, 'Y-m-d' );
66 | $view->addHelper( 'date', $helper );
67 |
68 | $paths = ['version', 'client', 'common', 'resource/fs/baseurl', 'resource/fs-media/baseurl', 'resource/fs-theme/baseurl'];
69 | $config = new \Aimeos\Base\Config\Decorator\Protect( $config, $paths );
70 | $helper = new \Aimeos\Base\View\Helper\Config\Standard( $view, $config );
71 | $view->addHelper( 'config', $helper );
72 |
73 | $helper = new \Aimeos\Base\View\Helper\Csrf\Standard( $view, '_csrf_token', '_csrf_value' );
74 | $view->addHelper( 'csrf', $helper );
75 |
76 | $psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory();
77 | $helper = new \Aimeos\Base\View\Helper\Request\Standard( $view, $psr17Factory->createServerRequest( 'POST', 'https://aimeos.org' ) );
78 | $view->addHelper( 'request', $helper );
79 |
80 | $helper = new \Aimeos\Base\View\Helper\Response\Standard( $view, $psr17Factory->createResponse() );
81 | $view->addHelper( 'response', $helper );
82 |
83 | return $view;
84 | }
85 |
86 |
87 | /**
88 | * @param string $site
89 | */
90 | private static function createContext( $site )
91 | {
92 | $ctx = new \Aimeos\MShop\Context();
93 | $aimeos = self::getAimeos();
94 |
95 |
96 | $paths = $aimeos->getConfigPaths();
97 | $paths[] = __DIR__ . DIRECTORY_SEPARATOR . 'config';
98 | $file = __DIR__ . DIRECTORY_SEPARATOR . 'confdoc.ser';
99 | $local = array( 'resource' => array( 'fs' => array( 'adapter' => 'Standard', 'basedir' => __DIR__ . '/tmp' ) ) );
100 |
101 | $conf = new \Aimeos\Base\Config\PHPArray( [], $paths );
102 | $conf = new \Aimeos\Base\Config\Decorator\Memory( $conf );
103 | $conf = new \Aimeos\Base\Config\Decorator\Documentor( $conf, $file );
104 | $ctx->setConfig( $conf->apply( $local ) );
105 |
106 |
107 | $logger = new \Aimeos\Base\Logger\File( $site . '.log', \Aimeos\Base\Logger\Iface::DEBUG );
108 | $ctx->setLogger( $logger );
109 |
110 |
111 | $dbm = new \Aimeos\Base\DB\Manager\Standard( $conf->get( 'resource', [] ), 'PDO' );
112 | $ctx->setDatabaseManager( $dbm );
113 |
114 |
115 | $fs = new \Aimeos\Base\Filesystem\Manager\Standard( $conf->get( 'resource', [] ) );
116 | $ctx->setFilesystemManager( $fs );
117 |
118 |
119 | $mq = new \Aimeos\Base\MQueue\Manager\Standard( $conf->get( 'resource', [] ) );
120 | $ctx->setMessageQueueManager( $mq );
121 |
122 |
123 | $cache = new \Aimeos\Base\Cache\None();
124 | $ctx->setCache( $cache );
125 |
126 |
127 | $i18n = new \Aimeos\Base\Translation\None( 'de' );
128 | $ctx->setI18n( array( 'de' => $i18n ) );
129 |
130 |
131 | $passwd = new \Aimeos\Base\Password\Standard();
132 | $ctx->setPassword( $passwd );
133 |
134 |
135 | $session = new \Aimeos\Base\Session\None();
136 | $ctx->setSession( $session );
137 |
138 |
139 | $localeManager = \Aimeos\MShop::create( $ctx, 'locale' );
140 | $locale = $localeManager->bootstrap( $site, '', '', false );
141 | $ctx->setLocale( $locale );
142 |
143 |
144 | return $ctx->setEditor( 'ai-admin-graphql' );
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/tests/Admin/Order/StandardTest.php:
--------------------------------------------------------------------------------
1 | context = \TestHelper::context();
21 | $this->context->config()->set( 'admin/graphql/debug', true );
22 | $this->context->setView( \TestHelper::view( 'unittest', $this->context->config() ) );
23 | }
24 |
25 |
26 | public function testAggregateOrders()
27 | {
28 | $body = '{"query":"query {\n aggregateOrders(key: [\"order.price\"]) {\n aggregates\n }\n}\n","variables":{},"operationName":null}';
29 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
30 |
31 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
32 |
33 | $this->assertStringContainsString( '"aggregates":"{\"13.50\":1,\"2400.00\":1,\"672.00\":1,\"53.50\":1}"', (string) $response->getBody() );
34 | }
35 |
36 |
37 | public function testAggregateOrdersNested()
38 | {
39 | $body = '{"query":"query {\n aggregateOrders(key: [\"order.statuspayment\", \"order.price\"]) {\n aggregates\n }\n}\n","variables":{},"operationName":null}';
40 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
41 |
42 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
43 |
44 | $this->assertStringContainsString( '"aggregates":"{\"5\":{\"13.50\":1},\"6\":{\"2400.00\":1,\"672.00\":1,\"53.50\":1}}"', (string) $response->getBody() );
45 | }
46 |
47 |
48 | public function testAggregateOrdersNestedSum()
49 | {
50 | $body = '{"query":"query {\n aggregateOrders(key: [\"order.statuspayment\", \"order.price\"], value: \"order.price\", type: \"sum\") {\n aggregates\n }\n}\n","variables":{},"operationName":null}';
51 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
52 |
53 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
54 |
55 | $this->assertStringContainsString( '"aggregates":"{\"5\":{\"13.50\":\"13.50\"},\"6\":{\"2400.00\":\"2400.00\",\"672.00\":\"672.00\",\"53.50\":\"53.50\"}}"', (string) $response->getBody() );
56 | }
57 |
58 |
59 | public function testSearchOrders()
60 | {
61 | $body = '{"query":"query {\n searchOrders(filter: \"{}\", include: [\"order/address\",\"order/coupon\",\"order/product\",\"order/service\",\"order/status\"]) {\n items {\n id\n sitecode\n address {\n id\n }\n coupon {\n code\n }\n product {\n id\n attribute {\n id\n }\n product {\n id\n attribute {\n id\n }\n }\n }\n service {\n id\n attribute {\n id\n }\n transaction {\n id\n }\n }\n status {\n id\n }\n }\n total\n }\n}\n","variables":{},"operationName":null}';
62 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
63 |
64 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
65 | $result = json_decode( (string) $response->getBody(), true );
66 | $items = $result['data']['searchOrders']['items'] ?? [];
67 |
68 | $this->assertEquals( $result['data']['searchOrders']['total'], count( $items ) );
69 | $this->assertEquals( 'unittest', $items[0]['sitecode'] );
70 | $this->assertEquals( 2, count( $items[0]['address'] ) );
71 | $this->assertEquals( 2, count( $items[0]['coupon'] ) );
72 | $this->assertEquals( 4, count( $items[0]['product'] ) );
73 | $this->assertEquals( 3, count( $items[0]['product'][0]['attribute'] ) );
74 | $this->assertEquals( 0, count( $items[0]['product'][0]['product'] ) );
75 | $this->assertEquals( 2, count( $items[0]['service'] ) );
76 | $this->assertEquals( 0, count( $items[0]['service'][0]['attribute'] ) );
77 | $this->assertEquals( 1, count( $items[0]['status'] ) );
78 | }
79 |
80 |
81 | public function testSaveOrder()
82 | {
83 | $stub = $this->getMockBuilder( '\\Aimeos\\MShop\\Order\\Manager\\Standard' )
84 | ->setConstructorArgs( array( $this->context ) )
85 | ->onlyMethods( ['save'] )
86 | ->getMock();
87 |
88 | $stub->expects( $this->once() )->method( 'save' )->willReturn( $stub->create( ['order.id' => 123, 'order.channel' => 'unittest'] ) );
89 |
90 | \Aimeos\MShop::inject( '\\Aimeos\\MShop\\Order\\Manager\\Standard', $stub );
91 |
92 | $body = '{"query":"mutation {\n saveOrder(input: {\n channel: \"unittest\"\n }) {\n id\n channel\n }\n}\n","variables":{},"operationName":null}';
93 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
94 |
95 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
96 |
97 | $this->assertStringContainsString( '"channel":"unittest"', (string) $response->getBody() );
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Admin/Graphql/UpdateTrait.php:
--------------------------------------------------------------------------------
1 | getAddresses()->reverse();
34 |
35 | foreach( $entries as $subentry )
36 | {
37 | $address = $addressItems->pop() ?: $manager->createAddressItem();
38 | $item->addAddressItem( $address->fromArray( $subentry ) );
39 | }
40 |
41 | return $item->deleteAddressItems( $addressItems );
42 | }
43 |
44 |
45 | /**
46 | * Updates the item
47 | *
48 | * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager object for the passed item
49 | * @param \Aimeos\MShop\Common\Item\AdddressRef\Iface $item Item to update
50 | * @param array $entry Associative list of key/value pairs of the item data
51 | * @return \Aimeos\MShop\Common\Item\Iface Updated item
52 | */
53 | protected function updateItem( \Aimeos\MShop\Common\Manager\Iface $manager,
54 | \Aimeos\MShop\Common\Item\Iface $item, array $entry ) : \Aimeos\MShop\Common\Item\Iface
55 | {
56 | $item = $item->fromArray( $entry, true );
57 |
58 | if( isset( $entry['address'] ) && $item instanceof \Aimeos\MShop\Common\Item\AddressRef\Iface ) {
59 | $item = $this->updateAddresses( $manager, $item, $entry['address'] );
60 | }
61 |
62 | if( isset( $entry['lists'] ) && $item instanceof \Aimeos\MShop\Common\Item\ListsRef\Iface ) {
63 | $item = $this->updateLists( $manager, $item, $entry['lists'] );
64 | }
65 |
66 | if( isset( $entry['property'] ) && $item instanceof \Aimeos\MShop\Common\Item\PropertyRef\Iface ) {
67 | $item = $this->updateProperties( $manager, $item, $entry['property'] );
68 | }
69 |
70 | return $item;
71 | }
72 |
73 |
74 | /**
75 | * Updates the list references of the item
76 | *
77 | * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager object for the passed item
78 | * @param \Aimeos\MShop\Common\Item\ListsRef\Iface $item Item to update
79 | * @param array $entries List of entries with key/value pairs of the reference data
80 | * @return \Aimeos\MShop\Common\Item\Iface Updated item
81 | */
82 | protected function updateLists( \Aimeos\MShop\Common\Manager\Iface $manager,
83 | \Aimeos\MShop\Common\Item\ListsRef\Iface $item, array $entries ) : \Aimeos\MShop\Common\Item\Iface
84 | {
85 | $resource = $item->getResourceType();
86 |
87 | foreach( $entries as $domain => $list )
88 | {
89 | $domainManager = \Aimeos\MShop::create( $this->context(), $domain );
90 | $listItems = $item->getListItems( $domain, null, null, false );
91 | $refItems = $item->getRefItems( $domain, null, null, false );
92 |
93 | foreach( $list as $subentry )
94 | {
95 | $refItem = null;
96 | $listId = $subentry[$resource . '.lists.id'] ?? '';
97 | $listType = $subentry[$resource . '.lists.type'] ?? 'default';
98 | $refId = $subentry['item'][$domain.'.id'] ?? $subentry[$resource . '.lists.refid'] ?? '';
99 |
100 | $listItem = $listItems->get( $listId ) ?? $item->getListItem( $domain, $listType, $refId ) ?? $manager->createListItem();
101 |
102 | if ( isset( $subentry['item'] ) ) {
103 | $refItem = ( $listItem->getRefItem() ?? $refItems->get( $refId ) ?? $domainManager->create() )->fromArray( $subentry['item'], true );
104 | }
105 |
106 | if( isset( $subentry['item']['address'] ) && $refItem instanceof \Aimeos\MShop\Common\Item\AddressRef\Iface ) {
107 | $refItem = $this->updateAddresses( $domainManager, $refItem, $subentry['item']['address'] );
108 | }
109 |
110 | if( isset( $subentry['item']['lists'] ) && $refItem instanceof \Aimeos\MShop\Common\Item\ListsRef\Iface ) {
111 | $refItem = $this->updateLists( $domainManager, $refItem, $subentry['item']['lists'] );
112 | }
113 |
114 | if( isset( $subentry['item']['property'] ) && $refItem instanceof \Aimeos\MShop\Common\Item\PropertyRef\Iface ) {
115 | $refItem = $this->updateProperties( $domainManager, $refItem, $subentry['item']['property'] );
116 | }
117 |
118 | $item->addListItem( $domain, $listItem->fromArray( $subentry, true ), $refItem );
119 | unset( $listItems[$listItem->getId()] );
120 | }
121 |
122 | $item->deleteListItems( $listItems );
123 | }
124 |
125 | return $item;
126 | }
127 |
128 |
129 | /**
130 | * Updates the properties of the item
131 | *
132 | * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager object for the passed item
133 | * @param \Aimeos\MShop\Common\Item\ListsRef\Iface $item Item to update
134 | * @param array $entries List of entries with key/value pairs of the property data
135 | * @return \Aimeos\MShop\Common\Item\Iface Updated item
136 | */
137 | protected function updateProperties( \Aimeos\MShop\Common\Manager\Iface $manager,
138 | \Aimeos\MShop\Common\Item\PropertyRef\Iface $item, array $entries ) : \Aimeos\MShop\Common\Item\Iface
139 | {
140 | $propItems = $item->getPropertyItems()->reverse();
141 |
142 | foreach( $entries as $subentry )
143 | {
144 | $propItem = $propItems->pop() ?: $manager->createPropertyItem();
145 | $item->addPropertyItem( $propItem->fromArray( $subentry ) );
146 | }
147 |
148 | return $item->deletePropertyItems( $propItems );
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/Admin/Graphql.php:
--------------------------------------------------------------------------------
1 | config()->get( 'admin/graphql/debug', false );
55 |
56 | if( !is_array( $input = $request->getParsedBody() ) )
57 | {
58 | if( empty( $input = json_decode( $request->getBody()->getContents() ?? '', true ) ) ) {
59 | throw new \Aimeos\Admin\Graphql\Exception( 'Invalid input' );
60 | }
61 | }
62 |
63 | if( isset( $input['operations'] ) )
64 | {
65 | $map = json_decode( $input['map'] ?? '[]', true );
66 | $operations = json_decode( $input['operations'], true );
67 | $operations = self::files( $request->getUploadedFiles(), $operations, $map );
68 |
69 | $opname = $operations['operationName'] ?? null;
70 | $variables = $operations['variables'] ?? [];
71 | $query = $operations['query'] ?? '';
72 | }
73 | else
74 | {
75 | $opname = $input['operationName'] ?? null;
76 | $variables = $input['variables'] ?? [];
77 | $query = $input['query'] ?? '';
78 | }
79 |
80 | $result = GraphQLBase::executeQuery(
81 | self::schema( $context ),
82 | $query,
83 | [], // root
84 | null, // context
85 | $variables,
86 | $opname
87 | )->toArray( $debug ? DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE : 0 );
88 | }
89 | catch( \Throwable $t )
90 | {
91 | $error = ['message' => $t->getMessage()];
92 |
93 | if( $debug ) {
94 | $error['locations'] = $t->getTrace();
95 | }
96 |
97 | $result = ['errors' => [$error]];
98 | }
99 |
100 | $body = \Nyholm\Psr7\Stream::create( json_encode( $result ) );
101 | return ( new Psr17Factory )->createResponse()->withBody( $body );
102 | }
103 |
104 |
105 | /**
106 | * Maps the files to the corresponding variables
107 | *
108 | * @param array $files List of \Psr\Http\Message\UploadedFileInterface objects
109 | * @param array $operations GraphQL operations with "variables" section
110 | * @param array $map GraphQL variable mapping
111 | * @return array Mapped operations array
112 | * @see https://github.com/Ecodev/graphql-upload
113 | */
114 | protected static function files( array $files, array $operations, array $map ) : array
115 | {
116 | foreach( $map as $fileKey => $locations )
117 | {
118 | foreach( $locations as $location )
119 | {
120 | $items = &$operations;
121 |
122 | foreach( explode( '.', $location ) as $key )
123 | {
124 | if( !isset( $items[$key] ) || !is_array( $items[$key] ) ) {
125 | $items[$key] = [];
126 | }
127 |
128 | $items = &$items[$key];
129 | }
130 |
131 | if( !array_key_exists( $fileKey, $files ) )
132 | {
133 | throw new \Aimeos\Admin\Graphql\Exception(
134 | "GraphQL query declared an upload in `$location`, but no corresponding file were actually uploaded"
135 | );
136 | }
137 |
138 | $items = $files[$fileKey];
139 | }
140 | }
141 |
142 | return $operations;
143 | }
144 |
145 |
146 | /**
147 | * Returns the GraphQL schema
148 | *
149 | * @param \Aimeos\MShop\ContextIface $context Context object
150 | * @return \GraphQL\Type\Schema List of GraphQL query schema definitions
151 | */
152 | protected static function schema( \Aimeos\MShop\ContextIface $context ) : \GraphQL\Type\Schema
153 | {
154 | $query = $mutation = [];
155 | $config = $context->config();
156 | $registry = new \Aimeos\Admin\Graphql\Registry( $context );
157 |
158 | $stdname = $config->get( 'admin/graphql/name', 'Standard' );
159 | $domains = $config->get( 'admin/graphql/domains', [] );
160 |
161 | foreach( $domains as $domain )
162 | {
163 | $name = $config->get( 'admin/graphql/' . $domain . '/name', 'Standard' );
164 | $classname = '\Aimeos\Admin\Graphql\\' . str_replace( '/', '\\', ucwords( $domain, '/' ) ) . '\\' . $name;
165 |
166 | if( !class_exists( $classname ) ) {
167 | $classname = '\Aimeos\Admin\Graphql\\' . $stdname;
168 | }
169 |
170 | $object = new $classname( $context, $registry );
171 |
172 | $mutation = array_replace_recursive( $mutation, $object->mutation( $domain ) );
173 | $query = array_replace_recursive( $query, $object->query( $domain ) );
174 | }
175 |
176 | return new Schema([
177 | 'query' => new ObjectType( [
178 | 'name' => 'query',
179 | 'fields' => $query
180 | ] ),
181 | 'mutation' => new ObjectType( [
182 | 'name' => 'mutation',
183 | 'fields' => $mutation
184 | ] ),
185 | ] );
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/tests/Admin/Locale/Site/StandardTest.php:
--------------------------------------------------------------------------------
1 | context = \TestHelper::context();
21 | $this->context->config()->set( 'admin/graphql/debug', true );
22 | $this->context->setView( \TestHelper::view( 'unittest', $this->context->config() ) );
23 | }
24 |
25 |
26 | public function testFindLocaleSite()
27 | {
28 | $body = '{"query":"query {\n findLocaleSite(code: \"unittest\") {\n id\n code\n hasChildren\n }\n}\n","variables":{},"operationName":null}';
29 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
30 |
31 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
32 |
33 | $this->assertStringContainsString( '"code":"unittest"', (string) $response->getBody() );
34 | }
35 |
36 |
37 | public function testGetLocaleSitePath()
38 | {
39 | $id = \Aimeos\MShop::create( $this->context, 'locale/site' )->find( 'unittest' );
40 |
41 | $body = '{"query":"query {\n getLocaleSitePath(id: \"' . $id . '\") {\n id\n code\n }\n}\n","variables":{},"operationName":null}';
42 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
43 |
44 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
45 |
46 | $this->assertStringContainsString( '"code":"unittest"', (string) $response->getBody() );
47 | }
48 |
49 |
50 | public function testGetLocaleSiteTree()
51 | {
52 | $id = \Aimeos\MShop::create( $this->context, 'locale/site' )->find( 'unittest' );
53 |
54 | $body = '{"query":"query {\n getLocaleSiteTree(id: \"' . $id . '\") {\n id\n code\n children {\n id\n code\n }\n}\n}\n","variables":{},"operationName":null}';
55 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
56 |
57 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
58 |
59 | $this->assertStringContainsString( '"code":"unittest"', (string) $response->getBody() );
60 | }
61 |
62 |
63 | public function testGetLocaleSiteTreeLevel()
64 | {
65 | $id = \Aimeos\MShop::create( $this->context, 'locale/site' )->find( 'unittest' );
66 |
67 | $body = '{"query":"query {\n getLocaleSiteTree(id: \"' . $id . '\", level: 2) {\n id\n code\n children {\n id\n code\n }\n}\n}\n","variables":{},"operationName":null}';
68 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
69 |
70 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
71 |
72 | $this->assertStringContainsString( '"code":"unittest"', (string) $response->getBody() );
73 | }
74 |
75 |
76 | public function testInsertLocaleSite()
77 | {
78 | $stub = $this->getMockBuilder( '\\Aimeos\\MShop\\Locale\\Manager\\Site\\Standard' )
79 | ->setConstructorArgs( array( $this->context ) )
80 | ->onlyMethods( ['insert'] )
81 | ->getMock();
82 |
83 | $item = $stub->create( ['locale.site.code' => 'test-graphql'] );
84 | $stub->expects( $this->once() )->method( 'insert' )->willReturn( $item );
85 |
86 | \Aimeos\MShop::inject( '\\Aimeos\\MShop\\Locale\\Manager\\Site\\Standard', $stub );
87 | \Aimeos\MShop::inject( '\\Aimeos\\MShop\\Locale\\Manager\\Site\\Sites', $stub );
88 |
89 | $body = '{"query":"mutation {\n insertLocaleSite(input: {\n code: \"test-graphql\"\n }) {\n id\n code\n }\n}\n","variables":{},"operationName":null}';
90 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
91 |
92 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
93 |
94 | $this->assertStringContainsString( '"code":"test-graphql"', (string) $response->getBody() );
95 | }
96 |
97 |
98 | public function testMoveLocaleSite()
99 | {
100 | $stub = $this->getMockBuilder( '\\Aimeos\\MShop\\Locale\\Manager\\Site\\Standard' )
101 | ->setConstructorArgs( array( $this->context ) )
102 | ->onlyMethods( ['move'] )
103 | ->getMock();
104 |
105 | $stub->expects( $this->once() )->method( 'move' );
106 |
107 | \Aimeos\MShop::inject( '\\Aimeos\\MShop\\Locale\\Manager\\Site\\Standard', $stub );
108 | \Aimeos\MShop::inject( '\\Aimeos\\MShop\\Locale\\Manager\\Site\\Sites', $stub );
109 |
110 | $body = '{"query":"mutation {\n moveLocaleSite(id: \"1\", parentid: null)\n}\n","variables":{},"operationName":null}';
111 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
112 |
113 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
114 |
115 | $this->assertStringContainsString( '"moveLocaleSite":"1"', (string) $response->getBody() );
116 | }
117 |
118 |
119 | public function testSearchLocaleSite()
120 | {
121 | $search = addslashes( addslashes( json_encode( ['==' => ['locale.site.code' => 'unittest']] ) ) );
122 | $body = '{"query":"query {\n searchLocaleSites(filter: \"' . $search . '\") {\n items {\n id\n code\n }\n total\n }\n}\n","variables":{},"operationName":null}';
123 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
124 |
125 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
126 |
127 | $this->assertStringContainsString( '"code":"unittest"', (string) $response->getBody() );
128 | }
129 |
130 |
131 | public function testSearchLocaleSiteTree()
132 | {
133 | $search = addslashes( addslashes( json_encode( ['==' => ['locale.site.code' => 'unittest']] ) ) );
134 | $body = '{"query":"query {\n searchLocaleSiteTree(filter: \"' . $search . '\") {\n id\n code\n }\n}\n","variables":{},"operationName":null}';
135 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
136 |
137 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
138 | $result = json_decode( (string) $response->getBody(), true );
139 |
140 | $this->assertEquals( 1, count( $result['data']['searchLocaleSiteTree'] ) );
141 | $this->assertEquals( 'unittest', $result['data']['searchLocaleSiteTree'][0]['code'] );
142 | }
143 |
144 | }
145 |
--------------------------------------------------------------------------------
/tests/Admin/Product/StandardTest.php:
--------------------------------------------------------------------------------
1 | context = \TestHelper::context();
21 | $this->context->config()->set( 'admin/graphql/debug', true );
22 | $this->context->setView( \TestHelper::view( 'unittest', $this->context->config() ) );
23 | }
24 |
25 |
26 | public function testGetProduct()
27 | {
28 | $id = \Aimeos\MShop::create( $this->context, 'product' )->find( 'CNC' );
29 |
30 | $body = '{"query":"query {\n getProduct(id: \"' . $id . '\", include: [\"stock\"]) {\n id\n code\n stock {\n type\n }\n }\n}\n","variables":{},"operationName":null}';
31 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
32 |
33 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
34 |
35 | $this->assertStringContainsString( '"code":"CNC"', (string) $response->getBody() );
36 | $this->assertStringContainsString( '"stock":[{"type":"default"}]', (string) $response->getBody() );
37 | }
38 |
39 |
40 | public function testFindProduct()
41 | {
42 | $body = '{"query":"query {\n findProduct(code: \"CNC\") {\n id\n code\n }\n}\n","variables":{},"operationName":null}';
43 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
44 |
45 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
46 |
47 | $this->assertStringContainsString( '"code":"CNC"', (string) $response->getBody() );
48 | }
49 |
50 |
51 | public function testFindProductType()
52 | {
53 | $body = '{"query":"query {\n findProductType(code: \"default\", domain: \"product\") {\n id\n code\n }\n}\n","variables":{},"operationName":null}';
54 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
55 |
56 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
57 |
58 | $this->assertStringContainsString( '"code":"default"', (string) $response->getBody() );
59 | }
60 |
61 |
62 | public function testSearchProducts()
63 | {
64 | $manager = \Aimeos\MShop::create( $this->context, 'product' );
65 | $filter = $manager->filter()->add( 'product.code', '==', ['CNC', 'CNE'] );
66 | $ids = $manager->search( $filter )->keys()->all();
67 |
68 | $search = addslashes( addslashes( json_encode( ['==' => ['product.id' => $ids]] ) ) );
69 | $body = '{"query":"query {\n searchProducts(filter: \"' . $search . '\", include: [\"stock\"]) {\n items {\n id\n code\n stock {\n type\n }\n }\n total\n }\n}\n","variables":{},"operationName":null}';
70 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
71 |
72 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
73 |
74 | $this->assertStringContainsString( '"code":"CNC"', (string) $response->getBody() );
75 | $this->assertStringContainsString( '"code":"CNE"', (string) $response->getBody() );
76 | $this->assertStringContainsString( '"stock":[{"type":"default"}]', (string) $response->getBody() );
77 | }
78 |
79 |
80 | public function testSaveProduct()
81 | {
82 | $stockStub = $this->getMockBuilder( '\\Aimeos\\MShop\\Stock\\Manager\\Standard' )
83 | ->setConstructorArgs( array( $this->context ) )
84 | ->onlyMethods( ['save'] )
85 | ->getMock();
86 |
87 | $stockStub->expects( $this->once() )->method( 'save' )->willReturnCallback( fn( $item ) => $item->setId( 123 ) );
88 |
89 | \Aimeos\MShop::inject( '\\Aimeos\\MShop\\Stock\\Manager\\Standard', $stockStub );
90 |
91 | $body = '{"query":"mutation {\n saveProduct(input: {\n code: \"test-graphql\"\n, stock: {\n type: \"default\" stocklevel: 100\n}\n }) {\n id\n code\n stock {\n id\n type\n stocklevel\n }\n }\n}\n","variables":{},"operationName":null}';
92 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
93 |
94 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
95 | $result = json_decode( (string) $response->getBody(), true );
96 |
97 | $this->assertTrue( isset( $result['data']['saveProduct']['id'] ) );
98 | \Aimeos\MShop::create( $this->context, 'product' )->delete( $result['data']['saveProduct']['id'] );
99 |
100 | $this->assertStringContainsString( '"code":"test-graphql"', (string) $response->getBody() );
101 | $this->assertStringContainsString( '"id":"123"', (string) $response->getBody() );
102 | $this->assertStringContainsString( '"type":"default"', (string) $response->getBody() );
103 | $this->assertStringContainsString( '"stocklevel":100', (string) $response->getBody() );
104 | }
105 |
106 |
107 | public function testSaveProducts()
108 | {
109 | $stub = $this->getMockBuilder( '\\Aimeos\\MShop\\Product\\Manager\\Standard' )
110 | ->setConstructorArgs( array( $this->context ) )
111 | ->onlyMethods( ['save', 'type'] )
112 | ->getMock();
113 |
114 | $stub->expects( $this->once() )->method( 'save' )->willReturnArgument( 0 );
115 | $stub->method( 'type' )->willReturn( ['product'] );
116 |
117 | \Aimeos\MShop::inject( '\\Aimeos\\MShop\\Product\\Manager\\Standard', $stub );
118 |
119 | $body = '{"query":"mutation {\n saveProducts(input: [{\n code: \"test-graphql\"\n }]) {\n id\n code\n }\n}\n","variables":{},"operationName":null}';
120 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
121 |
122 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
123 |
124 | $this->assertStringContainsString( '"code":"test-graphql"', (string) $response->getBody() );
125 | }
126 |
127 |
128 | public function testSaveProductLists()
129 | {
130 | $stub = $this->getMockBuilder( '\\Aimeos\\MShop\\Product\\Manager\\Standard' )
131 | ->setConstructorArgs( array( $this->context ) )
132 | ->onlyMethods( ['save', 'type'] )
133 | ->getMock();
134 |
135 | $stub->expects( $this->once() )->method( 'save' )->willReturnArgument( 0 );
136 | $stub->method( 'type' )->willReturn( ['product'] );
137 | $stub = new \Aimeos\MShop\Common\Manager\Decorator\Lists( $stub, $this->context );
138 |
139 | \Aimeos\MShop::inject( '\\Aimeos\\MShop\\Product\\Manager\\Standard', $stub );
140 |
141 | $body = '{"query":"mutation {\n saveProduct(input: {\n code: \"test-graphql\"\n lists: {\n group: {\n id: \"123\"\n item: {\n id: \"1\"\n code: \"test-group\"\n }\n }\n }\n }) {\n id\n code\n lists {\n group {\n id\n item {\n id\n code\n }\n }\n }\n }\n}\n","variables":{},"operationName":null}';
142 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
143 |
144 | $body = (string) \Aimeos\Admin\Graphql::execute( $this->context, $request )->getBody();
145 |
146 | $this->assertStringContainsString( '"code":"test-graphql"', $body );
147 | $this->assertStringContainsString( '"code":"test-group"', $body );
148 | $this->assertStringContainsString( '"id":"123"', $body );
149 | $this->assertStringContainsString( '"id":"1"', $body );
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # PHP CircleCI 2.0 configuration file
2 | #
3 | # Check https://circleci.com/docs/2.0/language-php/ for more details
4 | #
5 | version: 2.1
6 |
7 | jobs:
8 | "php81-mysql":
9 | docker:
10 | - image: aimeos/ci-php:8.1
11 | - image: mysql:latest
12 | environment:
13 | MYSQL_ROOT_PASSWORD: rootpw
14 | MYSQL_DATABASE: aimeos
15 | MYSQL_USER: aimeos
16 | MYSQL_PASSWORD: aimeos
17 | steps:
18 | - checkout
19 | - run: git clone --depth=50 --branch=master https://github.com/aimeos/aimeos-core ../aimeos-core
20 | - run: cd .. && mv project aimeos-core/ext/ai-admin-graphql && mv aimeos-core project && cd project
21 | - restore_cache:
22 | keys:
23 | - php81-{{ checksum "composer.json" }}
24 | - run: composer req --no-update webonyx/graphql-php
25 | - run: composer update -n --prefer-dist
26 | - save_cache:
27 | key: php81-{{ checksum "composer.json" }}
28 | paths: [./vendor]
29 | - run: echo " array( 'adapter' => 'mysql', 'host' => '127.0.0.1', 'port' => 3306, 'database' => 'aimeos', 'username' => 'aimeos', 'password' => 'aimeos', 'limit' => 2, 'opt-persistent' => false, 'stmt' => array( \"SET SESSIOn sort_buffer_size=2097144; SET NAMES 'utf8'; SET SESSION sql_mode='ANSI'\" ) ), 'fs' => array( 'adapter' => 'Standard', 'basedir' => '.' ), 'mq' => array( 'adapter' => 'Standard', 'db' => 'db' ) );" > config/resource.php
30 | - run: for i in `seq 1 10`; do nc -z 127.0.0.1 3306 && echo OK && exit 0; echo -n .; sleep 1; done
31 | - run: ./vendor/bin/phing -Ddir=ext/ai-admin-graphql setup testext
32 |
33 | "php82-mysql":
34 | docker:
35 | - image: aimeos/ci-php:8.2
36 | - image: mysql:latest
37 | environment:
38 | MYSQL_ROOT_PASSWORD: rootpw
39 | MYSQL_DATABASE: aimeos
40 | MYSQL_USER: aimeos
41 | MYSQL_PASSWORD: aimeos
42 | steps:
43 | - checkout
44 | - run: git clone --depth=50 --branch=master https://github.com/aimeos/aimeos-core ../aimeos-core
45 | - run: cd .. && mv project aimeos-core/ext/ai-admin-graphql && mv aimeos-core project && cd project
46 | - restore_cache:
47 | keys:
48 | - php82-{{ checksum "composer.json" }}
49 | - run: composer req --no-update webonyx/graphql-php
50 | - run: composer update -n --prefer-dist
51 | - save_cache:
52 | key: php82-{{ checksum "composer.json" }}
53 | paths: [./vendor]
54 | - run: echo " array( 'adapter' => 'mysql', 'host' => '127.0.0.1', 'port' => 3306, 'database' => 'aimeos', 'username' => 'aimeos', 'password' => 'aimeos', 'limit' => 2, 'opt-persistent' => false, 'stmt' => array( \"SET SESSIOn sort_buffer_size=2097144; SET NAMES 'utf8'; SET SESSION sql_mode='ANSI'\" ) ), 'fs' => array( 'adapter' => 'Standard', 'basedir' => '.' ), 'mq' => array( 'adapter' => 'Standard', 'db' => 'db' ) );" > config/resource.php
55 | - run: for i in `seq 1 10`; do nc -z 127.0.0.1 3306 && echo OK && exit 0; echo -n .; sleep 1; done
56 | - run: ./vendor/bin/phing -Ddir=ext/ai-admin-graphql setup coverageext
57 | - run: ./vendor/bin/php-coveralls --root_dir ext/ai-admin-graphql -o tests/coveralls.json -vvv -x tests/coverage.xml
58 |
59 | "php83-mysql":
60 | docker:
61 | - image: aimeos/ci-php:8.3
62 | - image: mysql:latest
63 | environment:
64 | MYSQL_ROOT_PASSWORD: rootpw
65 | MYSQL_DATABASE: aimeos
66 | MYSQL_USER: aimeos
67 | MYSQL_PASSWORD: aimeos
68 | steps:
69 | - checkout
70 | - run: git clone --depth=50 --branch=master https://github.com/aimeos/aimeos-core ../aimeos-core
71 | - run: cd .. && mv project aimeos-core/ext/ai-admin-graphql && mv aimeos-core project && cd project
72 | - restore_cache:
73 | keys:
74 | - php83-{{ checksum "composer.json" }}
75 | - run: composer req --no-update webonyx/graphql-php
76 | - run: composer update -n --prefer-dist
77 | - save_cache:
78 | key: php83-{{ checksum "composer.json" }}
79 | paths: [./vendor]
80 | - run: echo " array( 'adapter' => 'mysql', 'host' => '127.0.0.1', 'port' => 3306, 'database' => 'aimeos', 'username' => 'aimeos', 'password' => 'aimeos', 'limit' => 2, 'opt-persistent' => false, 'stmt' => array( \"SET SESSIOn sort_buffer_size=2097144; SET NAMES 'utf8'; SET SESSION sql_mode='ANSI'\" ) ), 'fs' => array( 'adapter' => 'Standard', 'basedir' => '.' ), 'mq' => array( 'adapter' => 'Standard', 'db' => 'db' ) );" > config/resource.php
81 | - run: for i in `seq 1 10`; do nc -z 127.0.0.1 3306 && echo OK && exit 0; echo -n .; sleep 1; done
82 | - run: ./vendor/bin/phing -Ddir=ext/ai-admin-graphql setup testext
83 |
84 | "php84-mysql":
85 | docker:
86 | - image: aimeos/ci-php:8.4
87 | - image: mysql:latest
88 | environment:
89 | MYSQL_ROOT_PASSWORD: rootpw
90 | MYSQL_DATABASE: aimeos
91 | MYSQL_USER: aimeos
92 | MYSQL_PASSWORD: aimeos
93 | steps:
94 | - checkout
95 | - run: git clone --depth=50 --branch=master https://github.com/aimeos/aimeos-core ../aimeos-core
96 | - run: cd .. && mv project aimeos-core/ext/ai-admin-graphql && mv aimeos-core project && cd project
97 | - restore_cache:
98 | keys:
99 | - php84-{{ checksum "composer.json" }}
100 | - run: composer req --no-update webonyx/graphql-php
101 | - run: composer update -n --prefer-dist
102 | - save_cache:
103 | key: php84-{{ checksum "composer.json" }}
104 | paths: [./vendor]
105 | - run: echo " array( 'adapter' => 'mysql', 'host' => '127.0.0.1', 'port' => 3306, 'database' => 'aimeos', 'username' => 'aimeos', 'password' => 'aimeos', 'limit' => 2, 'opt-persistent' => false, 'stmt' => array( \"SET SESSIOn sort_buffer_size=2097144; SET NAMES 'utf8'; SET SESSION sql_mode='ANSI'\" ) ), 'fs' => array( 'adapter' => 'Standard', 'basedir' => '.' ), 'mq' => array( 'adapter' => 'Standard', 'db' => 'db' ) );" > config/resource.php
106 | - run: for i in `seq 1 10`; do nc -z 127.0.0.1 3306 && echo OK && exit 0; echo -n .; sleep 1; done
107 | - run: ./vendor/bin/phing -Ddir=ext/ai-admin-graphql setup testext
108 |
109 | "php85-mysql":
110 | docker:
111 | - image: aimeos/ci-php:8.5
112 | - image: mysql:latest
113 | environment:
114 | MYSQL_ROOT_PASSWORD: rootpw
115 | MYSQL_DATABASE: aimeos
116 | MYSQL_USER: aimeos
117 | MYSQL_PASSWORD: aimeos
118 | steps:
119 | - checkout
120 | - run: git clone --depth=50 --branch=master https://github.com/aimeos/aimeos-core ../aimeos-core
121 | - run: cd .. && mv project aimeos-core/ext/ai-admin-graphql && mv aimeos-core project && cd project
122 | - restore_cache:
123 | keys:
124 | - php85-{{ checksum "composer.json" }}
125 | - run: composer req --no-update webonyx/graphql-php
126 | - run: composer update -n --prefer-dist
127 | - save_cache:
128 | key: php85-{{ checksum "composer.json" }}
129 | paths: [./vendor]
130 | - run: echo " array( 'adapter' => 'mysql', 'host' => '127.0.0.1', 'port' => 3306, 'database' => 'aimeos', 'username' => 'aimeos', 'password' => 'aimeos', 'limit' => 2, 'opt-persistent' => false, 'stmt' => array( \"SET SESSIOn sort_buffer_size=2097144; SET NAMES 'utf8'; SET SESSION sql_mode='ANSI'\" ) ), 'fs' => array( 'adapter' => 'Standard', 'basedir' => '.' ), 'mq' => array( 'adapter' => 'Standard', 'db' => 'db' ) );" > config/resource.php
131 | - run: for i in `seq 1 10`; do nc -z 127.0.0.1 3306 && echo OK && exit 0; echo -n .; sleep 1; done
132 | - run: ./vendor/bin/phing -Ddir=ext/ai-admin-graphql setup testext
133 |
134 | workflows:
135 | version: 2
136 | unittest:
137 | jobs:
138 | - "php81-mysql"
139 | - "php82-mysql"
140 | - "php83-mysql"
141 | - "php84-mysql"
142 | - "php85-mysql"
143 |
--------------------------------------------------------------------------------
/tests/Admin/Catalog/StandardTest.php:
--------------------------------------------------------------------------------
1 | context = \TestHelper::context();
21 | $this->context->config()->set( 'admin/graphql/debug', true );
22 | $this->context->setView( \TestHelper::view( 'unittest', $this->context->config() ) );
23 | }
24 |
25 |
26 | public function testFindCatalog()
27 | {
28 | $body = '{"query":"query {\n findCatalog(code: \"cafe\") {\n id\n code\n }\n}\n","variables":{},"operationName":null}';
29 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
30 |
31 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
32 |
33 | $this->assertStringContainsString( '"code":"cafe"', (string) $response->getBody() );
34 | }
35 |
36 |
37 | public function testGetCatalogPath()
38 | {
39 | $id = \Aimeos\MShop::create( $this->context, 'catalog' )->find( 'cafe' );
40 |
41 | $body = '{"query":"query {\n getCatalogPath(id: \"' . $id . '\") {\n id\n code\n }\n}\n","variables":{},"operationName":null}';
42 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
43 |
44 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
45 |
46 | $this->assertStringContainsString( '"code":"root"', (string) $response->getBody() );
47 | $this->assertStringContainsString( '"code":"cafe"', (string) $response->getBody() );
48 | }
49 |
50 |
51 | public function testGetCatalogTree()
52 | {
53 | $body = '{"query":"query {\n getCatalogTree {\n id\n code\n children {\n id\n code\n children {\n id\n code\n}\n}\n}\n}\n","variables":{},"operationName":null}';
54 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
55 |
56 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
57 |
58 | $this->assertStringContainsString( '"code":"root"', (string) $response->getBody() );
59 | $this->assertStringContainsString( '"code":"cafe"', (string) $response->getBody() );
60 | }
61 |
62 |
63 | public function testGetCatalogTreeId()
64 | {
65 | $id = \Aimeos\MShop::create( $this->context, 'catalog' )->find( 'categories' );
66 |
67 | $body = '{"query":"query {\n getCatalogTree(id: \"' . $id . '\") {\n id\n code\n children {\n id\n code\n }\n}\n}\n","variables":{},"operationName":null}';
68 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
69 |
70 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
71 |
72 | $this->assertStringContainsString( '"code":"categories"', (string) $response->getBody() );
73 | $this->assertStringContainsString( '"code":"cafe"', (string) $response->getBody() );
74 | }
75 |
76 |
77 | public function testGetCatalogTreeLevel()
78 | {
79 | $body = '{"query":"query {\n getCatalogTree(level: 2) {\n id\n code\n children {\n id\n code\n }\n}\n}\n","variables":{},"operationName":null}';
80 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
81 |
82 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
83 |
84 | $this->assertStringContainsString( '"code":"root"', (string) $response->getBody() );
85 | $this->assertStringContainsString( '"code":"categories"', (string) $response->getBody() );
86 | }
87 |
88 |
89 | public function testInsertCatalog()
90 | {
91 | $stub = $this->getMockBuilder( '\\Aimeos\\MShop\\Catalog\\Manager\\Standard' )
92 | ->setConstructorArgs( array( $this->context ) )
93 | ->onlyMethods( ['insert'] )
94 | ->getMock();
95 |
96 | $item = $stub->create( ['catalog.code' => 'test-graphql'] );
97 | $stub->expects( $this->once() )->method( 'insert' )->willReturn( $item );
98 |
99 | \Aimeos\MShop::inject( '\\Aimeos\\MShop\\Catalog\\Manager\\Standard', $stub );
100 |
101 | $body = '{"query":"mutation {\n insertCatalog(input: {\n code: \"test-graphql\"\n }) {\n id\n code\n }\n}\n","variables":{},"operationName":null}';
102 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
103 |
104 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
105 |
106 | $this->assertStringContainsString( '"code":"test-graphql"', (string) $response->getBody() );
107 | }
108 |
109 |
110 | public function testMoveCatalog()
111 | {
112 | $stub = $this->getMockBuilder( '\\Aimeos\\MShop\\Catalog\\Manager\\Standard' )
113 | ->setConstructorArgs( array( $this->context ) )
114 | ->onlyMethods( ['move'] )
115 | ->getMock();
116 |
117 | $stub->expects( $this->once() )->method( 'move' );
118 |
119 | \Aimeos\MShop::inject( '\\Aimeos\\MShop\\Catalog\\Manager\\Standard', $stub );
120 |
121 | $body = '{"query":"mutation {\n moveCatalog(id: \"1\", parentid: null)\n}\n","variables":{},"operationName":null}';
122 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
123 |
124 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
125 |
126 | $this->assertStringContainsString( '"moveCatalog":"1"', (string) $response->getBody() );
127 | }
128 |
129 |
130 | public function testSearchCatalog()
131 | {
132 | $search = addslashes( addslashes( json_encode( ['~=' => ['catalog.code' => 'c']] ) ) );
133 | $body = '{"query":"query {\n searchCatalogs(filter: \"' . $search . '\") {\n items {\n id\n code\n }\n total\n }\n}\n","variables":{},"operationName":null}';
134 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
135 |
136 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
137 |
138 | $this->assertStringContainsString( '"total":3', (string) $response->getBody() );
139 | $this->assertStringContainsString( '"code":"categories"', (string) $response->getBody() );
140 | $this->assertStringContainsString( '"code":"cafe"', (string) $response->getBody() );
141 | $this->assertStringContainsString( '"code":"misc"', (string) $response->getBody() );
142 | }
143 |
144 |
145 | public function testSearchCatalogTree()
146 | {
147 | $filter = ['||' => [
148 | ['~=' => ['catalog.code' => 'c']],
149 | ['==' => ['catalog.code' => 'new']],
150 | ['==' => ['catalog.code' => 'internet']],
151 | ['==' => ['catalog.code' => 'root']]
152 | ]];
153 |
154 | $search = addslashes( addslashes( json_encode( $filter ) ) );
155 | $body = '{"query":"query {\n searchCatalogTree(filter: \"' . $search . '\") {\n id\n code\n children {\n id\n code\n children {\n id\n code\n}}}\n}\n","variables":{},"operationName":null}';
156 | $request = new \Nyholm\Psr7\ServerRequest( 'POST', 'localhost', [], $body );
157 |
158 | $response = \Aimeos\Admin\Graphql::execute( $this->context, $request );
159 | $result = json_decode( (string) $response->getBody(), true );
160 |
161 | $this->assertEquals( 1, count( $result['data']['searchCatalogTree'] ) );
162 | $this->assertEquals( 'root', $result['data']['searchCatalogTree'][0]['code'] );
163 | $this->assertEquals( 2, count( $result['data']['searchCatalogTree'][0]['children'] ) );
164 | $this->assertEquals( 'categories', $result['data']['searchCatalogTree'][0]['children'][0]['code'] );
165 | $this->assertEquals( 2, count( $result['data']['searchCatalogTree'][0]['children'][0]['children'] ) );
166 | $this->assertEquals( 'cafe', $result['data']['searchCatalogTree'][0]['children'][0]['children'][0]['code'] );
167 | $this->assertEquals( 'misc', $result['data']['searchCatalogTree'][0]['children'][0]['children'][1]['code'] );
168 | $this->assertEquals( 'group', $result['data']['searchCatalogTree'][0]['children'][1]['code'] );
169 | $this->assertEquals( 2, count( $result['data']['searchCatalogTree'][0]['children'][1]['children'] ) );
170 | $this->assertEquals( 'new', $result['data']['searchCatalogTree'][0]['children'][1]['children'][0]['code'] );
171 | $this->assertEquals( 'internet', $result['data']['searchCatalogTree'][0]['children'][1]['children'][1]['code'] );
172 | }
173 |
174 | }
175 |
--------------------------------------------------------------------------------
/src/Admin/Graphql/Base.php:
--------------------------------------------------------------------------------
1 | context = $context;
37 | $this->registry = $registry;
38 | }
39 |
40 |
41 | /**
42 | * Checks if the user has access to the given domain and action
43 | *
44 | * @param string $domain Domain path of the manager
45 | * @param string $action Action name
46 | * @return bool True if access is allowed, false if not
47 | */
48 | protected function access( string $domain, string $action ) : bool
49 | {
50 | $groups = $this->context->config()->get( 'admin/graphql/resource/' . $domain . '/' . $action, [] );
51 |
52 | if( $this->context->view()->access( $groups ) === true ) {
53 | return true;
54 | }
55 |
56 | throw new \Aimeos\Admin\Graphql\Exception( 'Forbidden', 403 );
57 | }
58 |
59 |
60 | /**
61 | * Returns a closure for aggregating items
62 | *
63 | * @param string $domain Domain path of the manager
64 | * @return \Closure Anonymous method aggregating several items
65 | */
66 | protected function aggregateItems( string $domain ) : \Closure
67 | {
68 | return function( $root, $args, $context ) use ( $domain ) {
69 |
70 | $this->access( $domain, 'get' );
71 | $manager = \Aimeos\MShop::create( $this->context(), $domain );
72 |
73 | $filter = $manager->filter()->order( $args['sort'] )->slice( 0, $args['limit'] );
74 | $filter->add( $filter->parse( json_decode( $args['filter'], true ) ) );
75 |
76 | return $manager->aggregate( $filter, $args['key'], $args['value'], $args['type'] )->all();
77 | };
78 | }
79 |
80 |
81 | /**
82 | * Returns the context object
83 | *
84 | * @return \Aimeos\MShop\ContextIface Context object
85 | */
86 | protected function context() : \Aimeos\MShop\ContextIface
87 | {
88 | return $this->context;
89 | }
90 |
91 |
92 | /**
93 | * Returns a closure for deleting items
94 | *
95 | * @param string $domain Domain path of the manager
96 | * @return \Closure Anonymous method deleting one or more items
97 | */
98 | protected function deleteItems( string $domain ) : \Closure
99 | {
100 | return function( $root, $args, $context ) use ( $domain ) {
101 |
102 | $this->access( $domain, 'delete' );
103 | \Aimeos\MShop::create( $this->context(), $domain )->delete( $args['id'] );
104 | return $args['id'];
105 | };
106 | }
107 |
108 |
109 | /**
110 | * Returns a closure for returning a single item by its ID
111 | *
112 | * @param string $domain Domain path of the manager
113 | * @return \Closure Anonymous method returning one item
114 | */
115 | protected function getItem( string $domain ) : \Closure
116 | {
117 | return function( $root, $args, $context ) use ( $domain ) {
118 |
119 | $this->access( $domain, 'get' );
120 | return $this->filter( \Aimeos\MShop::create( $this->context(), $domain )->get( $args['id'], $args['include'] ) );
121 | };
122 | }
123 |
124 |
125 | /**
126 | * Returns the item if not removed for security reasons
127 | *
128 | * @param \Aimeos\MShop\Common\Item\Iface $item Item to check
129 | * @return \Aimeos\MShop\Common\Item\Iface Item if not removed
130 | */
131 | protected function filter( \Aimeos\MShop\Common\Item\Iface $item ) : \Aimeos\MShop\Common\Item\Iface
132 | {
133 | return $item;
134 | }
135 |
136 |
137 | /**
138 | * Returns the items if not removed for security reasons
139 | *
140 | * @param iterable $items List of items to check
141 | * @return iterable List of items not removed
142 | */
143 | protected function filters( iterable $items ) : iterable
144 | {
145 | return $items;
146 | }
147 |
148 |
149 | /**
150 | * Returns a closure for returning a single item by its code
151 | *
152 | * @param string $domain Domain path of the manager
153 | * @return \Closure Anonymous method returning one item
154 | */
155 | protected function findItem( string $domain ) : \Closure
156 | {
157 | return function( $root, $args, $context ) use ( $domain ) {
158 |
159 | $this->access( $domain, 'get' );
160 | return $this->filter( \Aimeos\MShop::create( $this->context(), $domain )->find( $args['code'], $args['include'] ) );
161 | };
162 | }
163 |
164 |
165 | /**
166 | * Returns a closure for returning a single type item by its code
167 | *
168 | * @param string $domain Domain path of the manager
169 | * @return \Closure Anonymous method returning one item
170 | */
171 | protected function findTypeItem( string $domain ) : \Closure
172 | {
173 | return function( $root, $args, $context ) use ( $domain ) {
174 |
175 | $this->access( $domain, 'get' );
176 | return $this->filter( \Aimeos\MShop::create( $this->context(), $domain )->find( $args['code'], [], $args['domain'] ) );
177 | };
178 | }
179 |
180 |
181 | /**
182 | * Returns a closure for returning several items
183 | *
184 | * @param string $domain Domain path of the manager
185 | * @return \Closure Anonymous method returning several items
186 | */
187 | protected function searchItems( string $domain ) : \Closure
188 | {
189 | return function( $root, $args, $context ) use ( $domain ) {
190 |
191 | $this->access( $domain, 'get' );
192 | $manager = \Aimeos\MShop::create( $this->context(), $domain );
193 |
194 | $filter = $manager->filter()->order( $args['sort'] )->slice( $args['offset'], $args['limit'] );
195 | $filter->add( $filter->parse( json_decode( $args['filter'], true ) ) );
196 |
197 | $total = 0;
198 | $items = $this->filters( $manager->search( $filter, $args['include'], $total )->toArray() );
199 |
200 | return [
201 | 'items' => $items,
202 | 'total' => $total
203 | ];
204 | };
205 | }
206 |
207 |
208 | /**
209 | * Returns a closure for saving one item
210 | *
211 | * @param string $domain Domain path of the manager
212 | * @return \Closure Anonymous method returning one item
213 | */
214 | protected function saveItem( string $domain ) : \Closure
215 | {
216 | return function( $root, $args, $context ) use ( $domain ) {
217 |
218 | if( empty( $entry = $args['input'] ) ) {
219 | throw new \Aimeos\Admin\Graphql\Exception( 'Parameter "input" must not be empty' );
220 | }
221 |
222 | $this->access( $domain, 'save' );
223 | $ref = $this->getRefs( $entry, $domain );
224 | $manager = \Aimeos\MShop::create( $this->context(), $domain );
225 | $key = str_replace( '/', '.', $domain ) . '.id';
226 |
227 | if( !empty( $entry[$key] ) ) {
228 | $item = $manager->get( $entry[$key], array_unique( $ref ) );
229 | } else {
230 | $item = $manager->create();
231 | }
232 |
233 | return $manager->save( $this->updateItem( $manager, $item, $entry ) );
234 | };
235 | }
236 |
237 |
238 | /**
239 | * Returns a closure for saving several items
240 | *
241 | * @param string $domain Domain path of the manager
242 | * @return \Closure Anonymous method saving several items
243 | */
244 | protected function saveItems( string $domain ) : \Closure
245 | {
246 | return function( $root, $args, $context ) use ( $domain ) {
247 |
248 | if( empty( $entries = (array) $args['input'] ) ) {
249 | throw new \Aimeos\Admin\Graphql\Exception( 'Parameter "input" must not be empty' );
250 | }
251 |
252 | $this->access( $domain, 'save' );
253 | $manager = \Aimeos\MShop::create( $this->context(), $domain );
254 | $key = str_replace( '/', '.', $domain ) . '.id';
255 |
256 | $ids = array_filter( array_column( $entries, $key ) );
257 | $filter = $manager->filter()->add( $key, '==', $ids )->slice( 0, count( $entries ) );
258 |
259 | $ref = [];
260 | foreach( $entries as $entry ) {
261 | $ref = array_merge( $ref, $this->getRefs( $entry, $domain ) );
262 | }
263 |
264 | $map = $manager->search( $filter, array_unique( $ref ) );
265 | $items = [];
266 |
267 | foreach( $entries as $entry )
268 | {
269 | $item = $map->get( $entry[$key] ?? null ) ?: $manager->create();
270 | $items[] = $this->updateItem( $manager, $item, $entry );
271 | }
272 |
273 | return $manager->save( $items );
274 | };
275 | }
276 |
277 |
278 | /**
279 | * Recursively collect all referenced domains
280 | * @param array $entry Entry or subentry with input data
281 | * @param string $domain Domain of subentry
282 | * @return array Array with all domains collected
283 | */
284 | protected function getRefs( array $entry, string $domain ): array
285 | {
286 | $ref = array_keys( $entry['lists'] ?? [] );
287 |
288 | foreach( $entry['lists'] ?? [] as $listDomain => $subentry )
289 | {
290 | foreach( $subentry ?? [] as $subItem ) {
291 | $ref = array_merge( $ref, $this->getRefs( $subItem['item'] ?? [], $listDomain ) );
292 | }
293 | }
294 |
295 | if( isset( $entry['property'] ) ) {
296 | $ref[] = $domain . '/property';
297 | }
298 |
299 | if( isset( $entry['stock'] ) ) {
300 | $ref[] = 'stock';
301 | }
302 |
303 | return $ref;
304 | }
305 |
306 |
307 | /**
308 | * Returns the types registry
309 | *
310 | * @return \Aimeos\Admin\Graphql\Registry Type registry object
311 | */
312 | protected function types() : Registry
313 | {
314 | return $this->registry;
315 | }
316 | }
317 |
--------------------------------------------------------------------------------
/src/Admin/Graphql/Catalog/Standard.php:
--------------------------------------------------------------------------------
1 | [
37 | 'type' => Type::string(),
38 | 'args' => [
39 | ['name' => 'id', 'type' => Type::string(), 'description' => 'Item ID'],
40 | ],
41 | 'resolve' => $this->deleteItems( $domain ),
42 | ],
43 | 'deleteCatalogs' => [
44 | 'type' => Type::listOf( Type::string() ),
45 | 'args' => [
46 | ['name' => 'id', 'type' => Type::listOf( Type::string() ), 'description' => 'List of item IDs'],
47 | ],
48 | 'resolve' => $this->deleteItems( $domain ),
49 | ],
50 | 'saveCatalog' => [
51 | 'type' => $this->types()->treeOutputType( $domain ),
52 | 'args' => [
53 | ['name' => 'input', 'type' => $this->types()->inputType( $domain ), 'description' => 'Item object'],
54 | ],
55 | 'resolve' => $this->saveItem( $domain ),
56 | ],
57 | 'saveCatalogs' => [
58 | 'type' => Type::listOf( $this->types()->treeOutputType( $domain ) ),
59 | 'args' => [
60 | ['name' => 'input', 'type' => Type::listOf( $this->types()->inputType( $domain ) ), 'description' => 'Item objects'],
61 | ],
62 | 'resolve' => $this->saveItems( $domain ),
63 | ],
64 | 'insertCatalog' => [
65 | 'type' => $this->types()->treeOutputType( $domain ),
66 | 'args' => [
67 | ['name' => 'input', 'type' => Type::nonNull( $this->types()->inputType( $domain ) ), 'description' => 'Item object'],
68 | ['name' => 'parentid', 'type' => Type::string(), 'defaultValue' => null, 'description' => 'ID of the parent category'],
69 | ['name' => 'refid', 'type' => Type::string(), 'defaultValue' => null, 'description' => 'Category ID the new item should be inserted before'],
70 | ],
71 | 'resolve' => $this->insertItem( $domain ),
72 | ],
73 | 'moveCatalog' => [
74 | 'type' => Type::String(),
75 | 'args' => [
76 | ['name' => 'id', 'type' => Type::nonNull( Type::string() ), 'description' => 'ID of the category to move'],
77 | ['name' => 'parentid', 'type' => Type::string(), 'description' => 'ID of the old parent category'],
78 | ['name' => 'targetid', 'type' => Type::string(), 'defaultValue' => null, 'description' => 'ID of the new parent category'],
79 | ['name' => 'refid', 'type' => Type::string(), 'defaultValue' => null, 'description' => 'Category ID the new item should be inserted before'],
80 | ],
81 | 'resolve' => $this->moveItem( $domain ),
82 | ]
83 | ];
84 | }
85 |
86 |
87 | /**
88 | * Returns GraphQL schema definition for the available queries
89 | *
90 | * @param string $domain Domain name of the responsible manager
91 | * @return array GraphQL query schema definition
92 | */
93 | public function query( string $domain ) : array
94 | {
95 | return [
96 | 'getCatalog' => [
97 | 'type' => $this->types()->treeOutputType( $domain ),
98 | 'args' => [
99 | ['name' => 'id', 'type' => Type::string(), 'description' => 'Unique ID'],
100 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
101 | ],
102 | 'resolve' => $this->getItem( $domain ),
103 | ],
104 | 'getCatalogPath' => [
105 | 'type' => Type::listOf( $this->types()->treeOutputType( $domain ) ),
106 | 'args' => [
107 | ['name' => 'id', 'type' => Type::nonNull( Type::string() ), 'description' => 'Unique category ID'],
108 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
109 | ],
110 | 'resolve' => $this->getPath( $domain ),
111 | ],
112 | 'getCatalogTree' => [
113 | 'type' => $this->types()->treeOutputType( $domain ),
114 | 'args' => [
115 | ['name' => 'id', 'type' => Type::string(), 'defaultValue' => null, 'description' => 'Unique category ID'],
116 | ['name' => 'level', 'type' => Type::int(), 'defaultValue' => 3, 'description' => '1 = node only, 2 = with children, 3 = whole subtree'],
117 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
118 | ],
119 | 'resolve' => $this->getTree( $domain ),
120 | ],
121 | 'findCatalog' => [
122 | 'type' => $this->types()->treeOutputType( $domain ),
123 | 'args' => [
124 | ['name' => 'code', 'type' => Type::nonNull( Type::string() ), 'description' => 'Unique code'],
125 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
126 | ],
127 | 'resolve' => $this->findItem( $domain ),
128 | ],
129 | 'searchCatalogs' => [
130 | 'type' => $this->types()->searchOutputType( $domain, fn( $path ) => $this->types()->treeOutputType( $path ) ),
131 | 'args' => [
132 | ['name' => 'filter', 'type' => Type::string(), 'defaultValue' => '{}', 'description' => 'Filter conditions'],
133 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
134 | ['name' => 'sort', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Sort keys'],
135 | ['name' => 'offset', 'type' => Type::int(), 'defaultValue' => 0, 'description' => 'Slice offset'],
136 | ['name' => 'limit', 'type' => Type::int(), 'defaultValue' => 100, 'description' => 'Slice size'],
137 | ],
138 | 'resolve' => $this->searchItems( $domain ),
139 | ],
140 | 'searchCatalogTree' => [
141 | 'type' => Type::listOf( $this->types()->treeOutputType( $domain ) ),
142 | 'args' => [
143 | ['name' => 'filter', 'type' => Type::string(), 'defaultValue' => '{}', 'description' => 'Filter conditions'],
144 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
145 | ['name' => 'limit', 'type' => Type::int(), 'defaultValue' => 100, 'description' => 'Slice size'],
146 | ],
147 | 'resolve' => $this->searchTree( $domain ),
148 | ]
149 | ];
150 | }
151 |
152 |
153 | /**
154 | * Returns the tree of parents including the given items as leaf nodes
155 | *
156 | * @param \Aimeos\Map $items List of items (with numeric indexes)
157 | * @param array $refs List of domains to fetch in addition
158 | * @return \Aimeos\Map List of parent items
159 | */
160 | protected function getParents( \Aimeos\Map $items, array $refs ) : \Aimeos\Map
161 | {
162 | if( ( $parentIds = $items->getParentId()->filter() )->isEmpty() ) {
163 | return $items;
164 | }
165 |
166 | $manager = $this->manager();
167 | $filter = $manager->filter()
168 | ->add( 'catalog.id', '==', $parentIds->unique() )
169 | ->order( ['-catalog.level', 'sort:catalog:position'] )
170 | ->slice( 0, 0x7fffffff );
171 |
172 | $parents = $manager->search( $filter, $refs );
173 | $indexes = $parentIds->unique()->flip();
174 | $itemkeys = $items->getId()->flip();
175 |
176 | foreach( $parents as $pid => $parent )
177 | {
178 | if( isset( $itemkeys[$pid] ) ) {
179 | $items[$itemkeys[$pid]]->addChild( $items[$indexes[$pid]] );
180 | unset( $items[$indexes[$pid]] );
181 | } else {
182 | $items[$indexes[$pid]] = $parent->addChild( $items[$indexes[$pid]] );
183 | }
184 | }
185 |
186 | return $this->getParents( $items, $refs );
187 | }
188 |
189 |
190 | /**
191 | * Returns a closure for returning the nodes from the passed ID up to the root node
192 | *
193 | * @param string $domain Domain path of the manager
194 | * @return \Closure Anonymous method returning one item
195 | */
196 | protected function getPath( string $domain ) : \Closure
197 | {
198 | return function( $root, $args, $context ) use ( $domain ) {
199 | return $this->manager()->getPath( $args['id'], $args['include'] );
200 | };
201 | }
202 |
203 |
204 | /**
205 | * Returns a closure for returning the node tree
206 | *
207 | * @param string $domain Domain path of the manager
208 | * @return \Closure Anonymous method returning one item
209 | */
210 | protected function getTree( string $domain ) : \Closure
211 | {
212 | return function( $root, $args, $context ) use ( $domain ) {
213 | return $this->manager()->getTree( $args['id'], $args['include'], $args['level'] );
214 | };
215 | }
216 |
217 |
218 | /**
219 | * Returns a closure for inserting a new node into the tree
220 | *
221 | * @param string $domain Domain path of the manager
222 | * @return \Closure Anonymous method returning one item
223 | */
224 | protected function insertItem( string $domain ) : \Closure
225 | {
226 | return function( $root, $args, $context ) use ( $domain ) {
227 |
228 | if( empty( $entry = $args['input'] ) ) {
229 | throw new \Aimeos\Admin\Graphql\Exception( 'Parameter "input" must not be empty' );
230 | }
231 |
232 | $manager = $this->manager();
233 | $item = $this->updateItem( $manager, $manager->create(), $entry );
234 |
235 | return $manager->insert( $item, $args['parentid'], $args['refid'] );
236 | };
237 | }
238 |
239 |
240 | /**
241 | * Returns the manager for the site items
242 | *
243 | * @return \Aimeos\MShop\Common\Manager\Iface Manager object
244 | */
245 | protected function manager() : \Aimeos\MShop\Common\Manager\Iface
246 | {
247 | if( !isset( $this->manager ) ) {
248 | $this->manager = \Aimeos\MShop::create( $this->context(), 'catalog' );
249 | }
250 |
251 | return $this->manager;
252 | }
253 |
254 |
255 | /**
256 | * Returns a closure for moving a node within the tree
257 | *
258 | * @param string $domain Domain path of the manager
259 | * @return \Closure Anonymous method returning one item
260 | */
261 | protected function moveItem( string $domain ) : \Closure
262 | {
263 | return function( $root, $args, $context ) use ( $domain ) {
264 | $this->manager()->move( $args['id'], $args['parentid'], $args['targetid'], $args['refid'] );
265 | return $args['id'];
266 | };
267 | }
268 |
269 |
270 | /**
271 | * Returns a closure for searching the tree
272 | *
273 | * @param string $domain Domain path of the manager
274 | * @return \Closure Anonymous method returning one item
275 | */
276 | protected function searchTree( string $domain ) : \Closure
277 | {
278 | return function( $root, $args, $context ) use ( $domain ) {
279 |
280 | $this->access( $domain, 'get' );
281 | $manager = $this->manager();
282 |
283 | $filter = $manager->filter()->order( ['-catalog.level', 'sort:catalog:position'] );
284 | $filter->add( $filter->parse( json_decode( $args['filter'], true ) ) );
285 |
286 | $items = $manager->search( $filter->slice( 0, $args['limit'] ), $args['include'] );
287 |
288 | foreach( $items as $key => $item )
289 | {
290 | if( isset( $items[$item->getParentId()] ) ) {
291 | $items[$item->getParentId()]->addChild( $item );
292 | unset( $items[$key] );
293 | }
294 | }
295 |
296 | return $this->getParents( $items->values(), $args['include'] );
297 | };
298 | }
299 | }
300 |
--------------------------------------------------------------------------------
/src/Admin/Graphql/Locale/Site/Standard.php:
--------------------------------------------------------------------------------
1 | [
37 | 'type' => Type::string(),
38 | 'args' => [
39 | ['name' => 'id', 'type' => Type::string(), 'description' => 'Item ID'],
40 | ],
41 | 'resolve' => $this->deleteItems( $domain ),
42 | ],
43 | 'delete' . str_replace( '/', '', ucwords( $domain, '/' ) ) . 's' => [
44 | 'type' => Type::listOf( Type::string() ),
45 | 'args' => [
46 | ['name' => 'id', 'type' => Type::listOf( Type::string() ), 'description' => 'List of item IDs'],
47 | ],
48 | 'resolve' => $this->deleteItems( $domain ),
49 | ],
50 | 'insert' . str_replace( '/', '', ucwords( $domain, '/' ) ) => [
51 | 'type' => $this->types()->siteOutputType(),
52 | 'args' => [
53 | ['name' => 'input', 'type' => Type::nonNull( $this->types()->inputType( $domain ) ), 'description' => 'Item object'],
54 | ['name' => 'parentid', 'type' => Type::string(), 'defaultValue' => null, 'description' => 'ID of the parent site'],
55 | ['name' => 'refid', 'type' => Type::string(), 'defaultValue' => null, 'description' => 'Site ID the new item should be inserted before'],
56 | ],
57 | 'resolve' => $this->insertItem( $domain ),
58 | ],
59 | 'move' . str_replace( '/', '', ucwords( $domain, '/' ) ) => [
60 | 'type' => Type::String(),
61 | 'args' => [
62 | ['name' => 'id', 'type' => Type::nonNull( Type::string() ), 'description' => 'ID of the site to move'],
63 | ['name' => 'parentid', 'type' => Type::string(), 'description' => 'ID of the old parent site'],
64 | ['name' => 'targetid', 'type' => Type::string(), 'defaultValue' => null, 'description' => 'ID of the new parent site'],
65 | ['name' => 'refid', 'type' => Type::string(), 'defaultValue' => null, 'description' => 'Site ID the new item should be inserted before'],
66 | ],
67 | 'resolve' => $this->moveItem( $domain ),
68 | ],
69 | 'save' . str_replace( '/', '', ucwords( $domain, '/' ) ) => [
70 | 'type' => $this->types()->siteOutputType(),
71 | 'args' => [
72 | ['name' => 'input', 'type' => $this->types()->inputType( $domain ), 'description' => 'Item object'],
73 | ],
74 | 'resolve' => $this->saveItem( $domain ),
75 | ],
76 | 'save' . str_replace( '/', '', ucwords( $domain, '/' ) ) . 's' => [
77 | 'type' => Type::listOf( $this->types()->siteOutputType() ),
78 | 'args' => [
79 | ['name' => 'input', 'type' => Type::listOf( $this->types()->inputType( $domain ) ), 'description' => 'Item objects'],
80 | ],
81 | 'resolve' => $this->saveItems( $domain ),
82 | ]
83 | ];
84 | }
85 |
86 |
87 | /**
88 | * Returns GraphQL schema definition for the available queries
89 | *
90 | * @param string $domain Domain name of the responsible manager
91 | * @return array GraphQL query schema definition
92 | */
93 | public function query( string $domain ) : array
94 | {
95 | return [
96 | 'find' . str_replace( '/', '', ucwords( $domain, '/' ) ) => [
97 | 'type' => $this->types()->siteOutputType(),
98 | 'args' => [
99 | ['name' => 'code', 'type' => Type::nonNull( Type::string() ), 'description' => 'Unique code'],
100 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
101 | ],
102 | 'resolve' => $this->findItem( $domain ),
103 | ],
104 | 'get' . str_replace( '/', '', ucwords( $domain, '/' ) ) => [
105 | 'type' => $this->types()->siteOutputType(),
106 | 'args' => [
107 | ['name' => 'id', 'type' => Type::string(), 'description' => 'Unique ID'],
108 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
109 | ],
110 | 'resolve' => $this->getItem( $domain ),
111 | ],
112 | 'get' . str_replace( '/', '', ucwords( $domain, '/' ) ) . 'Path' => [
113 | 'type' => Type::listOf( $this->types()->siteOutputType() ),
114 | 'args' => [
115 | ['name' => 'id', 'type' => Type::nonNull( Type::string() ), 'description' => 'Unique site ID'],
116 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
117 | ],
118 | 'resolve' => $this->getPath( $domain ),
119 | ],
120 | 'get' . str_replace( '/', '', ucwords( $domain, '/' ) ) . 'Tree' => [
121 | 'type' => $this->types()->siteOutputType(),
122 | 'args' => [
123 | ['name' => 'id', 'type' => Type::string(), 'defaultValue' => null, 'description' => 'Unique site ID'],
124 | ['name' => 'level', 'type' => Type::int(), 'defaultValue' => 3, 'description' => '1 = node only, 2 = with children, 3 = whole subtree'],
125 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
126 | ],
127 | 'resolve' => $this->getTree( $domain ),
128 | ],
129 | 'search' . str_replace( '/', '', ucwords( $domain, '/' ) ) . 's' => [
130 | 'type' => $this->types()->searchOutputType( $domain, fn( $path ) => $this->types()->siteOutputType() ),
131 | 'args' => [
132 | ['name' => 'filter', 'type' => Type::string(), 'defaultValue' => '{}', 'description' => 'Filter conditions'],
133 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
134 | ['name' => 'sort', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Sort keys'],
135 | ['name' => 'offset', 'type' => Type::int(), 'defaultValue' => 0, 'description' => 'Slice offset'],
136 | ['name' => 'limit', 'type' => Type::int(), 'defaultValue' => 100, 'description' => 'Slice size'],
137 | ],
138 | 'resolve' => $this->searchItems( $domain ),
139 | ],
140 | 'search' . str_replace( '/', '', ucwords( $domain, '/' ) ) . 'Tree' => [
141 | 'type' => Type::listOf( $this->types()->siteOutputType() ),
142 | 'args' => [
143 | ['name' => 'filter', 'type' => Type::string(), 'defaultValue' => '{}', 'description' => 'Filter conditions'],
144 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
145 | ['name' => 'limit', 'type' => Type::int(), 'defaultValue' => 100, 'description' => 'Slice size'],
146 | ],
147 | 'resolve' => $this->searchTree( $domain ),
148 | ]
149 | ];
150 | }
151 |
152 |
153 | /**
154 | * Returns the item if not removed for security reasons
155 | *
156 | * @param \Aimeos\MShop\Common\Item\Iface $item Item to check
157 | * @return \Aimeos\MShop\Common\Item\Iface Item if not removed
158 | */
159 | protected function filter( \Aimeos\MShop\Common\Item\Iface $item ) : \Aimeos\MShop\Common\Item\Iface
160 | {
161 | $siteid = (string) $this->context()->user()?->getSiteId();
162 |
163 | if( $item->getSiteId() && strncmp( $item->getSiteId(), $siteid, strlen( $siteid ) ) ) {
164 | throw new \Aimeos\Admin\Graphql\Exception( 'Forbidden', 403 );
165 | }
166 |
167 | return $item;
168 | }
169 |
170 |
171 | /**
172 | * Returns the items if not removed for security reasons
173 | *
174 | * @param iterable $items List of items to check
175 | * @return iterable List of items not removed
176 | */
177 | protected function filters( iterable $items ) : iterable
178 | {
179 | $list = [];
180 | $siteid = (string) $this->context()->user()?->getSiteId();
181 |
182 | foreach( $items as $id => $item )
183 | {
184 | if( !( $item->getSiteId() && strncmp( $item->getSiteId(), $siteid, strlen( $siteid ) ) ) ) {
185 | $list[$id] = $item;
186 | }
187 | }
188 |
189 | return $list;
190 | }
191 |
192 |
193 | /**
194 | * Returns the tree of parents including the given items as leaf nodes
195 | *
196 | * @param \Aimeos\Map $items List of items (with numeric indexes)
197 | * @param array $refs List of domains to fetch in addition
198 | * @return \Aimeos\Map List of parent items
199 | */
200 | protected function getParents( \Aimeos\Map $items, array $refs ) : \Aimeos\Map
201 | {
202 | if( ( $parentIds = $items->getParentId()->filter() )->isEmpty() ) {
203 | return $items;
204 | }
205 |
206 | $manager = $this->manager();
207 | $filter = $manager->filter()
208 | ->add( 'locale.site.siteid', '=~', (string) $this->context()->user()?->getSiteId() )
209 | ->add( 'locale.site.id', '==', $parentIds->unique() )
210 | ->order( ['-locale.site.level', 'sort:locale.site:position'] )
211 | ->slice( 0, 0x7fffffff );
212 |
213 | $parents = $manager->search( $filter, $refs );
214 | $indexes = $parentIds->unique()->flip();
215 | $itemkeys = $items->getId()->flip();
216 |
217 | foreach( $parents as $pid => $parent )
218 | {
219 | if( isset( $itemkeys[$pid] ) ) {
220 | $items[$itemkeys[$pid]]->addChild( $items[$indexes[$pid]] );
221 | unset( $items[$indexes[$pid]] );
222 | } else {
223 | $items[$indexes[$pid]] = $parent->addChild( $items[$indexes[$pid]] );
224 | }
225 | }
226 |
227 | return $this->getParents( $items, $refs );
228 | }
229 |
230 |
231 | /**
232 | * Returns a closure for returning the nodes from the passed ID up to the root node
233 | *
234 | * @param string $domain Domain path of the manager
235 | * @return \Closure Anonymous method returning one item
236 | */
237 | protected function getPath( string $domain ) : \Closure
238 | {
239 | return function( $root, $args, $context ) use ( $domain ) {
240 | return $this->filters( $this->manager()->getPath( $args['id'], $args['include'] ) );
241 | };
242 | }
243 |
244 |
245 | /**
246 | * Returns a closure for returning the node tree
247 | *
248 | * @param string $domain Domain path of the manager
249 | * @return \Closure Anonymous method returning one item
250 | */
251 | protected function getTree( string $domain ) : \Closure
252 | {
253 | return function( $root, $args, $context ) use ( $domain ) {
254 | return $this->filter( $this->manager()->getTree( $args['id'], $args['include'], $args['level'] ) );
255 | };
256 | }
257 |
258 |
259 | /**
260 | * Returns a closure for inserting a new node into the tree
261 | *
262 | * @param string $domain Domain path of the manager
263 | * @return \Closure Anonymous method returning one item
264 | */
265 | protected function insertItem( string $domain ) : \Closure
266 | {
267 | return function( $root, $args, $context ) use ( $domain ) {
268 |
269 | if( empty( $entry = $args['input'] ) ) {
270 | throw new \Aimeos\Admin\Graphql\Exception( 'Parameter "input" must not be empty' );
271 | }
272 |
273 | $this->access( $domain, 'insert' );
274 | $manager = $this->manager();
275 | $item = $manager->create()->fromArray( $entry, true );
276 |
277 | return $manager->insert( $item, $args['parentid'], $args['refid'] );
278 | };
279 | }
280 |
281 |
282 | /**
283 | * Returns the manager for the site items
284 | *
285 | * @return \Aimeos\MShop\Common\Manager\Iface Manager object
286 | */
287 | protected function manager() : \Aimeos\MShop\Common\Manager\Iface
288 | {
289 | if( !isset( $this->manager ) ) {
290 | $this->manager = \Aimeos\MShop::create( $this->context(), 'locale/site' );
291 | }
292 |
293 | return $this->manager;
294 | }
295 |
296 |
297 | /**
298 | * Returns a closure for moving a node within the tree
299 | *
300 | * @param string $domain Domain path of the manager
301 | * @return \Closure Anonymous method returning one item
302 | */
303 | protected function moveItem( string $domain ) : \Closure
304 | {
305 | return function( $root, $args, $context ) use ( $domain ) {
306 |
307 | $this->access( $domain, 'move' );
308 | $this->manager()->move( $args['id'], $args['parentid'], $args['targetid'], $args['refid'] );
309 |
310 | return $args['id'];
311 | };
312 | }
313 |
314 |
315 | /**
316 | * Returns a closure for returning several items
317 | *
318 | * @param string $domain Domain path of the manager
319 | * @return \Closure Anonymous method returning several items
320 | */
321 | protected function searchItems( string $domain ) : \Closure
322 | {
323 | return function( $root, $args, $context ) use ( $domain ) {
324 |
325 | $this->access( $domain, 'get' );
326 |
327 | $manager = \Aimeos\MShop::create( $this->context(), $domain );
328 | $prefix = str_replace( '/', '.', $domain );
329 |
330 | $filter = $manager->filter()->order( $args['sort'] )->slice( $args['offset'], $args['limit'] );
331 | $filter->add( $prefix . '.siteid', '=~', (string) $this->context()->user()?->getSiteId() );
332 | $filter->add( $filter->parse( json_decode( $args['filter'], true ) ) );
333 |
334 | $total = 0;
335 | $items = $manager->search( $filter, $args['include'], $total )->toArray();
336 |
337 | return [
338 | 'items' => $items,
339 | 'total' => $total
340 | ];
341 | };
342 | }
343 |
344 |
345 | /**
346 | * Returns a closure for searching the tree
347 | *
348 | * @param string $domain Domain path of the manager
349 | * @return \Closure Anonymous method returning one item
350 | */
351 | protected function searchTree( string $domain ) : \Closure
352 | {
353 | return function( $root, $args, $context ) use ( $domain ) {
354 |
355 | $this->access( $domain, 'get' );
356 | $manager = $this->manager();
357 |
358 | $filter = $manager->filter()->order( ['-locale.site.level', 'sort:locale.site:position'] );
359 | $filter->add( 'locale.site.siteid', '=~', (string) $this->context()->user()?->getSiteId() );
360 | $filter->add( $filter->parse( json_decode( $args['filter'], true ) ) );
361 |
362 | $items = $manager->search( $filter->slice( 0, $args['limit'] ), $args['include'] );
363 |
364 | foreach( $items as $key => $item )
365 | {
366 | if( isset( $items[$item->getParentId()] ) ) {
367 | $items[$item->getParentId()]->addChild( $item );
368 | unset( $items[$key] );
369 | }
370 | }
371 |
372 | return $this->getParents( $items->values(), $args['include'] );
373 | };
374 | }
375 |
376 |
377 | /**
378 | * Updates the item
379 | *
380 | * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager object for the passed item
381 | * @param \Aimeos\MShop\Common\Item\AdddressRef\Iface $item Item to update
382 | * @param array $entry Associative list of key/value pairs of the item data
383 | * @return \Aimeos\MShop\Common\Item\Iface Updated item
384 | */
385 | protected function updateItem( \Aimeos\MShop\Common\Manager\Iface $manager,
386 | \Aimeos\MShop\Common\Item\Iface $item, array $entry ) : \Aimeos\MShop\Common\Item\Iface
387 | {
388 | $siteid = (string) $this->context()->user()?->getSiteId();
389 |
390 | if( !$siteid || !$item->getSiteId() || strncmp( $item->getSiteId(), $siteid, strlen( $siteid ) ) ){
391 | throw new \Aimeos\Admin\Graphql\Exception( 'Forbidden', 403 );
392 | }
393 |
394 | return $item->fromArray( $entry, true );
395 | }
396 | }
397 |
--------------------------------------------------------------------------------
/src/Admin/Graphql/Order/Standard.php:
--------------------------------------------------------------------------------
1 | [
40 | 'type' => $this->orderOutputType( $domain ),
41 | 'args' => [
42 | ['name' => 'input', 'type' => $this->orderInputType( $domain ), 'description' => 'Item object'],
43 | ],
44 | 'resolve' => $this->saveItem( $domain ),
45 | ],
46 | 'save' . str_replace( '/', '', ucwords( $domain, '/' ) ) . 's' => [
47 | 'type' => Type::listOf( $this->orderOutputType( $domain ) ),
48 | 'args' => [
49 | ['name' => 'input', 'type' => Type::listOf( $this->orderInputType( $domain ) ), 'description' => 'Item objects'],
50 | ],
51 | 'resolve' => $this->saveItems( $domain ),
52 | ]
53 | ];
54 | }
55 |
56 |
57 | /**
58 | * Returns GraphQL schema definition for the available queries
59 | *
60 | * @param string $domain Domain name of the responsible manager
61 | * @return array GraphQL query schema definition
62 | */
63 | public function query( string $domain ) : array
64 | {
65 | return [
66 | 'aggregate' . str_replace( '/', '', ucwords( $domain, '/' ) ) . 's' => [
67 | 'type' => $this->types()->aggregateOutputType( $domain ),
68 | 'args' => [
69 | ['name' => 'key', 'type' => Type::listOf( Type::string() ), 'description' => 'Aggregation key to group results by, e.g. ["order.status", "order.price"]'],
70 | ['name' => 'value', 'type' => Type::string(), 'defaultValue' => null, 'description' => 'Aggregate values from that column, e.g "order.price" (optional, only if type is passed)'],
71 | ['name' => 'type', 'type' => Type::string(), 'defaultValue' => null, 'description' => 'Type of aggregation like "sum" or "avg" (default: null for count)'],
72 | ['name' => 'filter', 'type' => Type::string(), 'defaultValue' => '{}', 'description' => 'Filter conditions'],
73 | ['name' => 'sort', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Sort keys'],
74 | ['name' => 'limit', 'type' => Type::int(), 'defaultValue' => 10000, 'description' => 'Slice size'],
75 | ],
76 | 'resolve' => $this->aggregateItems( $domain ),
77 | ],
78 | 'get' . str_replace( '/', '', ucwords( $domain, '/' ) ) => [
79 | 'type' => $this->orderOutputType( $domain ),
80 | 'args' => [
81 | ['name' => 'id', 'type' => Type::string(), 'description' => 'Unique ID'],
82 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
83 | ],
84 | 'resolve' => $this->getItem( $domain ),
85 | ],
86 | 'search' . str_replace( '/', '', ucwords( $domain, '/' ) ) . 's' => [
87 | 'type' => $this->types()->searchOutputType( $domain, fn( $path ) => $this->orderOutputType( $path ) ),
88 | 'args' => [
89 | ['name' => 'filter', 'type' => Type::string(), 'defaultValue' => '{}', 'description' => 'Filter conditions'],
90 | ['name' => 'include', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Domains to include'],
91 | ['name' => 'sort', 'type' => Type::listOf( Type::string() ), 'defaultValue' => [], 'description' => 'Sort keys'],
92 | ['name' => 'offset', 'type' => Type::int(), 'defaultValue' => 0, 'description' => 'Slice offset'],
93 | ['name' => 'limit', 'type' => Type::int(), 'defaultValue' => 100, 'description' => 'Slice size'],
94 | ],
95 | 'resolve' => $this->searchItems( $domain ),
96 | ]
97 | ];
98 | }
99 |
100 |
101 | /**
102 | * Defines the GraphQL order input type
103 | *
104 | * @param string $path Path of the domain manager
105 | * @return \GraphQL\Type\Definition\InputObjectType Input type definition
106 | */
107 | public function orderInputType( string $path ) : InputObjectType
108 | {
109 | $name = 'orderInput';
110 |
111 | if( isset( $this->types[$name] ) ) {
112 | return $this->types[$name];
113 | }
114 |
115 | return $this->types[$name] = new InputObjectType( [
116 | 'name' => $name,
117 | 'fields' => function() use ( $path ) {
118 |
119 | $manager = \Aimeos\MShop::create( $this->context(), $path );
120 | $list = $this->types()->fields( $manager->getSearchAttributes( false ) );
121 |
122 | $list['address'] = Type::listOf( $this->types()->inputType( $path . '/address' ) );
123 | $list['product'] = Type::listOf( $this->orderProductInputType( $path . '/product' ) );
124 | $list['service'] = Type::listOf( $this->orderServiceInputType( $path . '/service' ) );
125 |
126 | return $list;
127 | },
128 | 'parseValue' => function( array $values ) use ( $path ) {
129 | return $this->types()->prefix( $path, $values );
130 | }
131 | ] );
132 | }
133 |
134 |
135 | /**
136 | * Defines the GraphQL order product input type
137 | *
138 | * @param string $path Path of the domain manager
139 | * @return \GraphQL\Type\Definition\InputObjectType Input type definition
140 | */
141 | public function orderProductInputType( string $path ) : InputObjectType
142 | {
143 | $name = 'orderProductInput';
144 |
145 | if( isset( $this->types[$name] ) ) {
146 | return $this->types[$name];
147 | }
148 |
149 | return $this->types[$name] = new InputObjectType( [
150 | 'name' => $name,
151 | 'fields' => function() use ( $path ) {
152 |
153 | $manager = \Aimeos\MShop::create( $this->context(), $path );
154 | $list = $this->types()->fields( $manager->getSearchAttributes( false ) );
155 |
156 | $list['product'] = Type::listOf( $this->orderSubProductInputType( $path ) );
157 | $list['attribute'] = Type::listOf( $this->types()->inputType( $path . '/attribute' ) );
158 |
159 | return $list;
160 | },
161 | 'parseValue' => function( array $values ) use ( $path ) {
162 | return $this->types()->prefix( $path, $values );
163 | }
164 | ] );
165 | }
166 |
167 |
168 | /**
169 | * Defines the GraphQL order sub-product input type
170 | *
171 | * @param string $path Path of the domain manager
172 | * @return \GraphQL\Type\Definition\InputObjectType Input type definition
173 | */
174 | public function orderSubProductInputType( string $path ) : InputObjectType
175 | {
176 | $name = 'orderSubProductInput';
177 |
178 | if( isset( $this->types[$name] ) ) {
179 | return $this->types[$name];
180 | }
181 |
182 | return $this->types[$name] = new InputObjectType( [
183 | 'name' => $name,
184 | 'fields' => function() use ( $path ) {
185 |
186 | $manager = \Aimeos\MShop::create( $this->context(), $path );
187 |
188 | $list = $this->types()->fields( $manager->getSearchAttributes( false ) );
189 | $list['attribute'] = Type::listOf( $this->types()->inputType( $path . '/attribute' ) );
190 |
191 | return $list;
192 | },
193 | 'parseValue' => function( array $values ) use ( $path ) {
194 | return $this->types()->prefix( $path, $values );
195 | }
196 | ] );
197 | }
198 |
199 |
200 | /**
201 | * Defines the GraphQL order service input type
202 | *
203 | * @param string $path Path of the domain manager
204 | * @return \GraphQL\Type\Definition\InputObjectType Input type definition
205 | */
206 | public function orderServiceInputType( string $path ) : InputObjectType
207 | {
208 | $name = 'orderServiceInput';
209 |
210 | if( isset( $this->types[$name] ) ) {
211 | return $this->types[$name];
212 | }
213 |
214 | return $this->types[$name] = new InputObjectType( [
215 | 'name' => $name,
216 | 'fields' => function() use ( $path ) {
217 |
218 | $manager = \Aimeos\MShop::create( $this->context(), $path );
219 |
220 | $list = $this->types()->fields( $manager->getSearchAttributes( false ) );
221 | $list['attribute'] = Type::listOf( $this->types()->inputType( $path . '/attribute' ) );
222 | $list['transaction'] = Type::listOf( $this->types()->inputType( $path . '/transaction' ) );
223 |
224 | return $list;
225 | },
226 | 'parseValue' => function( array $values ) use ( $path ) {
227 | return $this->types()->prefix( $path, $values );
228 | }
229 | ] );
230 | }
231 |
232 |
233 | /**
234 | * Defines the GraphQL order output types
235 | *
236 | * @return \GraphQL\Type\Definition\ObjectType Output type definition
237 | */
238 | public function orderOutputType() : ObjectType
239 | {
240 | $name = 'orderOutputType';
241 |
242 | if( isset( $this->types[$name] ) ) {
243 | return $this->types[$name];
244 | }
245 |
246 | return $this->types[$name] = new ObjectType( [
247 | 'name' => $name,
248 | 'fields' => function() {
249 |
250 | $manager = \Aimeos\MShop::create( $this->context(), 'order' );
251 | $list = $this->types()->fields( $manager->getSearchAttributes( false ) );
252 |
253 | $list['address'] = [
254 | 'type' => Type::listOf( $this->orderAddressOutputType() ),
255 | 'resolve' => function( $item ) {
256 | return $item->getAddresses()->flat( 1 );
257 | }
258 | ];
259 |
260 | $list['coupon'] = [
261 | 'type' => Type::listOf( $this->orderCouponOutputType() ),
262 | 'resolve' => function( $item ) {
263 | return $item->getCoupons()->keys()->all();
264 | }
265 | ];
266 |
267 | $list['product'] = [
268 | 'type' => Type::listOf( $this->orderProductOutputType() ),
269 | 'resolve' => function( $item ) {
270 | return $item->getProducts();
271 | }
272 | ];
273 |
274 | $list['service'] = [
275 | 'type' => Type::listOf( $this->orderServiceOutputType() ),
276 | 'resolve' => function( $item ) {
277 | return $item->getServices()->flat( 1 );
278 | }
279 | ];
280 |
281 | $list['status'] = [
282 | 'type' => Type::listOf( $this->orderStatusOutputType() ),
283 | 'resolve' => function( $item ) {
284 | return $item->getStatuses()->flat( 1 );
285 | }
286 | ];
287 |
288 | return $list;
289 | },
290 | 'resolveField' => function( \Aimeos\MShop\Order\Item\Iface $item, array $args, $context, ResolveInfo $info ) {
291 | return $this->types()->resolve( $item, 'order', $info->fieldName );
292 | }
293 | ] );
294 | }
295 |
296 |
297 | /**
298 | * Defines the GraphQL order address output types
299 | *
300 | * @return \GraphQL\Type\Definition\ObjectType Output type definition
301 | */
302 | public function orderAddressOutputType() : ObjectType
303 | {
304 | $name = 'orderAddressOutput';
305 |
306 | if( isset( $this->types[$name] ) ) {
307 | return $this->types[$name];
308 | }
309 |
310 | return $this->types[$name] = new ObjectType( [
311 | 'name' => $name,
312 | 'fields' => function() {
313 | $manager = \Aimeos\MShop::create( $this->context(), 'order/address' );
314 | return $this->types()->fields( $manager->getSearchAttributes( false ) );
315 | },
316 | 'resolveField' => function( \Aimeos\MShop\Order\Item\Address\Iface $item, array $args, $context, ResolveInfo $info ) {
317 | return $this->types()->resolve( $item, 'order/address', $info->fieldName );
318 | }
319 | ] );
320 | }
321 |
322 |
323 | /**
324 | * Defines the GraphQL order coupon output types
325 | *
326 | * @return \GraphQL\Type\Definition\ObjectType Output type definition
327 | */
328 | public function orderCouponOutputType() : ObjectType
329 | {
330 | $name = 'orderCouponOutput';
331 |
332 | if( isset( $this->types[$name] ) ) {
333 | return $this->types[$name];
334 | }
335 |
336 | return $this->types[$name] = new ObjectType( [
337 | 'name' => $name,
338 | 'fields' => function() {
339 | return [
340 | 'code' => [
341 | 'name' => 'code',
342 | 'description' => 'Coupon codes',
343 | 'type' => Type::String(),
344 | ],
345 | ];
346 | },
347 | 'resolveField' => function( $codes, array $args, $context, ResolveInfo $info ) {
348 | return (string) $codes;
349 | }
350 | ] );
351 | }
352 |
353 |
354 | /**
355 | * Defines the GraphQL order product output types
356 | *
357 | * @return \GraphQL\Type\Definition\ObjectType Output type definition
358 | */
359 | public function orderProductOutputType() : ObjectType
360 | {
361 | $name = 'orderProductOutput';
362 |
363 | if( isset( $this->types[$name] ) ) {
364 | return $this->types[$name];
365 | }
366 |
367 | return $this->types[$name] = new ObjectType( [
368 | 'name' => $name,
369 | 'fields' => function() {
370 | $manager = \Aimeos\MShop::create( $this->context(), 'order/product' );
371 | $list = $this->types()->fields( $manager->getSearchAttributes( false ) );
372 |
373 | $list['product'] = [
374 | 'type' => Type::listOf( $this->orderSubProductOutputType() ),
375 | 'resolve' => function( $item ) {
376 | return $item->getProducts();
377 | }
378 | ];
379 |
380 | $list['attribute'] = [
381 | 'type' => Type::listOf( $this->orderProductAttributeOutputType() ),
382 | 'args' => [
383 | 'type' => Type::String(),
384 | ],
385 | 'resolve' => function( $item, $args ) {
386 | return $item->getAttributeItems( $args['type'] ?? null );
387 | }
388 | ];
389 |
390 | return $list;
391 | },
392 | 'resolveField' => function( \Aimeos\MShop\Order\Item\Product\Iface $item, array $args, $context, ResolveInfo $info ) {
393 | return $this->types()->resolve( $item, 'order/product', $info->fieldName );
394 | }
395 | ] );
396 | }
397 |
398 |
399 | /**
400 | * Defines the GraphQL order sub-product output types
401 | *
402 | * @return \GraphQL\Type\Definition\ObjectType Output type definition
403 | */
404 | public function orderSubProductOutputType() : ObjectType
405 | {
406 | $name = 'orderSubProductOutput';
407 |
408 | if( isset( $this->types[$name] ) ) {
409 | return $this->types[$name];
410 | }
411 |
412 | return $this->types[$name] = new ObjectType( [
413 | 'name' => $name,
414 | 'fields' => function() {
415 | $manager = \Aimeos\MShop::create( $this->context(), 'order/product' );
416 | $list = $this->types()->fields( $manager->getSearchAttributes( false ) );
417 |
418 | $list['attribute'] = [
419 | 'type' => Type::listOf( $this->orderProductAttributeOutputType() ),
420 | 'args' => [
421 | 'type' => Type::String(),
422 | ],
423 | 'resolve' => function( $item, $args ) {
424 | return $item->getAttributeItems( $args['type'] ?? null );
425 | }
426 | ];
427 |
428 | return $list;
429 | },
430 | 'resolveField' => function( \Aimeos\MShop\Order\Item\Product\Iface $item, array $args, $context, ResolveInfo $info ) {
431 | return $this->types()->resolve( $item, 'order/product', $info->fieldName );
432 | }
433 | ] );
434 | }
435 |
436 |
437 | /**
438 | * Defines the GraphQL order product attribute output types
439 | *
440 | * @return \GraphQL\Type\Definition\ObjectType Output type definition
441 | */
442 | public function orderProductAttributeOutputType() : ObjectType
443 | {
444 | $name = 'orderProductAttributeOutput';
445 |
446 | if( isset( $this->types[$name] ) ) {
447 | return $this->types[$name];
448 | }
449 |
450 | return $this->types[$name] = new ObjectType( [
451 | 'name' => $name,
452 | 'fields' => function() {
453 | $manager = \Aimeos\MShop::create( $this->context(), 'order/product/attribute' );
454 | return $this->types()->fields( $manager->getSearchAttributes( false ) );
455 | },
456 | 'resolveField' => function( \Aimeos\MShop\Order\Item\Product\Attribute\Iface $item, array $args, $context, ResolveInfo $info ) {
457 | return $this->types()->resolve( $item, 'order/product/attribute', $info->fieldName );
458 | }
459 | ] );
460 | }
461 |
462 |
463 | /**
464 | * Defines the GraphQL order service output types
465 | *
466 | * @return \GraphQL\Type\Definition\ObjectType Output type definition
467 | */
468 | public function orderServiceOutputType() : ObjectType
469 | {
470 | $name = 'orderServiceOutput';
471 |
472 | if( isset( $this->types[$name] ) ) {
473 | return $this->types[$name];
474 | }
475 |
476 | return $this->types[$name] = new ObjectType( [
477 | 'name' => $name,
478 | 'fields' => function() {
479 | $manager = \Aimeos\MShop::create( $this->context(), 'order/service' );
480 | $list = $this->types()->fields( $manager->getSearchAttributes( false ) );
481 |
482 | $list['attribute'] = [
483 | 'type' => Type::listOf( $this->orderServiceAttributeOutputType() ),
484 | 'args' => [
485 | 'type' => Type::String(),
486 | ],
487 | 'resolve' => function( $item, $args ) {
488 | return $item->getAttributeItems( $args['type'] ?? null );
489 | }
490 | ];
491 |
492 | $list['transaction'] = [
493 | 'type' => Type::listOf( $this->orderServiceTransactionOutputType() ),
494 | 'args' => [
495 | 'type' => Type::String(),
496 | ],
497 | 'resolve' => function( $item, $args ) {
498 | return $item->getTransactions( $args['type'] ?? null );
499 | }
500 | ];
501 |
502 | return $list;
503 | },
504 | 'resolveField' => function( \Aimeos\MShop\Order\Item\Service\Iface $item, array $args, $context, ResolveInfo $info ) {
505 | return $this->types()->resolve( $item, 'order/service', $info->fieldName );
506 | }
507 | ] );
508 | }
509 |
510 |
511 | /**
512 | * Defines the GraphQL order service attribute output types
513 | *
514 | * @return \GraphQL\Type\Definition\ObjectType Output type definition
515 | */
516 | public function orderServiceAttributeOutputType() : ObjectType
517 | {
518 | $name = 'orderServiceAttributeOutput';
519 |
520 | if( isset( $this->types[$name] ) ) {
521 | return $this->types[$name];
522 | }
523 |
524 | return $this->types[$name] = new ObjectType( [
525 | 'name' => $name,
526 | 'fields' => function() {
527 | $manager = \Aimeos\MShop::create( $this->context(), 'order/service/attribute' );
528 | return $this->types()->fields( $manager->getSearchAttributes( false ) );
529 | },
530 | 'resolveField' => function( \Aimeos\MShop\Order\Item\Service\Attribute\Iface $item, array $args, $context, ResolveInfo $info ) {
531 | return $this->types()->resolve( $item, 'order/service/attribute', $info->fieldName );
532 | }
533 | ] );
534 | }
535 |
536 |
537 | /**
538 | * Defines the GraphQL order service transaction output types
539 | *
540 | * @return \GraphQL\Type\Definition\ObjectType Output type definition
541 | */
542 | public function orderServiceTransactionOutputType() : ObjectType
543 | {
544 | $name = 'orderServiceTransactionOutput';
545 |
546 | if( isset( $this->types[$name] ) ) {
547 | return $this->types[$name];
548 | }
549 |
550 | return $this->types[$name] = new ObjectType( [
551 | 'name' => $name,
552 | 'fields' => function() {
553 | $manager = \Aimeos\MShop::create( $this->context(), 'order/service/transaction' );
554 | return $this->types()->fields( $manager->getSearchAttributes( false ) );
555 | },
556 | 'resolveField' => function( \Aimeos\MShop\Order\Item\Service\Transaction\Iface $item, array $args, $context, ResolveInfo $info ) {
557 | return $this->types()->resolve( $item, 'order/service/transaction', $info->fieldName );
558 | }
559 | ] );
560 | }
561 |
562 |
563 | /**
564 | * Defines the GraphQL order status output types
565 | *
566 | * @return \GraphQL\Type\Definition\ObjectType Output type definition
567 | */
568 | public function orderStatusOutputType() : ObjectType
569 | {
570 | $name = 'orderStatusOutput';
571 |
572 | if( isset( $this->types[$name] ) ) {
573 | return $this->types[$name];
574 | }
575 |
576 | return $this->types[$name] = new ObjectType( [
577 | 'name' => $name,
578 | 'fields' => function() {
579 | $manager = \Aimeos\MShop::create( $this->context(), 'order/status' );
580 | return $this->types()->fields( $manager->getSearchAttributes( false ) );
581 | },
582 | 'resolveField' => function( \Aimeos\MShop\Order\Item\Status\Iface $item, array $args, $context, ResolveInfo $info ) {
583 | return $this->types()->resolve( $item, 'order/status', $info->fieldName );
584 | }
585 | ] );
586 | }
587 | }
588 |
--------------------------------------------------------------------------------