├── src ├── Resources │ ├── public │ │ ├── .gitkeep │ │ └── administration │ │ │ └── css │ │ │ └── saas-connect.css │ ├── app │ │ ├── administration │ │ │ ├── test │ │ │ │ └── unit │ │ │ │ │ ├── __mocks__ │ │ │ │ │ ├── style.mock.js │ │ │ │ │ ├── template.mock.js │ │ │ │ │ ├── module.mock.js │ │ │ │ │ ├── components.mock.js │ │ │ │ │ └── http.factory.js │ │ │ │ │ ├── .eslintrc.js │ │ │ │ │ ├── index.test.js │ │ │ │ │ ├── jest-setup │ │ │ │ │ └── setup-shopware.js │ │ │ │ │ └── __fixtures__ │ │ │ │ │ └── app-system │ │ │ │ │ ├── app-url-change.fixtures.js │ │ │ │ │ └── action-buttons.fixtures.js │ │ │ ├── src │ │ │ │ ├── main.js │ │ │ │ ├── module │ │ │ │ │ ├── sw-my-apps │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ └── sw-my-apps-timeout-animation │ │ │ │ │ │ │ │ ├── sw-my-apps-timeout-animation.scss │ │ │ │ │ │ │ │ ├── sw-my-apps-timeout-animation.html │ │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ ├── page │ │ │ │ │ │ │ └── sw-my-apps-page │ │ │ │ │ │ │ │ ├── sw-my-apps-page.scss │ │ │ │ │ │ │ │ └── sw-my-apps-page.html.twig │ │ │ │ │ │ └── index.js │ │ │ │ │ └── index.js │ │ │ │ ├── snippets │ │ │ │ │ └── index.js │ │ │ │ ├── extension │ │ │ │ │ ├── app │ │ │ │ │ │ ├── component │ │ │ │ │ │ │ └── structure │ │ │ │ │ │ │ │ ├── sw-desktop │ │ │ │ │ │ │ │ ├── sw-desktop.html.twig │ │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ │ │ ├── sw-page │ │ │ │ │ │ │ │ ├── sw-page.scss │ │ │ │ │ │ │ │ └── sw-page.html.twig │ │ │ │ │ │ │ │ └── sw-admin-menu │ │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── index.js │ │ │ │ │ └── module │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ └── sw-settings-custom-field │ │ │ │ │ │ └── page │ │ │ │ │ │ └── sw-settings-custom-field-set-list │ │ │ │ │ │ └── index.js │ │ │ │ ├── app │ │ │ │ │ ├── component │ │ │ │ │ │ └── sw-connect-action-button │ │ │ │ │ │ │ ├── sw-connect-action-button.scss │ │ │ │ │ │ │ ├── sw-connect-action-button.html.twig │ │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── index.js │ │ │ │ │ └── state │ │ │ │ │ │ └── connect-apps.state.js │ │ │ │ └── core │ │ │ │ │ ├── index.js │ │ │ │ │ └── service │ │ │ │ │ ├── api │ │ │ │ │ └── app-modules.service.js │ │ │ │ │ └── index.js │ │ │ ├── .eslintrc.js │ │ │ ├── babel.config.js │ │ │ └── package.json │ │ ├── e2e │ │ │ ├── fixtures │ │ │ │ ├── themeApp │ │ │ │ │ ├── Resources │ │ │ │ │ │ ├── app │ │ │ │ │ │ │ └── storefront │ │ │ │ │ │ │ │ ├── src │ │ │ │ │ │ │ │ ├── scss │ │ │ │ │ │ │ │ │ ├── overrides.scss │ │ │ │ │ │ │ │ │ └── base.scss │ │ │ │ │ │ │ │ ├── main.js │ │ │ │ │ │ │ │ └── example-plugin │ │ │ │ │ │ │ │ │ └── example-plugin.js │ │ │ │ │ │ │ │ └── dist │ │ │ │ │ │ │ │ └── storefront │ │ │ │ │ │ │ │ └── js │ │ │ │ │ │ │ │ └── swag-theme.js │ │ │ │ │ │ ├── views │ │ │ │ │ │ │ └── storefront │ │ │ │ │ │ │ │ └── layout │ │ │ │ │ │ │ │ └── header │ │ │ │ │ │ │ │ └── logo.html.twig │ │ │ │ │ │ └── theme.json │ │ │ │ │ ├── icon.png │ │ │ │ │ └── manifest.xml │ │ │ │ ├── productApp │ │ │ │ │ └── icon.png │ │ │ │ └── product.json │ │ │ ├── babel.config.js │ │ │ ├── .eslintrc.js │ │ │ ├── cli-tools │ │ │ │ ├── example.rest │ │ │ │ ├── view │ │ │ │ │ └── show-product-app.html │ │ │ │ └── actions │ │ │ │ │ ├── app-registration-service.js │ │ │ │ │ ├── reset-db.js │ │ │ │ │ └── clear-cache.js │ │ │ ├── plugins │ │ │ │ └── index.js │ │ │ ├── support │ │ │ │ ├── commands │ │ │ │ │ ├── action-button-commands.js │ │ │ │ │ ├── system-commands.js │ │ │ │ │ ├── navigation.js │ │ │ │ │ ├── theme-commands.js │ │ │ │ │ ├── app-management-commands.js │ │ │ │ │ └── create-product-command.js │ │ │ │ └── index.js │ │ │ ├── integration │ │ │ │ └── administration │ │ │ │ │ └── general │ │ │ │ │ └── sales-channel-set.spec.js │ │ │ ├── cypress.json │ │ │ └── package.json │ │ └── common │ │ │ └── .eslintrc.js │ ├── config │ │ ├── routes.xml │ │ └── services_test.xml │ └── views │ │ └── administration │ │ └── index.html.twig ├── Core │ ├── Command │ │ ├── Exception │ │ │ └── UserAbortedCommandException.php │ │ ├── ShopwareSaasStyle.php │ │ └── VerifyManifestCommand.php │ ├── Content │ │ └── App │ │ │ ├── Exception │ │ │ ├── AppRegistrationException.php │ │ │ ├── InvalidArgumentException.php │ │ │ ├── SaasConnectException.php │ │ │ ├── CustomFieldTypeNotFoundException.php │ │ │ └── ActionNotFoundException.php │ │ │ ├── Lifecycle │ │ │ ├── Event │ │ │ │ ├── AppUpdatedEvent.php │ │ │ │ ├── AppActivatedEvent.php │ │ │ │ ├── AppDeactivatedEvent.php │ │ │ │ ├── AppInstalledEvent.php │ │ │ │ ├── ManifestChangedEvent.php │ │ │ │ ├── AppChangedEvent.php │ │ │ │ └── AppDeletedEvent.php │ │ │ ├── Registration │ │ │ │ ├── AppHandshakeInterface.php │ │ │ │ ├── StoreHandshake.php │ │ │ │ └── HandshakeFactory.php │ │ │ ├── AppLoaderInterface.php │ │ │ ├── Persister │ │ │ │ ├── PermissionGatewayStrategy.php │ │ │ │ └── PermissionGatewayFactory.php │ │ │ ├── AppLifecycleInterface.php │ │ │ └── AppLoader.php │ │ │ ├── Manifest │ │ │ └── Xml │ │ │ │ ├── CustomFieldTypes │ │ │ │ ├── MultiSelectField.php │ │ │ │ ├── MediaSelectionField.php │ │ │ │ ├── BoolField.php │ │ │ │ ├── ColorPickerField.php │ │ │ │ ├── CustomFieldTypeFactory.php │ │ │ │ ├── DateTimeField.php │ │ │ │ ├── TextAreaField.php │ │ │ │ └── TextField.php │ │ │ │ ├── Webhooks.php │ │ │ │ ├── CustomFields.php │ │ │ │ ├── Setup.php │ │ │ │ ├── Permissions.php │ │ │ │ └── Webhook.php │ │ │ ├── AppCollection.php │ │ │ ├── Aggregate │ │ │ ├── ActionButton │ │ │ │ └── ActionButtonCollection.php │ │ │ ├── AppTranslation │ │ │ │ ├── AppTranslationCollection.php │ │ │ │ └── AppTranslationDefinition.php │ │ │ └── ActionButtonTranslation │ │ │ │ ├── ActionButtonTranslationCollection.php │ │ │ │ └── ActionButtonTranslationDefinition.php │ │ │ ├── Subscriber │ │ │ └── AppLoadedSubscriber.php │ │ │ ├── AppService.php │ │ │ └── Action │ │ │ └── Executor.php │ ├── Framework │ │ ├── AppUrlChangeResolver │ │ │ ├── NoAppUrlChangeDetectedException.php │ │ │ ├── AppUrlChangeResolverInterface.php │ │ │ └── AppUrlChangeResolverNotFoundException.php │ │ ├── ShopId │ │ │ ├── AppUrlChangeDetectedException.php │ │ │ └── ShopIdProvider.php │ │ ├── Webhook │ │ │ ├── Hookable.php │ │ │ ├── WebhookCollection.php │ │ │ ├── WebhookCacheClearer.php │ │ │ ├── WebhookDefinition.php │ │ │ └── WebhookEntity.php │ │ ├── Template │ │ │ ├── TemplateLoaderInterface.php │ │ │ ├── TemplateCollection.php │ │ │ ├── TemplateLoader.php │ │ │ └── TemplateEntity.php │ │ └── Api │ │ │ ├── AppUrlChangeResolverNotFoundHttpException.php │ │ │ └── Acl │ │ │ └── AclPrivilegeCollection.php │ └── System │ │ └── CustomField │ │ └── Aggregate │ │ └── CustomFieldSet │ │ └── CustomFieldSetExtension.php ├── Storefront │ └── Theme │ │ └── StorefrontPluginConfiguration │ │ ├── StorefrontPluginConfigurationAppFactoryInterface.php │ │ └── StorefrontPluginConfigurationAppFactory.php ├── Migration │ ├── Migration1592313163DropAppAccessToken.php │ ├── Migration1594883800AddPrivacyToApp.php │ ├── Migration1594115469AddActiveColumnForApps.php │ ├── Migration1580907311ExtendCustomFieldSet.php │ ├── Migration1581948516Template.php │ ├── Migration1581087930Webhook.php │ └── Migration1597392859RenameAppIdColumn.php └── SaasConnect.php ├── tests ├── Core │ ├── Command │ │ └── _fixtures │ │ │ ├── empty │ │ │ └── .gitkeep │ │ │ ├── withoutPermissions │ │ │ └── manifest.xml │ │ │ ├── registrationFailure │ │ │ └── manifest.xml │ │ │ └── withPermissions │ │ │ └── manifest.xml │ ├── System │ │ └── Snippet │ │ │ └── File │ │ │ └── _fixtures │ │ │ ├── SnippetsWithWrongName │ │ │ ├── Resources │ │ │ │ └── snippet │ │ │ │ │ ├── en-GB.json │ │ │ │ │ └── test.json │ │ │ └── manifest.xml │ │ │ ├── AppWithSnippets │ │ │ ├── Resources │ │ │ │ └── snippet │ │ │ │ │ ├── storefront.de-DE.json │ │ │ │ │ └── storefront.en-GB.json │ │ │ └── manifest.xml │ │ │ └── AppWithBaseSnippets │ │ │ ├── Resources │ │ │ └── snippet │ │ │ │ ├── storefront.de-DE.base.json │ │ │ │ └── storefront.en-GB.base.json │ │ │ └── manifest.xml │ ├── Framework │ │ ├── Plugin │ │ │ └── _fixtures │ │ │ │ ├── with-webpack │ │ │ │ ├── Resources │ │ │ │ │ └── app │ │ │ │ │ │ └── storefront │ │ │ │ │ │ └── build │ │ │ │ │ │ └── webpack.config.js │ │ │ │ ├── icon.png │ │ │ │ └── manifest.xml │ │ │ │ └── theme │ │ │ │ ├── Resources │ │ │ │ ├── app │ │ │ │ │ └── storefront │ │ │ │ │ │ └── src │ │ │ │ │ │ ├── scss │ │ │ │ │ │ ├── overrides.scss │ │ │ │ │ │ └── base.scss │ │ │ │ │ │ ├── main.js │ │ │ │ │ │ └── example-plugin │ │ │ │ │ │ └── example-plugin.js │ │ │ │ └── views │ │ │ │ │ ├── administration │ │ │ │ │ └── index.html.twig │ │ │ │ │ └── storefront │ │ │ │ │ └── layout │ │ │ │ │ └── header │ │ │ │ │ └── logo.html.twig │ │ │ │ └── icon.png │ │ └── Webhook │ │ │ ├── _fixtures │ │ │ └── BusinessEvents │ │ │ │ ├── BusinessEventEncoderTestInterface.php │ │ │ │ ├── InvalidEventType.php │ │ │ │ ├── InvalidTypeBusinessEvent.php │ │ │ │ ├── InvalidAvailableDataBusinessEvent.php │ │ │ │ └── UnstructuredObjectBusinessEvent.php │ │ │ └── WebhookCacheClearerTest.php │ └── Content │ │ └── App │ │ ├── Manifest │ │ ├── _fixtures │ │ │ ├── test │ │ │ │ ├── icon.png │ │ │ │ └── Resources │ │ │ │ │ └── views │ │ │ │ │ ├── storefront │ │ │ │ │ └── layout │ │ │ │ │ │ └── header │ │ │ │ │ │ └── logo.html.twig │ │ │ │ │ └── administration │ │ │ │ │ └── index.html.twig │ │ │ ├── invalid │ │ │ │ └── manifest.xml │ │ │ ├── minimal │ │ │ │ └── manifest.xml │ │ │ └── public │ │ │ │ └── manifest.xml │ │ ├── Xml │ │ │ ├── CustomFieldTypes │ │ │ │ ├── CustomFieldTypeFactoryTest.php │ │ │ │ └── _fixtures │ │ │ │ │ ├── bool-field.xml │ │ │ │ │ ├── date-time-field.xml │ │ │ │ │ ├── color-picker-field.xml │ │ │ │ │ ├── media-selection-field.xml │ │ │ │ │ ├── text-field.xml │ │ │ │ │ └── text-area-field.xml │ │ │ ├── PermissionTest.php │ │ │ ├── CustomFieldsTest.php │ │ │ ├── MetadataTest.php │ │ │ └── WebhooksTest.php │ │ └── ManifestTest.php │ │ └── Lifecycle │ │ ├── Registration │ │ ├── StoreHandshakeTest.php │ │ └── _fixtures │ │ │ ├── minimal │ │ │ └── manifest.xml │ │ │ └── no-setup │ │ │ └── manifest.xml │ │ ├── Event │ │ ├── AppDeletedEventTest.php │ │ ├── AppActivatedEventTest.php │ │ ├── AppDeactivatedEventTest.php │ │ ├── AppUpdatedEventTest.php │ │ └── AppInstalledEventTest.php │ │ └── Persister │ │ └── PermissionGatewayFactoryTest.php ├── Storefront │ └── _fixtures │ │ ├── theme │ │ ├── Resources │ │ │ ├── app │ │ │ │ └── storefront │ │ │ │ │ └── src │ │ │ │ │ └── scss │ │ │ │ │ ├── overrides.scss │ │ │ │ │ └── base.scss │ │ │ ├── views │ │ │ │ ├── administration │ │ │ │ │ └── index.html.twig │ │ │ │ └── storefront │ │ │ │ │ └── layout │ │ │ │ │ └── header │ │ │ │ │ └── logo.html.twig │ │ │ └── theme.json │ │ ├── icon.png │ │ └── manifest.xml │ │ ├── noThemeNoCss │ │ ├── icon.png │ │ ├── Resources │ │ │ └── views │ │ │ │ ├── storefront │ │ │ │ └── layout │ │ │ │ │ └── header │ │ │ │ │ └── logo.html.twig │ │ │ │ └── administration │ │ │ │ └── index.html.twig │ │ └── manifest.xml │ │ └── noThemeCustomCss │ │ ├── icon.png │ │ ├── Resources │ │ ├── app │ │ │ └── storefront │ │ │ │ └── src │ │ │ │ └── scss │ │ │ │ └── base.scss │ │ └── views │ │ │ ├── administration │ │ │ └── index.html.twig │ │ │ └── storefront │ │ │ └── layout │ │ │ └── header │ │ │ └── logo.html.twig │ │ └── manifest.xml ├── TestBundle.php ├── TestKernel.php ├── GuzzleHistoryCollector.php ├── SystemConfigTestBehaviour.php ├── TemplateSystemIntegrationTest.php ├── TwigResetCacheTestBehaviour.php ├── EnvTestBehaviour.php ├── AppSystemTestBehaviour.php ├── TestBootstrap.php └── GuzzleTestClientBehaviour.php ├── dev-ops ├── tools │ ├── vendor-bin │ │ ├── psalm │ │ │ └── composer.json │ │ ├── phpstan │ │ │ └── composer.json │ │ ├── ecs │ │ │ └── composer.json │ │ └── phpinsights │ │ │ └── composer.json │ ├── Dockerfile │ └── composer.json └── gitlab │ └── cypress.env.json ├── SECURITY.md ├── .gitignore ├── phpstan.neon ├── composer.json └── LICENSE /src/Resources/public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/Core/Command/_fixtures/empty/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Resources/app/administration/test/unit/__mocks__/style.mock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /tests/Core/System/Snippet/File/_fixtures/SnippetsWithWrongName/Resources/snippet/en-GB.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/Core/System/Snippet/File/_fixtures/SnippetsWithWrongName/Resources/snippet/test.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Resources/app/administration/test/unit/__mocks__/template.mock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /tests/Core/System/Snippet/File/_fixtures/AppWithSnippets/Resources/snippet/storefront.de-DE.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/Core/System/Snippet/File/_fixtures/AppWithSnippets/Resources/snippet/storefront.en-GB.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/fixtures/themeApp/Resources/app/storefront/src/scss/overrides.scss: -------------------------------------------------------------------------------- 1 | $body-bg: red; 2 | -------------------------------------------------------------------------------- /tests/Core/Framework/Plugin/_fixtures/with-webpack/Resources/app/storefront/build/webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/Core/System/Snippet/File/_fixtures/AppWithBaseSnippets/Resources/snippet/storefront.de-DE.base.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/Core/System/Snippet/File/_fixtures/AppWithBaseSnippets/Resources/snippet/storefront.en-GB.base.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Resources/app/administration/test/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['jest'], 3 | }; 4 | -------------------------------------------------------------------------------- /tests/Core/Framework/Plugin/_fixtures/theme/Resources/app/storefront/src/scss/overrides.scss: -------------------------------------------------------------------------------- 1 | $body-bg: red; 2 | -------------------------------------------------------------------------------- /tests/Storefront/_fixtures/theme/Resources/app/storefront/src/scss/overrides.scss: -------------------------------------------------------------------------------- 1 | $sw-color-brand-primary: #ffff; 2 | -------------------------------------------------------------------------------- /dev-ops/tools/vendor-bin/psalm/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "vimeo/psalm": "^3.8" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/main.js: -------------------------------------------------------------------------------- 1 | import SaasConnect from './core'; 2 | 3 | SaasConnect.install(Shopware); 4 | -------------------------------------------------------------------------------- /src/Resources/app/administration/test/unit/__mocks__/module.mock.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | return []; 3 | }; 4 | -------------------------------------------------------------------------------- /dev-ops/tools/vendor-bin/phpstan/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "phpstan/phpstan": "^0.12.7" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/Resources/app/administration/test/unit/__mocks__/components.mock.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | return []; 3 | }; 4 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-env', 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /dev-ops/tools/vendor-bin/ecs/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "symplify/easy-coding-standard": "^7.2" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /dev-ops/tools/vendor-bin/phpinsights/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "nunomaduro/phpinsights": "^1.11" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/fixtures/themeApp/Resources/app/storefront/src/scss/base.scss: -------------------------------------------------------------------------------- 1 | .product-box { 2 | background-color: #000000; 3 | } 4 | -------------------------------------------------------------------------------- /tests/Core/Framework/Plugin/_fixtures/theme/Resources/app/storefront/src/scss/base.scss: -------------------------------------------------------------------------------- 1 | .product-box { 2 | background-color: #000000; 3 | } 4 | -------------------------------------------------------------------------------- /tests/Storefront/_fixtures/theme/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shopwareArchive/app-system/HEAD/tests/Storefront/_fixtures/theme/icon.png -------------------------------------------------------------------------------- /src/Resources/app/e2e/fixtures/themeApp/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shopwareArchive/app-system/HEAD/src/Resources/app/e2e/fixtures/themeApp/icon.png -------------------------------------------------------------------------------- /tests/Storefront/_fixtures/noThemeNoCss/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shopwareArchive/app-system/HEAD/tests/Storefront/_fixtures/noThemeNoCss/icon.png -------------------------------------------------------------------------------- /src/Resources/app/e2e/fixtures/productApp/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shopwareArchive/app-system/HEAD/src/Resources/app/e2e/fixtures/productApp/icon.png -------------------------------------------------------------------------------- /tests/Core/Framework/Plugin/_fixtures/theme/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shopwareArchive/app-system/HEAD/tests/Core/Framework/Plugin/_fixtures/theme/icon.png -------------------------------------------------------------------------------- /tests/Storefront/_fixtures/noThemeCustomCss/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shopwareArchive/app-system/HEAD/tests/Storefront/_fixtures/noThemeCustomCss/icon.png -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/_fixtures/test/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shopwareArchive/app-system/HEAD/tests/Core/Content/App/Manifest/_fixtures/test/icon.png -------------------------------------------------------------------------------- /src/Resources/app/administration/src/module/sw-my-apps/components/sw-my-apps-timeout-animation/sw-my-apps-timeout-animation.scss: -------------------------------------------------------------------------------- 1 | .sw-my-apps-timeout-animation { 2 | 3 | } -------------------------------------------------------------------------------- /tests/Core/Framework/Plugin/_fixtures/with-webpack/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shopwareArchive/app-system/HEAD/tests/Core/Framework/Plugin/_fixtures/with-webpack/icon.png -------------------------------------------------------------------------------- /dev-ops/gitlab/cypress.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": "http", 3 | "host": "docker.vm", 4 | "port": 8000, 5 | "locale": "en-GB", 6 | "cliProxy": { 7 | "port": 8005 8 | } 9 | } -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Reporting a vulnerability 2 | 3 | Visit our [issue tracker](https://issues.shopware.com/createissue) to report security vulnerabilities. 4 | Please make sure to use the issue type "Security Issue". 5 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/module/sw-my-apps/components/sw-my-apps-timeout-animation/sw-my-apps-timeout-animation.html: -------------------------------------------------------------------------------- 1 |
2 | Loading your app resulted in a timeout 3 |
-------------------------------------------------------------------------------- /tests/Storefront/_fixtures/theme/Resources/app/storefront/src/scss/base.scss: -------------------------------------------------------------------------------- 1 | .light{ 2 | font-weight:200; 3 | } 4 | 5 | .regular{ 6 | font-weight:400; 7 | } 8 | 9 | .bold{ 10 | font-weight:700; 11 | } 12 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/snippets/index.js: -------------------------------------------------------------------------------- 1 | export function installSnippets(Shopware) { 2 | Shopware.Locale.extend('de-DE', require('./de-DE')); 3 | Shopware.Locale.extend('en-GB', require('./en-GB')); 4 | } 5 | -------------------------------------------------------------------------------- /src/Core/Command/Exception/UserAbortedCommandException.php: -------------------------------------------------------------------------------- 1 | 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /tests/Storefront/_fixtures/noThemeNoCss/Resources/views/storefront/layout/header/logo.html.twig: -------------------------------------------------------------------------------- 1 | {% sw_extends '@Storefront/storefront/layout/header/logo.html.twig' %} 2 | 3 | {% block layout_header_logo_inner %} 4 | {{ parent() }} 5 | Built with <3 on Shopware as a Service 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /src/Core/Content/App/Exception/SaasConnectException.php: -------------------------------------------------------------------------------- 1 | 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /tests/Core/Framework/Plugin/_fixtures/theme/Resources/views/storefront/layout/header/logo.html.twig: -------------------------------------------------------------------------------- 1 | {% sw_extends '@Storefront/storefront/layout/header/logo.html.twig' %} 2 | 3 | {% block layout_header_logo_inner %} 4 | {{ parent() }} 5 | Built with <3 on Shopware as a Service 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /tests/Storefront/_fixtures/noThemeCustomCss/Resources/views/administration/index.html.twig: -------------------------------------------------------------------------------- 1 | {% sw_extends 'administration/index.html.twig' %} 2 | 3 | {% block administration_scripts %} 4 | {{ parent() }} 5 | 6 | 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /tests/Storefront/_fixtures/noThemeCustomCss/Resources/views/storefront/layout/header/logo.html.twig: -------------------------------------------------------------------------------- 1 | {% sw_extends '@Storefront/storefront/layout/header/logo.html.twig' %} 2 | 3 | {% block layout_header_logo_inner %} 4 | {{ parent() }} 5 | Built with <3 on Shopware as a Service 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /tests/Storefront/_fixtures/noThemeNoCss/Resources/views/administration/index.html.twig: -------------------------------------------------------------------------------- 1 | {% sw_extends 'administration/index.html.twig' %} 2 | 3 | {% block administration_scripts %} 4 | {{ parent() }} 5 | 6 | 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /tests/Storefront/_fixtures/theme/Resources/views/storefront/layout/header/logo.html.twig: -------------------------------------------------------------------------------- 1 | {% sw_extends '@Storefront/storefront/layout/header/logo.html.twig' %} 2 | 3 | {% block layout_header_logo_inner %} 4 | {{ parent() }} 5 | Built with <3 as a theme on Shopware as a Service 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/module/sw-my-apps/components/sw-my-apps-timeout-animation/index.js: -------------------------------------------------------------------------------- 1 | import template from './sw-my-apps-timeout-animation.html'; 2 | import './sw-my-apps-timeout-animation.scss'; 3 | 4 | export default { 5 | name: 'sw-my-apps-timeout-animation', 6 | template, 7 | }; 8 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/_fixtures/test/Resources/views/administration/index.html.twig: -------------------------------------------------------------------------------- 1 | {% sw_extends 'administration/index.html.twig' %} 2 | 3 | {% block administration_scripts %} 4 | {{ parent() }} 5 | 6 | 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/extension/app/component/structure/sw-desktop/sw-desktop.html.twig: -------------------------------------------------------------------------------- 1 | {% block sw_desktop_content %} 2 | {% parent %} 3 | 4 | {% endblock %} 5 | -------------------------------------------------------------------------------- /src/Resources/app/administration/test/unit/index.test.js: -------------------------------------------------------------------------------- 1 | describe('index.js', () => { 2 | test('It registers services after install method', () => { 3 | expect(Shopware.Service('AppActionButtonService')).toBeDefined(); 4 | expect(Shopware.Service('AppUrlChangeService')).toBeDefined(); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/_fixtures/invalid/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/extension/index.js: -------------------------------------------------------------------------------- 1 | import { overrideComponents } from './app'; 2 | import { installModuleExtensions } from './module'; 3 | 4 | function extendAdministration(Shopware) { 5 | overrideComponents(Shopware); 6 | installModuleExtensions(Shopware); 7 | } 8 | 9 | export default extendAdministration; 10 | -------------------------------------------------------------------------------- /tests/Core/Framework/Webhook/_fixtures/BusinessEvents/BusinessEventEncoderTestInterface.php: -------------------------------------------------------------------------------- 1 | .sw-context-button >.sw-button { 7 | padding: 11px; 8 | 9 | .sw-icon { 10 | color: $color-menu-start; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Core/Content/App/Lifecycle/Event/AppUpdatedEvent.php: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/extension/app/index.js: -------------------------------------------------------------------------------- 1 | export function overrideComponents(Shopware) { 2 | const { Component } = Shopware; 3 | const context = require.context('./component/', true, /\.js$/); 4 | 5 | return context.keys().forEach((item) => { 6 | const component = context(item).default; 7 | Component.override(component.name, component, 0); 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /tests/Storefront/_fixtures/theme/Resources/theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SwagTheme", 3 | "author": "Shopware AG", 4 | "views": [ 5 | "@Storefront", 6 | "@Plugins", 7 | "@SwagTheme" 8 | ], 9 | "style": [ 10 | "app/storefront/src/scss/overrides.scss", 11 | "@Storefront", 12 | "app/storefront/src/scss/base.scss" 13 | ], 14 | "script": [ 15 | "@Storefront" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/Core/Framework/AppUrlChangeResolver/NoAppUrlChangeDetectedException.php: -------------------------------------------------------------------------------- 1 | 'invalid', 13 | ]; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/fixtures/themeApp/Resources/theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SwagTheme", 3 | "author": "Shopware AG", 4 | "views": [ 5 | "@Storefront", 6 | "@Plugins", 7 | "@SwagTheme" 8 | ], 9 | "style": [ 10 | "app/storefront/src/scss/overrides.scss", 11 | "@Storefront", 12 | "app/storefront/src/scss/base.scss" 13 | ], 14 | "script": [ 15 | "@Storefront", 16 | "app/storefront/dist/storefront/js/swag-theme.js" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/Core/Content/App/Manifest/Xml/CustomFieldTypes/MultiSelectField.php: -------------------------------------------------------------------------------- 1 | confirm($question, $default)) { 12 | throw $exception; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/fixtures/themeApp/Resources/app/storefront/src/example-plugin/example-plugin.js: -------------------------------------------------------------------------------- 1 | import Plugin from 'src/plugin-system/plugin.class'; 2 | 3 | export default class ExamplePlugin extends Plugin { 4 | init() { 5 | window.onscroll = function() { 6 | if ((window.innerHeight + window.pageYOffset) >= document.body.offsetHeight) { 7 | alert('seems like there\'s nothing more to see here.'); 8 | } 9 | }; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/Core/Framework/Plugin/_fixtures/theme/Resources/app/storefront/src/example-plugin/example-plugin.js: -------------------------------------------------------------------------------- 1 | import Plugin from 'src/plugin-system/plugin.class'; 2 | 3 | export default class ExamplePlugin extends Plugin { 4 | init() { 5 | window.onscroll = function() { 6 | if ((window.innerHeight + window.pageYOffset) >= document.body.offsetHeight) { 7 | alert('seems like there\'s nothing more to see here.'); 8 | } 9 | }; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/extension/module/sw-settings-custom-field/page/sw-settings-custom-field-set-list/index.js: -------------------------------------------------------------------------------- 1 | const { Criteria } = Shopware.Data; 2 | 3 | export default { 4 | 'name': 'sw-settings-custom-field-set-list', 5 | 6 | computed: { 7 | listingCriteria() { 8 | const criteria = this.$super('listingCriteria'); 9 | criteria.addFilter(Criteria.equals('appId', null)); 10 | 11 | return criteria; 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/Resources/app/administration/test/unit/__mocks__/http.factory.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import AxiosMockAdapter from 'axios-mock-adapter'; 3 | 4 | const shopwareHttpFactory = require(path.join(administrationCorePath, 'core/factory/http.factory')).default; 5 | 6 | export default function createHTTPClient(context) { 7 | const client = shopwareHttpFactory(context); 8 | Shopware.Application.getContainer('service').mockAdapter = new AxiosMockAdapter(client); 9 | 10 | return client; 11 | } 12 | -------------------------------------------------------------------------------- /src/Core/Content/App/Exception/CustomFieldTypeNotFoundException.php: -------------------------------------------------------------------------------- 1 | { 4 | const url = `${config.env.schema}://${config.env.host}`; 5 | 6 | config.baseUrl = `${url}:${config.env.port}`; 7 | config.cliProxyUrl = `${url}:${config.env.cliProxy.port}`; 8 | }; 9 | 10 | module.exports = (on, config) => { 11 | shopwareE2ePlugin(on, config); 12 | setUpUrls(on, config); 13 | 14 | return config; 15 | }; 16 | -------------------------------------------------------------------------------- /src/Core/Content/App/Lifecycle/AppLoaderInterface.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | public function load(): array; 17 | 18 | public function getIcon(Manifest $app): ?string; 19 | } 20 | -------------------------------------------------------------------------------- /src/Resources/public/administration/css/saas-connect.css: -------------------------------------------------------------------------------- 1 | .smart-bar__app_actions{margin-right:8px}.smart-bar__app_actions>.sw-context-button>.sw-button{padding:11px}.smart-bar__app_actions>.sw-context-button>.sw-button .sw-icon{color:#303a4f}.sw-connect-action-button .sw-connect-action-button__icon{width:16px;height:16px;border:none}.sw-connect-action-button .sw-icon{margin-left:4px;vertical-align:baseline}.sw-connect-action-button .sw-icon svg{vertical-align:baseline}.sw-my-apps-page .sw-my-apps-page__app-content{width:100%;height:100%;border:none} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore ide settings 2 | .idea/ 3 | .vscode 4 | 5 | # ignore test output 6 | .phpunit.result.cache 7 | **/app/artifacts 8 | 9 | # ignore dependency folders 10 | vendor 11 | /composer.lock 12 | node_modules 13 | package-lock.json 14 | 15 | #ignore bundle output 16 | /build 17 | src/Resources/app/administration/build/** 18 | !tests/Core/Framework/Plugin/_fixtures/with-webpack/Resources/app/storefront/build/webpack.config.js 19 | 20 | #ignore caches 21 | .php_cs.cache 22 | 23 | cypress.env.json 24 | !dev-ops/gitlab/cypress.env.json 25 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/fixtures/product.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "product as a service", 3 | "stock": 1, 4 | "productNumber": "SWC-1234", 5 | "descriptionLong": "product description", 6 | "price": [ 7 | { 8 | "currencyId": "b7d2554b0ce847cd82f3ac9bd1c0dfca", 9 | "net": 42, 10 | "linked": false, 11 | "gross": 64 12 | } 13 | ], 14 | "product_manufacturer": { 15 | "name": "Cloud Team" 16 | }, 17 | "tax": { 18 | "name": "luxury", 19 | "taxRate": 45 20 | } 21 | } -------------------------------------------------------------------------------- /src/Resources/views/administration/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'administration/index.html.twig' %} 2 | 3 | {% block administration_stylesheets %} 4 | {{ parent() }} 5 | 6 | 7 | {% endblock %} 8 | 9 | {% block administration_scripts %} 10 | {{ parent() }} 11 | 12 | 13 | {% endblock %} -------------------------------------------------------------------------------- /src/Resources/app/e2e/support/commands/action-button-commands.js: -------------------------------------------------------------------------------- 1 | function actionButtons() { 2 | cy.get('.sw-page div.smart-bar__content .sw-context-button.sw-page__connect-action-buttons').then((contextButton) => { 3 | if (!contextButton.hasClass('is--active')) { 4 | cy.wrap(contextButton).click(); 5 | } 6 | 7 | return cy.get('.sw-context-button__menu-popover'); 8 | }); 9 | } 10 | 11 | export default function addActionButtonCommands(Cypress) { 12 | Cypress.Commands.add('actionButtons', { prevSubject: false }, actionButtons); 13 | }; 14 | -------------------------------------------------------------------------------- /tests/TestBundle.php: -------------------------------------------------------------------------------- 1 | load('services_test.xml'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/TestKernel.php: -------------------------------------------------------------------------------- 1 | $app 14 | */ 15 | public function update(Manifest $manifest, array $app, Context $context): void; 16 | 17 | /** 18 | * @param array $app 19 | */ 20 | public function delete(string $appName, array $app, Context $context): void; 21 | } 22 | -------------------------------------------------------------------------------- /src/Core/Framework/Template/TemplateLoaderInterface.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | public function getTemplatePathsForApp(Manifest $app): array; 19 | 20 | /** 21 | * Returns the content of the template 22 | */ 23 | public function getTemplateContent(string $path, Manifest $app): string; 24 | } 25 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/Xml/CustomFieldTypes/CustomFieldTypeFactoryTest.php: -------------------------------------------------------------------------------- 1 | executeUpdate(' 18 | ALTER TABLE `saas_app` 19 | DROP COLUMN `access_token` 20 | '); 21 | } 22 | 23 | public function updateDestructive(Connection $connection): void 24 | { 25 | // nth 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/integration/administration/general/sales-channel-set.spec.js: -------------------------------------------------------------------------------- 1 | describe('general', () => { 2 | it('uses ', () => { 3 | cy.setLocaleToEnGb(); 4 | cy.server(); 5 | cy.login('admin'); 6 | 7 | cy.get('.sw-admin-menu__sales-channel-item--1 > a').contains('Storefront').click(); 8 | 9 | cy.get('.sw-card') 10 | .contains('div.sw-card__title', 'Domains') 11 | .parent('.sw-card') 12 | .within(() => { 13 | cy.inspectDataGrid() 14 | .findRowsWith('URL', Cypress.config('baseUrl')) 15 | .should('exist'); 16 | }); 17 | 18 | cy.visit('/'); 19 | cy.title().should('equal', 'Catalogue #1'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /tests/Core/System/Snippet/File/_fixtures/AppWithSnippets/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | AppWithSnippet 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | MIT 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/Core/System/Snippet/File/_fixtures/AppWithBaseSnippets/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | AppWithBaseSnippet 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | MIT 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/GuzzleHistoryCollector.php: -------------------------------------------------------------------------------- 1 | executeUpdate(' 18 | ALTER TABLE `saas_app` 19 | ADD COLUMN `privacy` VARCHAR(255) NULL AFTER `license`; 20 | '); 21 | } 22 | 23 | public function updateDestructive(Connection $connection): void 24 | { 25 | // nth 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Core/System/Snippet/File/_fixtures/SnippetsWithWrongName/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SnippetsWithWrongName 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | MIT 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "integrationFolder": "./integration", 3 | "fixturesFolder": "./fixtures", 4 | "pluginsFile": "./plugins/index.js", 5 | "supportFile": "./support/index.js", 6 | 7 | "viewportHeight": 1080, 8 | "viewportWidth": 1920, 9 | "watchForFileChanges": false, 10 | "requestTimeout": 10000, 11 | "responseTimeout": 60000, 12 | "defaultCommandTimeout": 10000, 13 | "useDarkTheme": false, 14 | "video": false, 15 | "useShopwareTheme": true, 16 | "theme": "dark", 17 | 18 | "reporter": "junit", 19 | "screenshotsFolder": "../build/artifacts/e2e/screenshots", 20 | "reporterOptions": { 21 | "mochaFile": "../build/artifacts/e2e/saas-administration-e2e.xml", 22 | "toConsole": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/cli-tools/view/show-product-app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |

Hi this is your product service

10 | 11 |
12 | 13 | 14 | 15 | 16 |
17 | 18 | 23 | 24 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swag/saas-connect", 3 | "authors": [ 4 | { 5 | "name": "shopware AG" 6 | } 7 | ], 8 | "description": "A webhook based plugin system", 9 | "type": "shopware-platform-plugin", 10 | "license": "MIT", 11 | "autoload": { 12 | "psr-4": { 13 | "Swag\\SaasConnect\\": "src/" 14 | } 15 | }, 16 | "autoload-dev": { 17 | "psr-4": { 18 | "Swag\\SaasConnect\\Test\\": "tests/" 19 | } 20 | }, 21 | "extra": { 22 | "shopware-plugin-class": "Swag\\SaasConnect\\SaasConnect", 23 | "label": { 24 | "de-DE": "Cloud-Plugins", 25 | "en-GB": "Cloud plugins" 26 | } 27 | }, 28 | "require-dev": { 29 | "opis/json-schema": "^1.0.18" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Core/Content/App/AppCollection.php: -------------------------------------------------------------------------------- 1 | getIterator() 13 | * @method array getElements() 14 | * @method AppEntity|null get(string $key) 15 | * @method AppEntity|null first() 16 | * @method AppEntity|null last() 17 | */ 18 | class AppCollection extends EntityCollection 19 | { 20 | protected function getExpectedClass(): string 21 | { 22 | return AppEntity::class; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/app/component/sw-connect-action-button/sw-connect-action-button.html.twig: -------------------------------------------------------------------------------- 1 | {% block sw_connect_action_button %} 2 | 8 | 9 | 13 | 14 | 15 | {{ buttonLabel }} 16 | 17 | 18 | 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/ManifestTest.php: -------------------------------------------------------------------------------- 1 | getPath()); 15 | } 16 | 17 | public function testSetPath(): void 18 | { 19 | $manifest = Manifest::createFromXmlFile(__DIR__ . '/_fixtures/test/manifest.xml'); 20 | 21 | $manifest->setPath('test'); 22 | static::assertEquals('test', $manifest->getPath()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Migration/Migration1594115469AddActiveColumnForApps.php: -------------------------------------------------------------------------------- 1 | executeUpdate(' 18 | ALTER TABLE `saas_app` 19 | ADD COLUMN `active` tinyint(1) default 0 not null after `acl_role_id` 20 | '); 21 | } 22 | 23 | public function updateDestructive(Connection $connection): void 24 | { 25 | // implement update destructive 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/app/index.js: -------------------------------------------------------------------------------- 1 | export function installComponents(Shopware) { 2 | const { Component } = Shopware; 3 | const context = require.context('./component/', true, /\.js$/); 4 | 5 | context.keys().forEach((item) => { 6 | const component = context(item).default; 7 | 8 | Component.register(component.name, component); 9 | }); 10 | } 11 | 12 | export function addStateModules(Shopware) { 13 | const { State } = Shopware; 14 | const states = ['connect-apps']; 15 | 16 | states.forEach((moduleName) => { 17 | const state = require(`./state/${moduleName}.state`).default; 18 | State.registerModule(moduleName, state); 19 | }); 20 | } 21 | 22 | export function initializeAppModules() { 23 | return Shopware.State.dispatch('connect-apps/fetchAppModules'); 24 | } 25 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/support/commands/system-commands.js: -------------------------------------------------------------------------------- 1 | function cleanUpPreviousState() { 2 | const proxyUrl = Cypress.config('cliProxyUrl'); 3 | return cy.request({ 4 | method: 'DELETE', 5 | url: `${proxyUrl}/cleanup`, 6 | Headers: { 7 | 'content-type': 'application/json', 8 | accept: 'application/json', 9 | }, 10 | }).should((res) => { 11 | const body = JSON.parse(res.body); 12 | const dbName = body.message.match(/.* --port='\d+' '(.*)'.*/)[1]; 13 | expect(res.status).to.equal(200); 14 | expect(body.code).to.equal(0); 15 | expect(dbName).to.equal('shopware_e2e'); 16 | }); 17 | } 18 | 19 | export default function addSystemCommands(Cypress) { 20 | Cypress.Commands.overwrite('cleanUpPreviousState', cleanUpPreviousState); 21 | }; 22 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/module/index.js: -------------------------------------------------------------------------------- 1 | export function installModules(Shopware) { 2 | const { Module, Component } = Shopware; 3 | const modules = ['sw-my-apps']; 4 | 5 | modules.forEach((moduleName) => { 6 | const module = require(`./${moduleName}`).default; 7 | 8 | if (module.components) { 9 | Object.keys(module.components).forEach((componentName) => { 10 | const component = module.components[componentName]; 11 | 12 | if (component.extendsFrom) { 13 | Component.extend(componentName, component.extendsFrom, component); 14 | return; 15 | } 16 | 17 | Component.register(componentName, component); 18 | }); 19 | } 20 | 21 | Module.register(module.name, module); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /src/Core/Framework/Webhook/WebhookCollection.php: -------------------------------------------------------------------------------- 1 | getIterator() 13 | * @method array getElements() 14 | * @method WebhookEntity|null get(string $key) 15 | * @method WebhookEntity|null first() 16 | * @method WebhookEntity|null last() 17 | */ 18 | class WebhookCollection extends EntityCollection 19 | { 20 | protected function getExpectedClass(): string 21 | { 22 | return WebhookEntity::class; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/extension/app/component/structure/sw-desktop/index.js: -------------------------------------------------------------------------------- 1 | import template from './sw-desktop.html.twig'; 2 | 3 | const { Service } = Shopware; 4 | 5 | /** 6 | * @private 7 | */ 8 | export default { 9 | name: 'sw-desktop', 10 | template, 11 | 12 | data() { 13 | return { 14 | urlDiff: null, 15 | }; 16 | }, 17 | 18 | computed: { 19 | appUrlChangeService() { 20 | return Service('AppUrlChangeService'); 21 | }, 22 | }, 23 | 24 | async created() { 25 | await this.updateShowUrlChangedModal(); 26 | }, 27 | 28 | methods: { 29 | async updateShowUrlChangedModal() { 30 | this.urlDiff = await this.appUrlChangeService.getUrlDiff(); 31 | }, 32 | 33 | closeModal() { 34 | this.urlDiff = null; 35 | }, 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Lifecycle/Registration/StoreHandshakeTest.php: -------------------------------------------------------------------------------- 1 | assembleRequest(); 15 | } 16 | 17 | public function testGetHandshakeFetchAppProofIsUnimplemented(): void 18 | { 19 | $storeHandshake = new StoreHandshake(); 20 | static::expectException(\RuntimeException::class); 21 | $storeHandshake->fetchAppProof(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/fixtures/themeApp/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagTheme 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | icon.png 14 | 15 | 16 | __PROXY_URL__/SwagTheme/registration 17 | s3cr3t 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Core/Framework/Api/AppUrlChangeResolverNotFoundHttpException.php: -------------------------------------------------------------------------------- 1 | getMessage(), [], $previous); 14 | } 15 | 16 | public function getStatusCode(): int 17 | { 18 | return Response::HTTP_BAD_REQUEST; 19 | } 20 | 21 | public function getErrorCode(): string 22 | { 23 | return 'SAAS_CONNECT__APP_URL_CHANGE_RESOLVER_NOT_FOUND'; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Core/Framework/Template/TemplateCollection.php: -------------------------------------------------------------------------------- 1 | getIterator() 13 | * @method array getElements() 14 | * @method TemplateEntity|null get(string $key) 15 | * @method TemplateEntity|null first() 16 | * @method TemplateEntity|null last() 17 | */ 18 | class TemplateCollection extends EntityCollection 19 | { 20 | protected function getExpectedClass(): string 21 | { 22 | return TemplateEntity::class; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Core/Framework/Webhook/WebhookCacheClearer.php: -------------------------------------------------------------------------------- 1 | dispatcher = $dispatcher; 17 | } 18 | 19 | /** 20 | * @return array 21 | */ 22 | public static function getSubscribedEvents(): array 23 | { 24 | return [ 25 | 'saas_webhook.written' => 'clearWebhookCache', 26 | ]; 27 | } 28 | 29 | public function clearWebhookCache(): void 30 | { 31 | $this->dispatcher->clearInternalCache(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | import addAppManagementCommands from './commands/app-management-commands'; 2 | import addNavigationCommands from './commands/navigation'; 3 | import addSystemCommands from './commands/system-commands'; 4 | import addDataGridCommands from './commands/data-grid-commands'; 5 | import addActionButtonCommands from './commands/action-button-commands'; 6 | import addProductCommand from './commands/create-product-command'; 7 | import addThemeCommands from './commands/theme-commands'; 8 | 9 | Cypress.Cookies.defaults({ 10 | whitelist: ['sw-admin-locale'], 11 | }); 12 | 13 | require('@shopware-ag/e2e-testsuite-platform/cypress/support'); 14 | 15 | addSystemCommands(Cypress); 16 | addAppManagementCommands(Cypress); 17 | addNavigationCommands(Cypress); 18 | addDataGridCommands(Cypress); 19 | addActionButtonCommands(Cypress); 20 | addProductCommand(Cypress); 21 | addThemeCommands(Cypress); 22 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Lifecycle/Registration/_fixtures/minimal/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagTestApp 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | MIT 14 | 15 | 16 | https://my.app.com/registration 17 | s3cr3t 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/_fixtures/minimal/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagAppMinimal 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | MIT 14 | 15 | 16 | https://my.app.com/SwagAppMinimal/registration 17 | s3cr3t 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/_fixtures/public/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagAppPrivate 6 | 7 | 8 | Test for private App authentication 9 | Test für die private App Authentifizierung 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | MIT 14 | 15 | 16 | https://my.app.com/SwagAppPrivate/registration 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/Storefront/_fixtures/theme/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagTheme 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | icon.png 14 | MIT 15 | 16 | 17 | https://my.app.com/SwagTheme/registration 18 | s3cr3t 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/Core/Framework/Plugin/_fixtures/with-webpack/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagTest 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | icon.png 14 | MIT 15 | 16 | 17 | https://my.app.com/SwagTest/registration 18 | s3cr3t 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/Storefront/_fixtures/noThemeNoCss/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagNoThemeNoCss 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | icon.png 14 | MIT 15 | 16 | 17 | https://my.app.com/SwagNoThemeNoCss/registration 18 | s3cr3t 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Migration/Migration1580907311ExtendCustomFieldSet.php: -------------------------------------------------------------------------------- 1 | executeUpdate(' 18 | ALTER TABLE `custom_field_set` 19 | ADD COLUMN `saas_app_id` BINARY(16) NULL AFTER `active`, 20 | ADD CONSTRAINT `fk.custom_field_set.saas_app_id` FOREIGN KEY (`saas_app_id`) REFERENCES `saas_app` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; 21 | '); 22 | } 23 | 24 | public function updateDestructive(Connection $connection): void 25 | { 26 | // nth 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Storefront/_fixtures/noThemeCustomCss/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagNoThemeCustomCss 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | icon.png 14 | MIT 15 | 16 | 17 | https://my.app.com/SwagNoThemeCustomCss/registration 18 | s3cr3t 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Core/Content/App/Aggregate/ActionButton/ActionButtonCollection.php: -------------------------------------------------------------------------------- 1 | getIterator() 13 | * @method array getElements() 14 | * @method ActionButtonEntity|null get(string $key) 15 | * @method ActionButtonEntity|null first() 16 | * @method ActionButtonEntity|null last() 17 | */ 18 | class ActionButtonCollection extends EntityCollection 19 | { 20 | protected function getExpectedClass(): string 21 | { 22 | return ActionButtonEntity::class; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/core/service/api/app-modules.service.js: -------------------------------------------------------------------------------- 1 | const serviceName = 'AppModulesService'; 2 | 3 | export default class AppModulesService{ 4 | static get name() { 5 | return serviceName; 6 | } 7 | 8 | constructor(httpClient, loginService) { 9 | this.httpClient = httpClient; 10 | this.loginService = loginService; 11 | this.name = serviceName; 12 | } 13 | 14 | get basicHeaders() { 15 | return { 16 | 'Content-Type': 'application/json', 17 | Accept: 'application/json', 18 | Authorization: `Bearer ${this.loginService.getToken()}`, 19 | }; 20 | } 21 | 22 | fetchAppModules() { 23 | return this.httpClient.get( 24 | 'app-system/modules', 25 | { 26 | headers: this.basicHeaders, 27 | }, 28 | ).then(({ data }) => { 29 | return data.modules || []; 30 | }); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/extension/app/component/structure/sw-page/sw-page.html.twig: -------------------------------------------------------------------------------- 1 | {% block sw_page_smart_bar_content_actions %} 2 | {% block sw_page_smart_bar_content_app_actions %} 3 |
4 | 5 | 10 | 11 | 16 | 17 | 18 |
19 | {% endblock %} 20 | 21 | {% parent() %} 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /tests/Core/Framework/Webhook/_fixtures/BusinessEvents/InvalidTypeBusinessEvent.php: -------------------------------------------------------------------------------- 1 | add('invalid', new InvalidEventType()); 15 | } 16 | 17 | public function getName(): string 18 | { 19 | return 'test'; 20 | } 21 | 22 | public function getContext(): Context 23 | { 24 | return Context::createDefaultContext(); 25 | } 26 | 27 | public function getInvalid(): string 28 | { 29 | return 'invalid'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Core/Framework/Webhook/_fixtures/BusinessEvents/InvalidAvailableDataBusinessEvent.php: -------------------------------------------------------------------------------- 1 | add('invalid', new ScalarValueType(ScalarValueType::TYPE_STRING)); 16 | } 17 | 18 | public function getName(): string 19 | { 20 | return 'test'; 21 | } 22 | 23 | public function getContext(): Context 24 | { 25 | return Context::createDefaultContext(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/SystemConfigTestBehaviour.php: -------------------------------------------------------------------------------- 1 | getContainer()->get(SystemConfigService::class); 19 | 20 | // reset internal system config cache 21 | $reflection = new \ReflectionClass($systemConfigService); 22 | 23 | $property = $reflection->getProperty('configs'); 24 | $property->setAccessible(true); 25 | $property->setValue($systemConfigService, []); 26 | } 27 | 28 | abstract protected function getContainer(): ContainerInterface; 29 | } 30 | -------------------------------------------------------------------------------- /src/Core/Content/App/Aggregate/AppTranslation/AppTranslationCollection.php: -------------------------------------------------------------------------------- 1 | getIterator() 13 | * @method array getElements() 14 | * @method AppTranslationEntity|null get(string $key) 15 | * @method AppTranslationEntity|null first() 16 | * @method AppTranslationEntity|null last() 17 | */ 18 | class AppTranslationCollection extends EntityCollection 19 | { 20 | protected function getExpectedClass(): string 21 | { 22 | return AppTranslationEntity::class; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/Xml/PermissionTest.php: -------------------------------------------------------------------------------- 1 | getPermissions()); 15 | static::assertCount(6, $manifest->getPermissions()->getPermissions()); 16 | static::assertEquals([ 17 | 'product' => ['create', 'update', 'delete'], 18 | 'category' => ['delete'], 19 | 'product_manufacturer' => ['create', 'delete'], 20 | 'tax' => ['create'], 21 | 'language' => ['read'], 22 | 'custom_field_set' => ['update'], 23 | ], $manifest->getPermissions()->getPermissions()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Core/Content/App/Subscriber/AppLoadedSubscriber.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | public static function getSubscribedEvents(): array 15 | { 16 | return [ 17 | 'saas_app.loaded' => 'unserialize', 18 | ]; 19 | } 20 | 21 | public function unserialize(EntityLoadedEvent $event): void 22 | { 23 | /** @var AppEntity $app */ 24 | foreach ($event->getEntities() as $app) { 25 | $iconRaw = $app->getIconRaw(); 26 | 27 | if ($iconRaw !== null) { 28 | $app->setIcon(base64_encode($iconRaw)); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/cli-tools/actions/app-registration-service.js: -------------------------------------------------------------------------------- 1 | import {createHmac} from 'crypto'; 2 | 3 | /** 4 | * @module actions/AppRegistrationService 5 | */ 6 | /** 7 | * Creates an AppRegistrationService object 8 | * @param {string} secret 9 | * @param {string} confirmationUrl 10 | */ 11 | export default function AppRegistrationService(secret, confirmationUrl) { 12 | this.secret = secret; 13 | this.cliProxyUrl = confirmationUrl; 14 | } 15 | 16 | AppRegistrationService.prototype = { 17 | 18 | /** 19 | * Generates a 20 | * @param {string} name 21 | * @param {string} shop 22 | * @return {{confirmation_url: string, proof: PromiseLike | *, secret: string}} 23 | */ 24 | registerApp(name, shop) { 25 | const hmac = createHmac('sha256', this.secret); 26 | hmac.update(shop + name); 27 | 28 | return { 29 | proof: hmac.digest('hex'), 30 | secret: 'dont_tell', 31 | confirmation_url: this.cliProxyUrl 32 | }; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /tests/TemplateSystemIntegrationTest.php: -------------------------------------------------------------------------------- 1 | loadAppsFromDir(__DIR__ . '/Core/Content/App/Manifest/_fixtures/test'); 23 | 24 | $homepage = $this->request('GET', '/', []); 25 | 26 | static::assertEquals(200, $homepage->getStatusCode()); 27 | static::assertStringContainsString('Built with <3 on Shopware as a Service', $homepage->getContent()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/TwigResetCacheTestBehaviour.php: -------------------------------------------------------------------------------- 1 | getContainer() 17 | ->get(Environment::class); 18 | 19 | $reflection = new \ReflectionClass($twigEnv); 20 | $prop = $reflection->getProperty('loadedTemplates'); 21 | 22 | $prop->setAccessible(true); 23 | $prop->setValue($twigEnv, null); 24 | 25 | $reflection = new \ReflectionClass($twigEnv); 26 | $prop = $reflection->getProperty('templateClassPrefix'); 27 | 28 | $prop->setAccessible(true); 29 | $prop->setValue($twigEnv, '__TwigTemplate_' . Uuid::randomHex() . '_'); 30 | } 31 | 32 | abstract protected function getContainer(): ContainerInterface; 33 | } 34 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/module/sw-my-apps/page/sw-my-apps-page/sw-my-apps-page.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 27 | -------------------------------------------------------------------------------- /tests/EnvTestBehaviour.php: -------------------------------------------------------------------------------- 1 | $value) { 12 | if (!array_key_exists($envVar, $this->originalEnvVars)) { 13 | $this->originalEnvVars[$envVar] = $_SERVER[$envVar]; 14 | } 15 | $_SERVER[$envVar] = $value; 16 | $_ENV[$envVar] = $value; 17 | putenv($envVar . '=' . $value); 18 | } 19 | } 20 | 21 | /** 22 | * @after 23 | */ 24 | public function resetEnvVars(): void 25 | { 26 | if ($this->originalEnvVars) { 27 | foreach ($this->originalEnvVars as $envVar => $value) { 28 | $_SERVER[$envVar] = $value; 29 | $_ENV[$envVar] = $value; 30 | putenv($envVar . '=' . $value); 31 | } 32 | 33 | $this->originalEnvVars = []; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 shopware AG 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 5 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 6 | persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 12 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 13 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 14 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /tests/AppSystemTestBehaviour.php: -------------------------------------------------------------------------------- 1 | getContainer()->get('saas_app.repository'), 21 | new AppLoader($appDir) 22 | ), 23 | $this->getContainer()->get(AppLifecycle::class) 24 | ); 25 | 26 | $appService->refreshApps($activateApps, Context::createDefaultContext()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Core/Content/App/Aggregate/ActionButtonTranslation/ActionButtonTranslationCollection.php: -------------------------------------------------------------------------------- 1 | getIterator() 13 | * @method array getElements() 14 | * @method ActionButtonTranslationEntity|null get(string $key) 15 | * @method ActionButtonTranslationEntity|null first() 16 | * @method ActionButtonTranslationEntity|null last() 17 | */ 18 | class ActionButtonTranslationCollection extends EntityCollection 19 | { 20 | protected function getExpectedClass(): string 21 | { 22 | return ActionButtonTranslationEntity::class; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Core/Framework/Webhook/WebhookCacheClearerTest.php: -------------------------------------------------------------------------------- 1 | 'clearWebhookCache', 16 | ], WebhookCacheClearer::getSubscribedEvents()); 17 | } 18 | 19 | public function testClearWebhookCache(): void 20 | { 21 | /** @var MockObject $dispatcherMock */ 22 | $dispatcherMock = $this->createMock(WebhookDispatcher::class); 23 | $dispatcherMock->expects(static::once()) 24 | ->method('clearInternalCache'); 25 | 26 | $cacheClearer = new WebhookCacheClearer($dispatcherMock); 27 | $cacheClearer->clearWebhookCache(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/core/service/index.js: -------------------------------------------------------------------------------- 1 | import AppActionButtonService from './api/app-action-button.service'; 2 | import AppModulesService from './api/app-modules.service'; 3 | import AppAppUrlChangeService from './api/app-url-change.service'; 4 | 5 | function installServices(Shopware) { 6 | Shopware.Application.addServiceProvider(AppActionButtonService.name, () => { 7 | const init = Shopware.Application.getContainer('init'); 8 | return new AppActionButtonService(init.httpClient, Shopware.Service('loginService')); 9 | }); 10 | 11 | Shopware.Application.addServiceProvider(AppModulesService.name, () => { 12 | const init = Shopware.Application.getContainer('init'); 13 | return new AppModulesService(init.httpClient, Shopware.Service('loginService')); 14 | }); 15 | 16 | Shopware.Application.addServiceProvider(AppAppUrlChangeService.name, () => { 17 | const init = Shopware.Application.getContainer('init'); 18 | return new AppAppUrlChangeService(init.httpClient, Shopware.Service('loginService')); 19 | }); 20 | } 21 | 22 | export default installServices; 23 | -------------------------------------------------------------------------------- /src/Resources/app/common/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['import'], 3 | 4 | rules: { 5 | 'use-isnan': ['error'], 6 | 'curly': ['error', 'all'], 7 | 'array-callback-return': ['error'], 8 | 'arrow-parens': ['error', 'always'], 9 | 'quotes': ['error', 'single'], 10 | 'no-unused-vars': ['error'], 11 | 'no-console': ['error', { allow: ["error"]}], 12 | 'comma-dangle': ['error', 'always-multiline'], 13 | 'no-multiple-empty-lines': ['error', { max: 1 }], 14 | 'padded-blocks': ['error', 'never'], 15 | 'semi': ['error', 'always'], 16 | 'no-useless-return': ['error'], 17 | 'keyword-spacing': ['error'], 18 | 19 | 'import/no-useless-path-segments': ['warn', { noUselessIndex: true }], 20 | 'max-len': [ 'warn', 125, { 'ignoreRegExpLiterals': true } ], 21 | 'consistent-return': ['warn'], 22 | 'eol-last': ['warn'], 23 | 'array-bracket-spacing': ['warn', 'never'], 24 | 'object-curly-spacing': ['warn', 'always'], 25 | 'comma-spacing': ['warn', {"before": false, "after": true}], 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/support/commands/navigation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Navigates to a module in the administration 3 | * @param {string} mainNavigation 4 | * @param {string} subNavigation 5 | */ 6 | function navigateAdministration(mainNavigation, subNavigation) { 7 | cy.get('aside.sw-admin-menu').within(($adminMenu) => { 8 | // ensure admin menu is expanded 9 | if ($adminMenu.hasClass('is--collapsed')) { 10 | cy.get('button.sw-admin-menu__toggle').click(); 11 | } 12 | }); 13 | 14 | cy.get('li.sw-admin-menu__navigation-list-item').contains(mainNavigation).click(); 15 | 16 | if (subNavigation) { 17 | cy.get('li.sw-admin-menu__navigation-list-item').contains(subNavigation).click(); 18 | } 19 | } 20 | 21 | /** 22 | * Opens settings module in administration 23 | */ 24 | function openSettings() { 25 | cy.navigateAdministration('Settings'); 26 | } 27 | 28 | export default function addNavigationCommands(Cypress) { 29 | Cypress.Commands.add('navigateAdministration', { prevSubject: false }, navigateAdministration); 30 | Cypress.Commands.add('openSettings', { prevSubject: false }, openSettings); 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/Core/Content/App/Manifest/Xml/CustomFieldTypes/MediaSelectionField.php: -------------------------------------------------------------------------------- 1 | > $data 11 | */ 12 | private function __construct(array $data) 13 | { 14 | foreach ($data as $property => $value) { 15 | $this->$property = $value; 16 | } 17 | } 18 | 19 | public static function fromXml(\DOMElement $element): CustomFieldType 20 | { 21 | return new self(self::parse($element)); 22 | } 23 | 24 | /** 25 | * @return array> 26 | */ 27 | protected function toEntityArray(): array 28 | { 29 | return [ 30 | 'type' => CustomFieldTypes::TEXT, 31 | 'config' => [ 32 | 'componentName' => 'sw-media-field', 33 | 'customFieldType' => 'media', 34 | ], 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "saasconnecte2etestsuite", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "description": "E2E testsuite for Shopware SaasConnect", 6 | "public": false, 7 | "license": "MIT", 8 | "author": "shopware AG", 9 | "contributors": [ 10 | { 11 | "name": "Sebastian Franze", 12 | "email": "s.franze@shopware.com" 13 | } 14 | ], 15 | "scripts": { 16 | "open": "Cypress open", 17 | "run": "Cypress run", 18 | "lint": "eslint --ext .js", 19 | "lint-fix": "eslint --ext .js", 20 | "start-e2e-proxy": "node --experimental-modules --experimental-json-modules cli-tools/index.js" 21 | }, 22 | "dependencies": { 23 | "@babel/core": "^7.8.7", 24 | "@babel/preset-env": "^7.8.7", 25 | "@shopware-ag/e2e-testsuite-platform": "^1.2.4", 26 | "body": "^5.1.0", 27 | "express": "^4.17.1", 28 | "fs-extra": "^8.1.0" 29 | }, 30 | "devDependencies": { 31 | "cypress": "^4.5.0", 32 | "eslint": "^6.8.0", 33 | "eslint-plugin-cypress": "^2.10.3", 34 | "eslint-plugin-import": "^2.20.0" 35 | }, 36 | "engines": { 37 | "node": ">= 12.14.1 <13.0.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/cli-tools/actions/reset-db.js: -------------------------------------------------------------------------------- 1 | import { spawn } from 'child_process'; 2 | 3 | /** 4 | * Resets the tests database. Internally runs psh.phar e2e:restore-db on the server 5 | * @module actions/resetDb 6 | * @param {string} rootDir 7 | */ 8 | export default function resetDb(rootDir) { 9 | return new Promise((resolve, reject) => { 10 | const resetDbProcess = spawn( 11 | `${rootDir}/psh.phar`, 12 | [ 13 | 'e2e:restore-db', 14 | '--APP_ENV="prod"', 15 | ] 16 | ); 17 | const outputBuffer = []; 18 | let output = null; 19 | 20 | resetDbProcess.stdout.on('data', (chunk) => { 21 | outputBuffer.push(chunk); 22 | }); 23 | resetDbProcess.stdout.on('end', () => { 24 | output = Buffer.concat(outputBuffer).toString(); 25 | }); 26 | 27 | resetDbProcess.on('exit', (code) => { 28 | if (code === 0) { 29 | resolve({ code, message: output }); 30 | return; 31 | } 32 | 33 | reject({ message: output, code }); 34 | }); 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /src/Core/Content/App/Manifest/Xml/CustomFieldTypes/BoolField.php: -------------------------------------------------------------------------------- 1 | > $data 11 | */ 12 | private function __construct(array $data) 13 | { 14 | foreach ($data as $property => $value) { 15 | $this->$property = $value; 16 | } 17 | } 18 | 19 | public static function fromXml(\DOMElement $element): CustomFieldType 20 | { 21 | return new self(self::parse($element)); 22 | } 23 | 24 | /** 25 | * @return array> 26 | */ 27 | protected function toEntityArray(): array 28 | { 29 | return [ 30 | 'type' => CustomFieldTypes::BOOL, 31 | 'config' => [ 32 | 'type' => 'checkbox', 33 | 'componentName' => 'sw-field', 34 | 'customFieldType' => 'checkbox', 35 | ], 36 | ]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Resources/app/administration/test/unit/jest-setup/setup-shopware.js: -------------------------------------------------------------------------------- 1 | const adminContext = { 2 | apiContext: { 3 | host: 'www.shopware-test.de', 4 | port: '80', 5 | scheme: 'http', 6 | schemeAndHttpHost: 'http://www.shopware-test.de:80', 7 | uri: 'http://www.shopware-test.de:80/admin', 8 | basePath: '', 9 | pathInfo: '/admin', 10 | liveVersionId: '0fa91ce3e96a4bc2be4bd9ce752c3425', 11 | systemLanguageId: '2fbb5fe2e29a4d70aa5854ce7ce3e20b', 12 | apiVersion: 1, 13 | }, 14 | appContext: { 15 | features: [], 16 | firstRunWizard: 'true', 17 | systemCurrencyId: 'b7d2554b0ce847cd82f3ac9bd1c0dfca', 18 | }, 19 | }; 20 | 21 | module.exports = (() => { 22 | require('babel-plugin-require-context-hook/register')(); 23 | const Shopware = require('src/core/shopware'); 24 | const connect = require('connect/core').default; 25 | 26 | global.Shopware = Shopware; 27 | 28 | require('src/app/main'); 29 | Shopware.Application 30 | .initState() 31 | .registerConfig(adminContext) 32 | .initializeFeatureFlags(); 33 | connect.install(Shopware); 34 | })(); 35 | -------------------------------------------------------------------------------- /src/Core/System/CustomField/Aggregate/CustomFieldSet/CustomFieldSetExtension.php: -------------------------------------------------------------------------------- 1 | add( 17 | new FkField('saas_app_id', 'saasAppId', AppDefinition::class) 18 | ); 19 | 20 | $collection->add( 21 | new ManyToOneAssociationField('saasApp', 'saas_app_id', AppDefinition::class) 22 | ); 23 | } 24 | 25 | public function getDefinitionClass(): string 26 | { 27 | return CustomFieldSetDefinition::class; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Core/Content/App/Manifest/Xml/CustomFieldTypes/ColorPickerField.php: -------------------------------------------------------------------------------- 1 | > $data 11 | */ 12 | private function __construct(array $data) 13 | { 14 | foreach ($data as $property => $value) { 15 | $this->$property = $value; 16 | } 17 | } 18 | 19 | public static function fromXml(\DOMElement $element): CustomFieldType 20 | { 21 | return new self(self::parse($element)); 22 | } 23 | 24 | /** 25 | * @return array> 26 | */ 27 | protected function toEntityArray(): array 28 | { 29 | return [ 30 | 'type' => CustomFieldTypes::TEXT, 31 | 'config' => [ 32 | 'type' => 'colorpicker', 33 | 'componentName' => 'sw-field', 34 | 'customFieldType' => 'colorpicker', 35 | ], 36 | ]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Core/Content/App/Manifest/Xml/Webhooks.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | protected $webhooks = []; 11 | 12 | /** 13 | * @param array $webhooks 14 | */ 15 | private function __construct(array $webhooks) 16 | { 17 | $this->webhooks = $webhooks; 18 | } 19 | 20 | public static function fromXml(\DOMElement $element): self 21 | { 22 | return new self(self::parseWebhooks($element)); 23 | } 24 | 25 | /** 26 | * @return array 27 | */ 28 | public function getWebhooks(): array 29 | { 30 | return $this->webhooks; 31 | } 32 | 33 | /** 34 | * @return array 35 | */ 36 | private static function parseWebhooks(\DOMElement $element): array 37 | { 38 | $webhooks = []; 39 | /** @var \DOMElement $webhook */ 40 | foreach ($element->getElementsByTagName('webhook') as $webhook) { 41 | $webhooks[] = Webhook::fromXml($webhook); 42 | } 43 | 44 | return $webhooks; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/cli-tools/actions/clear-cache.js: -------------------------------------------------------------------------------- 1 | 2 | import { spawn } from 'child_process'; 3 | 4 | /** 5 | * Clears the shopware servers cache. Internally runs psh.phar cache on the server 6 | * @module actions/resetDb 7 | * @param {string} rootDir 8 | */ 9 | export default function clearCache(rootDir) { 10 | return new Promise((resolve, reject) => { 11 | const clearCacheProcess = spawn( 12 | `${rootDir}/psh.phar`, 13 | [ 14 | 'cache', 15 | '--DB_NAME="shopware_e2e"', 16 | '--APP_ENV="prod"', 17 | ], 18 | ); 19 | const outputBuffer = []; 20 | let output = null; 21 | 22 | clearCacheProcess.stdout.on('data', (chunk) => { 23 | outputBuffer.push(chunk); 24 | }); 25 | clearCacheProcess.stdout.on('end', () => { 26 | output = Buffer.concat(outputBuffer).toString(); 27 | }); 28 | 29 | clearCacheProcess.on('exit', (code) => { 30 | if (code === 0) { 31 | resolve(); 32 | return; 33 | } 34 | 35 | reject({ message: output, code }); 36 | }); 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/support/commands/theme-commands.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Navigates to a module in the administration 3 | * @param {string} themeName 4 | */ 5 | function activateThemeForStorefront(themeName) { 6 | cy.setLocaleToEnGb(); 7 | cy.server(); 8 | cy.login('admin'); 9 | 10 | cy.get('.sw-admin-menu__sales-channel-item--1 > a').contains('Storefront'); 11 | cy.get('.sw-admin-menu__sales-channel-item--1').click(); 12 | cy.get('.sw-tabs-item').contains('Theme').click(); 13 | cy.get('.sw-button').contains('Change theme').click(); 14 | 15 | cy.get('.sw-theme-modal .sw-theme-list-item__title').contains(themeName).click(); 16 | cy.get('.sw-theme-modal .sw-button--primary').contains('Save').click(); 17 | 18 | cy.route({ 19 | method: 'post', 20 | url: 'api/v1/_action/theme/**', 21 | }).as('themeAction'); 22 | 23 | cy.get('.sw-modal .sw-button--primary').contains('Change theme').click(); 24 | 25 | cy.wait('@themeAction'); 26 | 27 | cy.get('.sw-sales-channel-detail__save-action').click(); 28 | } 29 | 30 | export default function addThemeCommands(Cypress) { 31 | Cypress.Commands.add('activateThemeForStorefront', { prevSubject: false }, activateThemeForStorefront); 32 | } 33 | -------------------------------------------------------------------------------- /src/Core/Content/App/Manifest/Xml/CustomFieldTypes/CustomFieldTypeFactory.php: -------------------------------------------------------------------------------- 1 | IntField::class, 11 | 'float' => FloatField::class, 12 | 'text' => TextField::class, 13 | 'text-area' => TextAreaField::class, 14 | 'bool' => BoolField::class, 15 | 'datetime' => DateTimeField::class, 16 | 'single-select' => SingleSelectField::class, 17 | 'multi-select' => MultiSelectField::class, 18 | 'color-picker' => ColorPickerField::class, 19 | 'media-selection' => MediaSelectionField::class, 20 | ]; 21 | 22 | public static function createFromXml(\DOMElement $element): CustomFieldType 23 | { 24 | $fieldClass = self::TAG_TO_CLASS_MAPPING[$element->tagName] ?? null; 25 | 26 | if (!$fieldClass) { 27 | throw new CustomFieldTypeNotFoundException($element->tagName); 28 | } 29 | 30 | return $fieldClass::fromXml($element); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Resources/app/administration/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "saasconnect", 3 | "version": "1.0.0", 4 | "description": "Saas Connect Administration plugin ", 5 | "author": "shopware AG", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": { 9 | "unit": "jest --config jest.config.js", 10 | "unit-watch": "jest --config jest.config.js --watch", 11 | "eslint": "eslint --ext .js,.vue src test", 12 | "eslint-junit": "eslint --ext .js,.vue --format junit src test" 13 | }, 14 | "devDependencies": { 15 | "@babel/core": "^7.8.3", 16 | "@babel/plugin-proposal-class-properties": "^7.7.4", 17 | "@babel/plugin-syntax-dynamic-import": "^7.7.4", 18 | "@babel/plugin-transform-runtime": "^7.8.3", 19 | "@babel/preset-env": "^7.7.7", 20 | "axios-mock-adapter": "^1.17.0", 21 | "babel-core": "^6.26.3", 22 | "babel-jest": "^24.9.0", 23 | "babel-plugin-require-context-hook": "^1.0.0", 24 | "eslint": "^6.8.0", 25 | "eslint-plugin-import": "^2.20.0", 26 | "eslint-plugin-jest": "^23.3.0", 27 | "html-loader-jest": "^0.2.1", 28 | "jest": "^24.9.0", 29 | "jest-junit": "^10.0.0" 30 | }, 31 | "dependencies": { 32 | "@babel/runtime": "^7.8.3", 33 | "axios": "^0.19.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/module/sw-my-apps/index.js: -------------------------------------------------------------------------------- 1 | import swMyAppsPage from './page/sw-my-apps-page'; 2 | 3 | export default { 4 | type: 'plugin', 5 | name: 'sw-my-apps', 6 | title: 'sw-connect.module.sw-my-apps.general.mainMenuItemGeneral', 7 | description: 'sw-connect.module.sw-my-apps.general.moduleDescription', 8 | icon: 'default-view-grid', 9 | color: '#9AA8B5', 10 | routePrefixPath: 'my-apps', 11 | 12 | components: { 13 | 'sw-my-apps-page': swMyAppsPage, 14 | }, 15 | 16 | routes: { 17 | index: { 18 | component: 'sw-my-apps-page', 19 | path: ':appName/:moduleName', 20 | props: { 21 | default(route) { 22 | const { appName, moduleName } = route.params; 23 | return { 24 | appName, 25 | moduleName, 26 | }; 27 | }, 28 | }, 29 | }, 30 | }, 31 | 32 | navigation: [{ 33 | id: 'sw-my-apps', 34 | label:'sw-connect.module.sw-my-apps.general.mainMenuItemGeneral', 35 | icon: 'default-view-grid', 36 | color: '#9AA8B5', 37 | position: 100, 38 | }], 39 | }; 40 | 41 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/support/commands/app-management-commands.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Installs fixture apps on the server 4 | * @param {Array.} apps 5 | */ 6 | function installE2EApps(apps) { 7 | const proxyUrl = Cypress.config('cliProxyUrl'); 8 | return cy.request({ 9 | method: 'POST', 10 | url: `${proxyUrl}/install-e2e-apps`, 11 | Headers: { 12 | 'content-type': 'application/json', 13 | accept: 'application/json', 14 | }, 15 | body: { apps }, 16 | }).its('statusCode').should('be', 204); 17 | } 18 | 19 | /** 20 | * Removes all fixture apps from the server 21 | */ 22 | function removeAllE2EApps() { 23 | const proxyUrl = Cypress.config('cliProxyUrl'); 24 | 25 | return cy.request({ 26 | method: 'DELETE', 27 | url: `${proxyUrl}/remove-e2e-apps`, 28 | Headers: { 29 | 'content-type': 'application/json', 30 | accept: 'application/json', 31 | }, 32 | }).its('statusCode').should('be', 204); 33 | } 34 | 35 | export default function addAppManagementCommands(Cypress) { 36 | Cypress.Commands.add('installE2EApps', { prevSubject: false }, installE2EApps); 37 | Cypress.Commands.add('removeE2EApps', { prevSubject: false }, removeAllE2EApps); 38 | }; 39 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/Xml/CustomFieldsTest.php: -------------------------------------------------------------------------------- 1 | getCustomFields()); 16 | static::assertCount(1, $manifest->getCustomFields()->getCustomFieldSets()); 17 | 18 | /** @var CustomFieldSet $customFieldSet */ 19 | $customFieldSet = $manifest->getCustomFields()->getCustomFieldSets()[0]; 20 | static::assertEquals('custom_field_test', $customFieldSet->getName()); 21 | static::assertEquals([ 22 | 'en-GB' => 'Custom field test', 23 | 'de-DE' => 'Zusatzfeld Test', 24 | ], $customFieldSet->getLabel()); 25 | static::assertEquals(['product', 'customer'], $customFieldSet->getRelatedEntities()); 26 | 27 | static::assertCount(0, $customFieldSet->getFields()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/support/commands/create-product-command.js: -------------------------------------------------------------------------------- 1 | function createProduct(productData) { 2 | return cy.request({ 3 | method: 'POST', 4 | url: 'api/oauth/token', 5 | headers: { 6 | 'content-type': 'application/json', 7 | accept: 'application/json', 8 | }, 9 | body: { 10 | 'grant_type': 'password', 11 | 'client_id': 'administration', 12 | 'scopes': 'write', 13 | 'username': 'admin', 14 | 'password': 'shopware', 15 | }, 16 | }).then(({ body }) => { 17 | return cy.request({ 18 | method: 'POST', 19 | url: '/api/v1/product', 20 | headers: { 21 | 'content-type': 'application/json', 22 | accept: 'application/json', 23 | Authorization: `Bearer ${body['access_token']}`, 24 | 25 | }, 26 | body: productData, 27 | }); 28 | }).should((response) => { 29 | if (response.status !== 204) { 30 | cy.log(response.body); 31 | } 32 | expect(response.status).to.equals(204); 33 | }); 34 | } 35 | 36 | export default function addProductCommand(Cypress) { 37 | Cypress.Commands.add('createProduct', { prevSubject: false }, createProduct); 38 | }; 39 | -------------------------------------------------------------------------------- /src/Core/Content/App/Manifest/Xml/CustomFieldTypes/DateTimeField.php: -------------------------------------------------------------------------------- 1 | > $data 11 | */ 12 | private function __construct(array $data) 13 | { 14 | foreach ($data as $property => $value) { 15 | $this->$property = $value; 16 | } 17 | } 18 | 19 | public static function fromXml(\DOMElement $element): CustomFieldType 20 | { 21 | return new self(self::parse($element)); 22 | } 23 | 24 | /** 25 | * @return array>> 26 | */ 27 | protected function toEntityArray(): array 28 | { 29 | return [ 30 | 'type' => CustomFieldTypes::DATETIME, 31 | 'config' => [ 32 | 'type' => 'date', 33 | 'componentName' => 'sw-field', 34 | 'customFieldType' => 'date', 35 | 'config' => [ 36 | 'time_24hr' => true, 37 | ], 38 | 'dateType' => 'datetime', 39 | ], 40 | ]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Core/Content/App/Lifecycle/Event/ManifestChangedEvent.php: -------------------------------------------------------------------------------- 1 | app = $app; 19 | parent::__construct($appId, $context); 20 | } 21 | 22 | abstract public function getName(): string; 23 | 24 | public function getApp(): Manifest 25 | { 26 | return $this->app; 27 | } 28 | 29 | /** 30 | * @return array 31 | */ 32 | public function getWebhookPayload(): array 33 | { 34 | return [ 35 | 'appVersion' => $this->getApp()->getMetadata()->getVersion(), 36 | ]; 37 | } 38 | 39 | /** 40 | * @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter 41 | */ 42 | public function isAllowed(string $appId, AclPrivilegeCollection $permissions): bool 43 | { 44 | return $appId === $this->getAppId(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Core/Content/App/Manifest/Xml/CustomFields.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | protected $customFieldSets = []; 11 | 12 | /** 13 | * @param array $customFieldSets 14 | */ 15 | private function __construct(array $customFieldSets) 16 | { 17 | $this->customFieldSets = $customFieldSets; 18 | } 19 | 20 | public static function fromXml(\DOMElement $element): self 21 | { 22 | return new self(self::parseCustomFieldSets($element)); 23 | } 24 | 25 | /** 26 | * @return array 27 | */ 28 | public function getCustomFieldSets(): array 29 | { 30 | return $this->customFieldSets; 31 | } 32 | 33 | /** 34 | * @return array 35 | */ 36 | private static function parseCustomFieldSets(\DOMElement $element): array 37 | { 38 | $customFieldSets = []; 39 | /** @var \DOMElement $customFieldSet */ 40 | foreach ($element->getElementsByTagName('custom-field-set') as $customFieldSet) { 41 | $customFieldSets[] = CustomFieldSet::fromXml($customFieldSet); 42 | } 43 | 44 | return $customFieldSets; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/app/component/sw-connect-action-button/index.js: -------------------------------------------------------------------------------- 1 | import template from './sw-connect-action-button.html.twig'; 2 | import './sw-connect-action-button.scss'; 3 | 4 | export default { 5 | template, 6 | name: 'sw-connect-action-button', 7 | 8 | props: { 9 | action: { 10 | type: Object, 11 | required: true, 12 | }, 13 | }, 14 | 15 | computed: { 16 | buttonLabel() { 17 | const currentLocale = Shopware.State.get('session').currentLocale; 18 | const fallbackLocale = Shopware.Context.app.fallbackLocale; 19 | 20 | return this.action.label[currentLocale] || this.action.label[fallbackLocale] || ''; 21 | }, 22 | 23 | openInNewTab() { 24 | return !!this.action.openNewTab; 25 | }, 26 | 27 | linkData() { 28 | if (this.openInNewTab) { 29 | return { 30 | target: '_blank', 31 | href: this.action.url, 32 | }; 33 | } 34 | 35 | return {}; 36 | }, 37 | }, 38 | 39 | methods: { 40 | runAction() { 41 | if (this.openInNewTab) { 42 | return; 43 | } 44 | 45 | this.$emit('run-app-action', this.action.id); 46 | }, 47 | }, 48 | }; 49 | 50 | -------------------------------------------------------------------------------- /src/Resources/app/administration/test/unit/__fixtures__/app-system/app-url-change.fixtures.js: -------------------------------------------------------------------------------- 1 | const defaultStrategies = { 2 | status: 200, 3 | statusText: 'OK', 4 | data: { 5 | MoveShopPermanently: 6 | 'Use this URL for communicating with installed apps, this will disable communication to apps on the old' + 7 | ' URLs installation, but the app-data from the old installation will be available in this installation.', 8 | ReinstallApps: 9 | 'Reinstall all apps anew for the new URL, so app communication on the old URLs installation keeps ' + 10 | 'working like before. App-data from the old installation will not be available in this installation.', 11 | UninstallApps: 12 | 'Uninstall all apps on this URL, so app communication on the old URLs installation keeps ' + 13 | 'working like before.', 14 | }, 15 | }; 16 | 17 | const urlDifference = { 18 | status: 200, 19 | statusText: 'OK', 20 | data: { 21 | oldUrl: 'https://old.com', 22 | newUrl: 'https://new.com', 23 | }, 24 | }; 25 | 26 | const emptyUrlDifference = { 27 | status: 204, 28 | statusText: 'No Content', 29 | data: '', 30 | }; 31 | 32 | export default { 33 | defaultStrategies, 34 | urlDifference, 35 | emptyUrlDifference, 36 | }; 37 | -------------------------------------------------------------------------------- /src/Migration/Migration1581948516Template.php: -------------------------------------------------------------------------------- 1 | executeUpdate(' 18 | CREATE TABLE IF NOT EXISTS `saas_template` ( 19 | `id` BINARY(16) NOT NULL, 20 | `template` LONGTEXT NOT NULL, 21 | `path` VARCHAR(1024) NOT NULL, 22 | `active` TINYINT(1) NOT NULL, 23 | `app_id` BINARY(16) NOT NULL, 24 | `created_at` DATETIME(3) NOT NULL, 25 | `updated_at` DATETIME(3) NULL, 26 | PRIMARY KEY (`id`), 27 | INDEX `idx.saas_template.path` (`path`(256)), 28 | CONSTRAINT `fk.saas_template.app_id` FOREIGN KEY (`app_id`) REFERENCES `saas_app` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 29 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 30 | '); 31 | } 32 | 33 | public function updateDestructive(Connection $connection): void 34 | { 35 | // nth 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/SaasConnect.php: -------------------------------------------------------------------------------- 1 | keepUserData() === true) { 14 | if (method_exists($uninstallContext, 'enableKeepMigrations')) { 15 | $uninstallContext->enableKeepMigrations(); 16 | } 17 | 18 | return; 19 | } 20 | 21 | $connection = $this->container->get(Connection::class); 22 | $connection->executeUpdate(' 23 | ALTER TABLE `custom_field_set` 24 | DROP FOREIGN KEY `fk.custom_field_set.saas_app_id`, 25 | DROP COLUMN `saas_app_id`; 26 | '); 27 | $connection->executeUpdate(' 28 | DROP TABLE IF EXISTS 29 | `saas_webhook`, 30 | `saas_template`, 31 | `saas_app_action_button_translation`, 32 | `saas_app_action_button`, 33 | `saas_app_translation`, 34 | `saas_app`; 35 | '); 36 | } 37 | 38 | public function rebuildContainer(): bool 39 | { 40 | return false; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Migration/Migration1581087930Webhook.php: -------------------------------------------------------------------------------- 1 | executeUpdate(' 18 | CREATE TABLE `saas_webhook` ( 19 | `id` BINARY(16) NOT NULL, 20 | `name` VARCHAR(255) NOT NULL, 21 | `event_name` VARCHAR(500) NOT NULL, 22 | `url` VARCHAR(500) NOT NULL, 23 | `app_id` BINARY(16) NULL, 24 | `created_at` DATETIME(3) NOT NULL, 25 | `updated_at` DATETIME(3) NULL, 26 | PRIMARY KEY (`id`), 27 | CONSTRAINT `fk.saas_webhook.app_id` FOREIGN KEY (`app_id`) REFERENCES `saas_app` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 28 | CONSTRAINT `uniq.saas_webhook.name` UNIQUE (`name`, `app_id`) 29 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; 30 | '); 31 | } 32 | 33 | public function updateDestructive(Connection $connection): void 34 | { 35 | // nth 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/Xml/MetadataTest.php: -------------------------------------------------------------------------------- 1 | getMetadata(); 15 | static::assertEquals('SwagApp', $metaData->getName()); 16 | static::assertEquals('shopware AG', $metaData->getAuthor()); 17 | static::assertEquals('(c) by shopware AG', $metaData->getCopyright()); 18 | static::assertEquals('MIT', $metaData->getLicense()); 19 | static::assertEquals('https://test.com/privacy', $metaData->getPrivacy()); 20 | static::assertEquals('1.0.0', $metaData->getVersion()); 21 | static::assertEquals('icon.png', $metaData->getIcon()); 22 | 23 | static::assertEquals([ 24 | 'en-GB' => 'Swag App Test', 25 | 'de-DE' => 'Swag App Test', 26 | ], $metaData->getLabel()); 27 | static::assertEquals([ 28 | 'en-GB' => 'Test for App System', 29 | 'de-DE' => 'Test für das App System', 30 | ], $metaData->getDescription()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/Xml/WebhooksTest.php: -------------------------------------------------------------------------------- 1 | getWebhooks()); 16 | static::assertCount(2, $manifest->getWebhooks()->getWebhooks()); 17 | 18 | /** @var Webhook $firstWebhook */ 19 | $firstWebhook = $manifest->getWebhooks()->getWebhooks()[0]; 20 | static::assertEquals('hook1', $firstWebhook->getName()); 21 | static::assertEquals('https://test.com/hook', $firstWebhook->getUrl()); 22 | static::assertEquals('checkout.customer.before.login', $firstWebhook->getEvent()); 23 | 24 | /** @var Webhook $secondWebhook */ 25 | $secondWebhook = $manifest->getWebhooks()->getWebhooks()[1]; 26 | static::assertEquals('hook2', $secondWebhook->getName()); 27 | static::assertEquals('https://test.com/hook2', $secondWebhook->getUrl()); 28 | static::assertEquals('checkout.order.placed', $secondWebhook->getEvent()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Resources/app/administration/test/unit/__fixtures__/app-system/action-buttons.fixtures.js: -------------------------------------------------------------------------------- 1 | const emptyActionButtonList = { 2 | status: 200, 3 | statusText: 'OK', 4 | data: { 5 | actions: [], 6 | }, 7 | }; 8 | 9 | const actionButtons = { 10 | status: 200, 11 | statusText: 'OK', 12 | data: { 13 | actions: [ 14 | { 15 | action: 'doStuffWithProducts', 16 | app: 'SwagApp', 17 | id: '28e76437999b41d0b8e87e9aab44e41b', 18 | label: { 19 | 'en-GB': 'Do Stuff', 20 | }, 21 | openNewTab: false, 22 | url: 'https://swag-test.com/do-stuff', 23 | }, 24 | ], 25 | }, 26 | }; 27 | 28 | const malformedList = { 29 | status: 200, 30 | statusText: 'OK', 31 | data: [ 32 | { 33 | action: 'doStuffWithProducts', 34 | app: 'SwagApp', 35 | id: '28e76437999b41d0b8e87e9aab44e41b', 36 | label: { 37 | 'en-GB': 'Do Stuff', 38 | }, 39 | openNewTab: false, 40 | url: 'https://swag-test.com/do-stuff', 41 | }, 42 | ], 43 | }; 44 | 45 | const emptyResponse = { 46 | status: 200, 47 | statusText: 'OK', 48 | data: [], 49 | }; 50 | 51 | export default { 52 | emptyActionButtonList, 53 | malformedList, 54 | actionButtons, 55 | emptyResponse, 56 | }; 57 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/extension/app/component/structure/sw-admin-menu/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'sw-admin-menu', 3 | 4 | computed: { 5 | appEntries() { 6 | return Shopware.State.getters['connect-apps/navigation']; 7 | }, 8 | }, 9 | 10 | watch: { 11 | appEntries() { 12 | this.updateAppEntries(); 13 | }, 14 | 15 | mainMenuEntries() { 16 | this.updateAppEntries(); 17 | }, 18 | }, 19 | 20 | methods: { 21 | updateAppEntries() { 22 | const entryIndex = this.mainMenuEntries.findIndex((entry) => entry.id === 'sw-my-apps'); 23 | 24 | if (entryIndex < 0) { 25 | return; 26 | } 27 | 28 | const myAppsEntry = this.mainMenuEntries[entryIndex]; 29 | const newEntries = this.getNewEntries(myAppsEntry.children, this.appEntries); 30 | 31 | myAppsEntry.children = [...myAppsEntry.children, ...newEntries]; 32 | 33 | this.mainMenuEntries[entryIndex] = { ...myAppsEntry }; 34 | }, 35 | 36 | getNewEntries(actualNavigationEntries, appNavigationEntries) { 37 | return appNavigationEntries.filter((appNavigationEntry) => { 38 | return !actualNavigationEntries.some((actualEntry) => { 39 | return actualEntry.path === appNavigationEntry.path; 40 | }); 41 | }); 42 | }, 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /tests/Core/Framework/Webhook/_fixtures/BusinessEvents/UnstructuredObjectBusinessEvent.php: -------------------------------------------------------------------------------- 1 | 'test', 17 | 'bool' => true, 18 | ]; 19 | 20 | public static function getAvailableData(): EventDataCollection 21 | { 22 | return (new EventDataCollection()) 23 | ->add('nested', new ObjectType()); 24 | } 25 | 26 | public function getEncodeValues(string $shopwareVersion): array 27 | { 28 | return [ 29 | 'nested' => [ 30 | 'string' => 'test', 31 | 'bool' => true, 32 | ], 33 | ]; 34 | } 35 | 36 | public function getName(): string 37 | { 38 | return 'test'; 39 | } 40 | 41 | public function getContext(): Context 42 | { 43 | return Context::createDefaultContext(); 44 | } 45 | 46 | public function getNested(): array 47 | { 48 | return $this->nested; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Core/Content/App/AppService.php: -------------------------------------------------------------------------------- 1 | appLifecycleIterator = $appLifecycleIterator; 28 | $this->appLifecycle = $appLifecycle; 29 | } 30 | 31 | /** 32 | * @return array 33 | */ 34 | public function refreshApps(bool $activateInstalled, Context $context): array 35 | { 36 | return $this->appLifecycleIterator->iterate($this->appLifecycle, $activateInstalled, $context); 37 | } 38 | 39 | public function getRefreshableAppInfo(Context $context): RefreshableAppDryRun 40 | { 41 | $appInfo = new RefreshableAppDryRun(); 42 | 43 | $this->appLifecycleIterator->iterate($appInfo, false, $context); 44 | 45 | return $appInfo; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Core/Content/App/Manifest/Xml/Setup.php: -------------------------------------------------------------------------------- 1 | > $data 19 | */ 20 | private function __construct(array $data) 21 | { 22 | foreach ($data as $property => $value) { 23 | $this->$property = $value; 24 | } 25 | } 26 | 27 | public static function fromXml(\DOMElement $element): self 28 | { 29 | return new self(self::parse($element)); 30 | } 31 | 32 | public function getRegistrationUrl(): string 33 | { 34 | return $this->registrationUrl; 35 | } 36 | 37 | public function getSecret(): ?string 38 | { 39 | return $this->secret; 40 | } 41 | 42 | /** 43 | * @return array> 44 | */ 45 | private static function parse(\DOMElement $element): array 46 | { 47 | $values = []; 48 | 49 | foreach ($element->childNodes as $child) { 50 | if (!$child instanceof \DOMElement) { 51 | continue; 52 | } 53 | 54 | $values[$child->tagName] = $child->nodeValue; 55 | } 56 | 57 | return $values; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Core/Content/App/Aggregate/ActionButtonTranslation/ActionButtonTranslationDefinition.php: -------------------------------------------------------------------------------- 1 | addFlags(new Required()), 39 | ]); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Lifecycle/Event/AppDeletedEventTest.php: -------------------------------------------------------------------------------- 1 | getAppId()); 23 | static::assertEquals($context, $event->getContext()); 24 | static::assertEquals(AppDeletedEvent::NAME, $event->getName()); 25 | static::assertEquals([], $event->getWebhookPayload()); 26 | } 27 | 28 | public function testIsAllowed(): void 29 | { 30 | $appId = Uuid::randomHex(); 31 | $context = Context::createDefaultContext(); 32 | $event = new AppDeletedEvent( 33 | $appId, 34 | $context 35 | ); 36 | 37 | static::assertTrue($event->isAllowed($appId, new AclPrivilegeCollection())); 38 | static::assertFalse($event->isAllowed(Uuid::randomHex(), new AclPrivilegeCollection())); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/TestBootstrap.php: -------------------------------------------------------------------------------- 1 | load(TEST_PROJECT_DIR . '/.env'); 37 | 38 | $testDb = ($_SERVER['DATABASE_URL'] ?? '') . '_test'; 39 | putenv('DATABASE_URL=' . $testDb); 40 | $_ENV['DATABASE_URL'] = $testDb; 41 | $_SERVER['DATABASE_URL'] = $testDb; 42 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Lifecycle/Event/AppActivatedEventTest.php: -------------------------------------------------------------------------------- 1 | getAppId()); 23 | static::assertEquals($context, $event->getContext()); 24 | static::assertEquals(AppActivatedEvent::NAME, $event->getName()); 25 | static::assertEquals([], $event->getWebhookPayload()); 26 | } 27 | 28 | public function testIsAllowed(): void 29 | { 30 | $appId = Uuid::randomHex(); 31 | $context = Context::createDefaultContext(); 32 | $event = new AppActivatedEvent( 33 | $appId, 34 | $context 35 | ); 36 | 37 | static::assertTrue($event->isAllowed($appId, new AclPrivilegeCollection())); 38 | static::assertFalse($event->isAllowed(Uuid::randomHex(), new AclPrivilegeCollection())); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Lifecycle/Event/AppDeactivatedEventTest.php: -------------------------------------------------------------------------------- 1 | getAppId()); 23 | static::assertEquals($context, $event->getContext()); 24 | static::assertEquals(AppDeactivatedEvent::NAME, $event->getName()); 25 | static::assertEquals([], $event->getWebhookPayload()); 26 | } 27 | 28 | public function testIsAllowed(): void 29 | { 30 | $appId = Uuid::randomHex(); 31 | $context = Context::createDefaultContext(); 32 | $event = new AppDeactivatedEvent( 33 | $appId, 34 | $context 35 | ); 36 | 37 | static::assertTrue($event->isAllowed($appId, new AclPrivilegeCollection())); 38 | static::assertFalse($event->isAllowed(Uuid::randomHex(), new AclPrivilegeCollection())); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Core/Content/App/Aggregate/AppTranslation/AppTranslationDefinition.php: -------------------------------------------------------------------------------- 1 | addFlags(new Required()), 40 | new LongTextField('description', 'description'), 41 | ]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Core/Content/App/Lifecycle/Persister/PermissionGatewayFactory.php: -------------------------------------------------------------------------------- 1 | shopwareVersion = $shopwareVersion; 23 | $this->container = $container; 24 | } 25 | 26 | public function createPermissionGateway(): PermissionGatewayStrategy 27 | { 28 | /** @var Connection $connection */ 29 | $connection = $this->container->get(Connection::class); 30 | 31 | if (version_compare($this->shopwareVersion, '6.3.0.0', '<')) { 32 | $privileges = []; 33 | 34 | if ($this->container->hasParameter('acl_resource_privileges')) { 35 | // should be always the case except in tests running on 6.3 36 | $privileges = $this->container->getParameter('acl_resource_privileges'); 37 | } 38 | 39 | return new PermissionGateway62( 40 | $connection, 41 | $privileges 42 | ); 43 | } 44 | 45 | return new PermissionGateway63($connection); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Core/Content/App/Action/Executor.php: -------------------------------------------------------------------------------- 1 | guzzleClient = $guzzle; 20 | } 21 | 22 | public function execute(AppAction $action, Context $context): void 23 | { 24 | $payload = $action->asPayload(); 25 | $payload['meta'] = [ 26 | 'timestamp' => (new \DateTime())->getTimestamp(), 27 | 'reference' => Uuid::randomHex(), 28 | 'language' => $context->getLanguageId(), 29 | ]; 30 | 31 | try { 32 | $this->guzzleClient->post( 33 | $action->getTargetUrl(), 34 | [ 35 | 'headers' => [ 36 | 'shopware-shop-signature' => hash_hmac( 37 | 'sha256', 38 | (string) \json_encode($payload), 39 | $action->getAppSecret() 40 | ), 41 | ], 42 | 'json' => $payload, 43 | ] 44 | ); 45 | } catch (ServerException $e) { 46 | // ignore failing requests 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/Xml/CustomFieldTypes/_fixtures/bool-field.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagApp 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | icon.png 14 | MIT 15 | 16 | 17 | https://my.app.com/SwagApp/registration 18 | s3cr3t 19 | 20 | 21 | 22 | custom_field_test 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/GuzzleTestClientBehaviour.php: -------------------------------------------------------------------------------- 1 | getContainer()->get(GuzzleHistoryCollector::class)->resetHistory(); 23 | $this->getContainer()->get(MockHandler::class)->reset(); 24 | } 25 | 26 | public function getLastRequest(): RequestInterface 27 | { 28 | return $this->getContainer()->get(MockHandler::class)->getLastRequest(); 29 | } 30 | 31 | public function getPastRequest(int $index): RequestInterface 32 | { 33 | return $this->getContainer()->get(GuzzleHistoryCollector::class)->getHistory()[$index]['request']; 34 | } 35 | 36 | public function getRequestCount(): int 37 | { 38 | return count($this->getContainer()->get(GuzzleHistoryCollector::class)->getHistory()); 39 | } 40 | 41 | /** 42 | * @param $response ResponseInterface | Exception | PromiseInterface 43 | */ 44 | public function appendNewResponse($response): void 45 | { 46 | $this->getContainer()->get(MockHandler::class)->append($response); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/Core/Command/_fixtures/withoutPermissions/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagApp 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | icon.png 14 | MIT 15 | 16 | 17 | https://my.app.com/SwagApp/registration 18 | s3cr3t 19 | 20 | 21 | 27 | 28 | 29 | 30 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/Xml/CustomFieldTypes/_fixtures/date-time-field.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagApp 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | icon.png 14 | MIT 15 | 16 | 17 | https://my.app.com/SwagApp/registration 18 | s3cr3t 19 | 20 | 21 | 22 | custom_field_test 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/Core/Command/_fixtures/registrationFailure/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagApp 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | icon.png 14 | MIT 15 | 16 | 17 | https://my.app.com/wrongurl/registration 18 | s3cr3t 19 | 20 | 21 | 27 | 28 | 29 | 30 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/Core/Content/App/Lifecycle/Event/AppChangedEvent.php: -------------------------------------------------------------------------------- 1 | appId = $appId; 26 | $this->context = $context; 27 | } 28 | 29 | abstract public function getName(): string; 30 | 31 | public function getAppId(): string 32 | { 33 | return $this->appId; 34 | } 35 | 36 | public function getContext(): Context 37 | { 38 | return $this->context; 39 | } 40 | 41 | /** 42 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint 43 | */ 44 | public function getWebhookPayload(): array 45 | { 46 | return []; 47 | } 48 | 49 | /** 50 | * @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter 51 | */ 52 | public function isAllowed(string $appId, AclPrivilegeCollection $permissions): bool 53 | { 54 | return $appId === $this->getAppId(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/Xml/CustomFieldTypes/_fixtures/color-picker-field.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagApp 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | icon.png 14 | MIT 15 | 16 | 17 | https://my.app.com/SwagApp/registration 18 | s3cr3t 19 | 20 | 21 | 22 | custom_field_test 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/Xml/CustomFieldTypes/_fixtures/media-selection-field.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagApp 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | icon.png 14 | MIT 15 | 16 | 17 | https://my.app.com/SwagApp/registration 18 | s3cr3t 19 | 20 | 21 | 22 | custom_field_test 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Core/Command/VerifyManifestCommand.php: -------------------------------------------------------------------------------- 1 | $manifestPaths */ 19 | $manifestPaths = $input->getArgument('manifests'); 20 | 21 | $invalidCount = 0; 22 | 23 | foreach ($manifestPaths as $manifestPath) { 24 | try { 25 | Manifest::createFromXmlFile($manifestPath); 26 | } catch (XmlParsingException $e) { 27 | $io->error($e->getMessage()); 28 | ++$invalidCount; 29 | } 30 | } 31 | if ($invalidCount > 0) { 32 | return 1; 33 | } 34 | $io->success('all files valid'); 35 | 36 | return 0; 37 | } 38 | 39 | protected function configure(): void 40 | { 41 | $this->setName('app:verify') 42 | ->setDescription('checks manifests for errors') 43 | ->addArgument('manifests', InputArgument::IS_ARRAY, 'The paths of the manifest file'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Migration/Migration1597392859RenameAppIdColumn.php: -------------------------------------------------------------------------------- 1 | executeQuery('SHOW COLUMNS FROM `custom_field_set` LIKE "saas_app_id";')->fetchAll(); 22 | 23 | if (count($columnExists) > 0) { 24 | // if column with the new name already exists, we don't need to rename anything 25 | return; 26 | } 27 | 28 | $connection->executeUpdate(' 29 | ALTER TABLE `custom_field_set` 30 | DROP FOREIGN KEY `fk.custom_field_set.app_id`, 31 | DROP INDEX `fk.custom_field_set.app_id`, 32 | CHANGE COLUMN `app_id` `saas_app_id` BINARY(16) NULL, 33 | ADD CONSTRAINT `fk.custom_field_set.saas_app_id` FOREIGN KEY (`saas_app_id`) REFERENCES `saas_app` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; 34 | '); 35 | } 36 | 37 | public function updateDestructive(Connection $connection): void 38 | { 39 | // nth 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Storefront/Theme/StorefrontPluginConfiguration/StorefrontPluginConfigurationAppFactory.php: -------------------------------------------------------------------------------- 1 | configurationFactory = $configurationFactory; 28 | $this->projectDir = $projectDir; 29 | } 30 | 31 | public function createFromApp(AppEntity $app): StorefrontPluginConfiguration 32 | { 33 | $absolutePath = $this->projectDir . '/' . $app->getPath(); 34 | if (file_exists($absolutePath . '/Resources/theme.json')) { 35 | return $this->configurationFactory->createThemeConfig($app->getName(), $absolutePath); 36 | } 37 | 38 | return $this->configurationFactory->createPluginConfig($app->getName(), $absolutePath); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/Xml/CustomFieldTypes/_fixtures/text-field.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagApp 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | icon.png 14 | MIT 15 | 16 | 17 | https://my.app.com/SwagApp/registration 18 | s3cr3t 19 | 20 | 21 | 22 | custom_field_test 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Enter a text... 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/Core/Content/App/Manifest/Xml/Permissions.php: -------------------------------------------------------------------------------- 1 | > 9 | */ 10 | protected $permissions; 11 | 12 | /** 13 | * @param array> $permissions 14 | */ 15 | private function __construct(array $permissions) 16 | { 17 | $this->permissions = $permissions; 18 | } 19 | 20 | public static function fromXml(\DOMElement $element): self 21 | { 22 | return new self(self::parsePermissions($element)); 23 | } 24 | 25 | /** 26 | * @param array> $permissions permissions as array indexed by resource 27 | */ 28 | public static function fromArray(array $permissions): self 29 | { 30 | return new self($permissions); 31 | } 32 | 33 | /** 34 | * @return array> 35 | */ 36 | public function getPermissions(): array 37 | { 38 | return $this->permissions; 39 | } 40 | 41 | /** 42 | * @return array> 43 | */ 44 | private static function parsePermissions(\DOMElement $element): array 45 | { 46 | $permissions = []; 47 | 48 | foreach ($element->childNodes as $child) { 49 | if (!$child instanceof \DOMElement) { 50 | continue; 51 | } 52 | 53 | $permissions[$child->nodeValue][] = $child->tagName; 54 | } 55 | 56 | return $permissions; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Lifecycle/Persister/PermissionGatewayFactoryTest.php: -------------------------------------------------------------------------------- 1 | getContainer() 23 | ); 24 | 25 | $gateway = $factory->createPermissionGateway(); 26 | static::assertInstanceOf($expectedGateway, $gateway); 27 | } 28 | 29 | public function getVersionGateways(): array 30 | { 31 | return [ 32 | ['6.1.3', PermissionGateway62::class], 33 | ['6.2.0', PermissionGateway62::class], 34 | ['6.2.3', PermissionGateway62::class], 35 | ['6.3.0.0', PermissionGateway63::class], 36 | ['6.3.0.1', PermissionGateway63::class], 37 | ['6.3.1.1', PermissionGateway63::class], 38 | ['6.4.0.0', PermissionGateway63::class], 39 | ]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Core/Content/App/Manifest/Xml/Webhook.php: -------------------------------------------------------------------------------- 1 | $data 26 | */ 27 | private function __construct(array $data) 28 | { 29 | foreach ($data as $property => $value) { 30 | $this->$property = $value; 31 | } 32 | } 33 | 34 | public static function fromXml(\DOMElement $element): self 35 | { 36 | return new self(self::parse($element)); 37 | } 38 | 39 | public function getName(): string 40 | { 41 | return $this->name; 42 | } 43 | 44 | public function getUrl(): string 45 | { 46 | return $this->url; 47 | } 48 | 49 | public function getEvent(): string 50 | { 51 | return $this->event; 52 | } 53 | 54 | /** 55 | * @return array 56 | */ 57 | private static function parse(\DOMElement $element): array 58 | { 59 | $values = []; 60 | 61 | /** @var \DOMAttr $attribute */ 62 | foreach ($element->attributes as $attribute) { 63 | $values[$attribute->name] = XmlUtils::phpize($attribute->value); 64 | } 65 | 66 | return $values; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Core/Content/App/Manifest/Xml/CustomFieldTypes/TextAreaField.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $placeholder = []; 15 | 16 | /** 17 | * @param array> $data 18 | */ 19 | private function __construct(array $data) 20 | { 21 | foreach ($data as $property => $value) { 22 | $this->$property = $value; 23 | } 24 | } 25 | 26 | public static function fromXml(\DOMElement $element): CustomFieldType 27 | { 28 | return new self(self::parse($element, self::TRANSLATABLE_FIELDS)); 29 | } 30 | 31 | /** 32 | * @return array 33 | */ 34 | public function getPlaceholder(): array 35 | { 36 | return $this->placeholder; 37 | } 38 | 39 | /** 40 | * @return array>> 41 | */ 42 | protected function toEntityArray(): array 43 | { 44 | return [ 45 | 'type' => CustomFieldTypes::HTML, 46 | 'config' => [ 47 | 'placeholder' => $this->placeholder, 48 | 'componentName' => 'sw-text-editor', 49 | 'customFieldType' => 'textEditor', 50 | ], 51 | ]; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Manifest/Xml/CustomFieldTypes/_fixtures/text-area-field.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagApp 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | icon.png 14 | MIT 15 | 16 | 17 | https://my.app.com/SwagApp/registration 18 | s3cr3t 19 | 20 | 21 | 22 | custom_field_test 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Enter a text... 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/Core/Content/App/Manifest/Xml/CustomFieldTypes/TextField.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $placeholder = []; 15 | 16 | /** 17 | * @param array> $data 18 | */ 19 | private function __construct(array $data) 20 | { 21 | foreach ($data as $property => $value) { 22 | $this->$property = $value; 23 | } 24 | } 25 | 26 | public static function fromXml(\DOMElement $element): CustomFieldType 27 | { 28 | return new self(self::parse($element, self::TRANSLATABLE_FIELDS)); 29 | } 30 | 31 | /** 32 | * @return array 33 | */ 34 | public function getPlaceholder(): array 35 | { 36 | return $this->placeholder; 37 | } 38 | 39 | /** 40 | * @return array>> 41 | */ 42 | protected function toEntityArray(): array 43 | { 44 | return [ 45 | 'type' => CustomFieldTypes::TEXT, 46 | 'config' => [ 47 | 'type' => 'text', 48 | 'placeholder' => $this->placeholder, 49 | 'componentName' => 'sw-field', 50 | 'customFieldType' => 'text', 51 | ], 52 | ]; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Core/Content/App/Lifecycle/Event/AppDeletedEvent.php: -------------------------------------------------------------------------------- 1 | appId = $appId; 28 | $this->context = $context; 29 | } 30 | 31 | public function getAppId(): string 32 | { 33 | return $this->appId; 34 | } 35 | 36 | public function getContext(): Context 37 | { 38 | return $this->context; 39 | } 40 | 41 | /** 42 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint 43 | */ 44 | public function getWebhookPayload(): array 45 | { 46 | return []; 47 | } 48 | 49 | /** 50 | * @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter 51 | */ 52 | public function isAllowed(string $appId, AclPrivilegeCollection $permissions): bool 53 | { 54 | return $appId === $this->getAppId(); 55 | } 56 | 57 | public function getName(): string 58 | { 59 | return self::NAME; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Resources/config/services_test.xml: -------------------------------------------------------------------------------- 1 | 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 | -------------------------------------------------------------------------------- /src/Resources/app/e2e/fixtures/themeApp/Resources/app/storefront/dist/storefront/js/swag-theme.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([["swag-theme"],{laxr:function(e,t,n){"use strict";function o(e){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function r(e,t){for(var n=0;n=document.body.offsetHeight&&alert("seems like there's nothing more to see here.")}}}])&&r(n.prototype,o),f&&r(n,f),t}(n("FGIj").a);window.PluginManager.register("ExamplePlugin",f)}},[["laxr","runtime","vendor-node","vendor-shared"]]]); -------------------------------------------------------------------------------- /src/Core/Content/App/Lifecycle/AppLoader.php: -------------------------------------------------------------------------------- 1 | appDir = $appDir; 23 | } 24 | 25 | /** 26 | * @return array 27 | */ 28 | public function load(): array 29 | { 30 | $finder = new Finder(); 31 | $finder->in($this->appDir) 32 | ->name('manifest.xml'); 33 | 34 | $manifests = []; 35 | foreach ($finder->files() as $xml) { 36 | try { 37 | $manifests[] = Manifest::createFromXmlFile($xml->getPathname()); 38 | } catch (XmlParsingException $e) { 39 | //nth, if app is already registered it will be deleted 40 | } 41 | } 42 | 43 | return $manifests; 44 | } 45 | 46 | public function getIcon(Manifest $app): ?string 47 | { 48 | if (!$app->getMetadata()->getIcon()) { 49 | return null; 50 | } 51 | 52 | $iconPath = sprintf('%s/%s', $app->getPath(), $app->getMetadata()->getIcon() ?: ''); 53 | $icon = @file_get_contents($iconPath); 54 | 55 | if (!$icon) { 56 | return null; 57 | } 58 | 59 | return $icon; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Core/Command/_fixtures/withPermissions/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagApp 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | icon.png 14 | MIT 15 | 16 | 17 | https://my.app.com/SwagApp/registration 18 | s3cr3t 19 | 20 | 21 | 27 | 28 | 29 | 30 | 35 | 36 | 37 | 38 | 39 | product 40 | product 41 | category 42 | order 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/Core/Framework/ShopId/ShopIdProvider.php: -------------------------------------------------------------------------------- 1 | systemConfigService = $systemConfigService; 21 | } 22 | 23 | public function getShopId(): string 24 | { 25 | /** @var array $shopId */ 26 | $shopId = $this->systemConfigService->get(self::SHOP_ID_SYSTEM_CONFIG_KEY); 27 | 28 | if (!$shopId) { 29 | $newShopId = $this->generateShopId(); 30 | $this->systemConfigService->set(self::SHOP_ID_SYSTEM_CONFIG_KEY, [ 31 | 'app_url' => $_SERVER['APP_URL'], 32 | 'value' => $newShopId, 33 | ]); 34 | 35 | return $newShopId; 36 | } 37 | 38 | if ($_SERVER['APP_URL'] !== ($shopId['app_url'] ?? '')) { 39 | $this->systemConfigService->set(self::SHOP_DOMAIN_CHANGE_CONFIG_KEY, true); 40 | /** @var string $appUrl */ 41 | $appUrl = $_SERVER['APP_URL']; 42 | 43 | throw new AppUrlChangeDetectedException($shopId['app_url'], $appUrl); 44 | } 45 | 46 | return $shopId['value']; 47 | } 48 | 49 | private function generateShopId(): string 50 | { 51 | return Random::getAlphanumericString(16); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Core/Content/App/Lifecycle/Registration/HandshakeFactory.php: -------------------------------------------------------------------------------- 1 | shopUrl = $shopUrl; 25 | $this->shopIdProvider = $shopIdProvider; 26 | } 27 | 28 | public function create(Manifest $manifest): AppHandshakeInterface 29 | { 30 | $setup = $manifest->getSetup(); 31 | $privateSecret = $setup->getSecret(); 32 | if ($privateSecret) { 33 | $metadata = $manifest->getMetadata(); 34 | 35 | try { 36 | $shopId = $this->shopIdProvider->getShopId(); 37 | } catch (AppUrlChangeDetectedException $e) { 38 | throw new AppRegistrationException( 39 | 'The app url changed. Please resolve how the apps should handle this change.' 40 | ); 41 | } 42 | 43 | return new PrivateHandshake( 44 | $this->shopUrl, 45 | $privateSecret, 46 | $setup->getRegistrationUrl(), 47 | $metadata->getName(), 48 | $shopId 49 | ); 50 | } 51 | 52 | return new StoreHandshake(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Resources/app/administration/src/app/state/connect-apps.state.js: -------------------------------------------------------------------------------- 1 | export default { 2 | namespaced: true, 3 | 4 | state() { 5 | return { 6 | apps: [], 7 | }; 8 | }, 9 | 10 | getters: { 11 | navigation(state) { 12 | return state.apps.reduce((previousValue, app) => { 13 | previousValue.push(...getNavigationForApps(app)); 14 | return previousValue; 15 | }, []); 16 | }, 17 | }, 18 | 19 | mutations: { 20 | setApps(state, apps) { 21 | state.apps = apps; 22 | }, 23 | }, 24 | 25 | actions: { 26 | fetchAppModules({ commit }) { 27 | const appModulesService = Shopware.Service('AppModulesService'); 28 | return appModulesService.fetchAppModules().then((modules) => { 29 | commit('setApps', modules); 30 | }); 31 | }, 32 | }, 33 | }; 34 | 35 | function getNavigationForApps(app) { 36 | const locale = Shopware.State.get('session').currentLocale; 37 | const fallbackLocale = Shopware.Context.app.fallbackLocale; 38 | 39 | const appLabel = app.label[locale] || app.label[fallbackLocale]; 40 | 41 | return app.modules.map((adminModule) => { 42 | const moduleLabel = adminModule.label[locale] || adminModule.label[fallbackLocale]; 43 | 44 | return { 45 | id: `app-${app.name}-${adminModule.name}`, 46 | path: 'sw.my.apps.index', 47 | params: { appName: app.name, moduleName: adminModule.name }, 48 | label: { 49 | translated: true, 50 | label: `${appLabel} - ${moduleLabel}`, 51 | }, 52 | color: '#9AA8B5', 53 | parent: 'sw-my-apps', 54 | children: [], 55 | }; 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Lifecycle/Registration/_fixtures/no-setup/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | SwagTestApp 6 | 7 | 8 | Test for App System 9 | Test für das App System 10 | shopware AG 11 | (c) by shopware AG 12 | 1.0.0 13 | MIT 14 | 15 | 16 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/Core/Framework/Api/Acl/AclPrivilegeCollection.php: -------------------------------------------------------------------------------- 1 | =6.3 8 | // only available in <=6.2 9 | public const PRIVILEGE_LIST = 'list'; 10 | // only available in <=6.2 11 | public const PRIVILEGE_DETAIL = 'detail'; 12 | // only available in >=6.3 13 | public const PRIVILEGE_READ = 'read'; 14 | public const PRIVILEGE_CREATE = 'create'; 15 | public const PRIVILEGE_UPDATE = 'update'; 16 | public const PRIVILEGE_DELETE = 'delete'; 17 | 18 | /** 19 | * @var array 20 | */ 21 | private $privileges; 22 | 23 | /** 24 | * @param array $privileges 25 | */ 26 | public function __construct(array $privileges = []) 27 | { 28 | $this->privileges = $privileges; 29 | } 30 | 31 | public function isAllowed(string $resource, string $privilege): bool 32 | { 33 | return in_array($resource . ':' . $privilege, $this->privileges, true); 34 | } 35 | 36 | public function count(): int 37 | { 38 | return count($this->privileges); 39 | } 40 | 41 | /** 42 | * @return array> 43 | */ 44 | public function as62Compatible(): array 45 | { 46 | return array_map(static function (string $privilege): array { 47 | $parts = explode(':', $privilege); 48 | 49 | return [ 50 | 'resource' => $parts[0], 51 | 'privilege' => $parts[1], 52 | ]; 53 | }, $this->privileges); 54 | } 55 | 56 | /** 57 | * @return array 58 | */ 59 | public function as63Compatible(): array 60 | { 61 | return $this->privileges; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Core/Framework/Webhook/WebhookDefinition.php: -------------------------------------------------------------------------------- 1 | addFlags(new PrimaryKey(), new Required()), 38 | (new StringField('name', 'name'))->addFlags(new Required()), 39 | (new StringField('event_name', 'eventName', 500))->addFlags(new Required()), 40 | (new StringField('url', 'url', 500))->addFlags(new Required()), 41 | 42 | new FkField('app_id', 'appId', AppDefinition::class), 43 | new ManyToOneAssociationField('app', 'app_id', AppDefinition::class), 44 | ]); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Core/Framework/Template/TemplateLoader.php: -------------------------------------------------------------------------------- 1 | getPath() . '/Resources/views'; 25 | 26 | if (!is_dir($viewDirectory)) { 27 | return []; 28 | } 29 | 30 | $finder = new Finder(); 31 | $finder->files() 32 | ->in($viewDirectory) 33 | ->name('*.html.twig') 34 | ->path(self::ALLOWED_TEMPLATE_DIRS) 35 | ->ignoreUnreadableDirs(); 36 | 37 | return array_values(array_map(static function (\SplFileInfo $file) use ($viewDirectory): string { 38 | // remove viewDirectory + any leading slashes from pathname 39 | return ltrim(mb_substr($file->getPathname(), mb_strlen($viewDirectory)), '/'); 40 | }, iterator_to_array($finder))); 41 | } 42 | 43 | /** 44 | * Returns the content of the template 45 | */ 46 | public function getTemplateContent(string $path, Manifest $app): string 47 | { 48 | $content = @file_get_contents($app->getPath() . '/Resources/views/' . $path); 49 | 50 | if ($content === false) { 51 | throw new \RuntimeException(sprintf('Unable to read file from: %s.', $app->getPath() . '/views/' . $path)); 52 | } 53 | 54 | return $content; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Lifecycle/Event/AppUpdatedEventTest.php: -------------------------------------------------------------------------------- 1 | getAppId()); 25 | static::assertInstanceOf(Manifest::class, $event->getApp()); 26 | static::assertEquals($context, $event->getContext()); 27 | static::assertEquals(AppUpdatedEvent::NAME, $event->getName()); 28 | static::assertEquals([ 29 | 'appVersion' => '1.0.0', 30 | ], $event->getWebhookPayload()); 31 | } 32 | 33 | public function testIsAllowed(): void 34 | { 35 | $appId = Uuid::randomHex(); 36 | $context = Context::createDefaultContext(); 37 | $event = new AppUpdatedEvent( 38 | $appId, 39 | Manifest::createFromXmlFile(__DIR__ . '/../../Manifest/_fixtures/test/manifest.xml'), 40 | $context 41 | ); 42 | 43 | static::assertTrue($event->isAllowed($appId, new AclPrivilegeCollection())); 44 | static::assertFalse($event->isAllowed(Uuid::randomHex(), new AclPrivilegeCollection())); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Core/Content/App/Lifecycle/Event/AppInstalledEventTest.php: -------------------------------------------------------------------------------- 1 | getAppId()); 25 | static::assertInstanceOf(Manifest::class, $event->getApp()); 26 | static::assertEquals($context, $event->getContext()); 27 | static::assertEquals(AppInstalledEvent::NAME, $event->getName()); 28 | static::assertEquals([ 29 | 'appVersion' => '1.0.0', 30 | ], $event->getWebhookPayload()); 31 | } 32 | 33 | public function testIsAllowed(): void 34 | { 35 | $appId = Uuid::randomHex(); 36 | $context = Context::createDefaultContext(); 37 | $event = new AppInstalledEvent( 38 | $appId, 39 | Manifest::createFromXmlFile(__DIR__ . '/../../Manifest/_fixtures/test/manifest.xml'), 40 | $context 41 | ); 42 | 43 | static::assertTrue($event->isAllowed($appId, new AclPrivilegeCollection())); 44 | static::assertFalse($event->isAllowed(Uuid::randomHex(), new AclPrivilegeCollection())); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Core/Framework/Webhook/WebhookEntity.php: -------------------------------------------------------------------------------- 1 | name; 41 | } 42 | 43 | public function setName(string $name): void 44 | { 45 | $this->name = $name; 46 | } 47 | 48 | public function getEventName(): string 49 | { 50 | return $this->eventName; 51 | } 52 | 53 | public function setEventName(string $eventName): void 54 | { 55 | $this->eventName = $eventName; 56 | } 57 | 58 | public function getUrl(): string 59 | { 60 | return $this->url; 61 | } 62 | 63 | public function setUrl(string $url): void 64 | { 65 | $this->url = $url; 66 | } 67 | 68 | public function getAppId(): ?string 69 | { 70 | return $this->appId; 71 | } 72 | 73 | public function setAppId(?string $appId): void 74 | { 75 | $this->appId = $appId; 76 | } 77 | 78 | public function getApp(): ?AppEntity 79 | { 80 | return $this->app; 81 | } 82 | 83 | public function setApp(?AppEntity $app): void 84 | { 85 | $this->app = $app; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Core/Framework/Template/TemplateEntity.php: -------------------------------------------------------------------------------- 1 | template; 41 | } 42 | 43 | public function setTemplate(string $template): void 44 | { 45 | $this->template = $template; 46 | } 47 | 48 | public function getPath(): string 49 | { 50 | return $this->path; 51 | } 52 | 53 | public function setPath(string $path): void 54 | { 55 | $this->path = $path; 56 | } 57 | 58 | public function isActive(): bool 59 | { 60 | return $this->active; 61 | } 62 | 63 | public function setActive(bool $active): void 64 | { 65 | $this->active = $active; 66 | } 67 | 68 | public function getAppId(): string 69 | { 70 | return $this->appId; 71 | } 72 | 73 | public function setAppId(string $appId): void 74 | { 75 | $this->appId = $appId; 76 | } 77 | 78 | public function getApp(): ?AppEntity 79 | { 80 | return $this->app; 81 | } 82 | 83 | public function setApp(?AppEntity $app): void 84 | { 85 | $this->app = $app; 86 | } 87 | } 88 | --------------------------------------------------------------------------------