├── .gitignore ├── .gitlab-ci.ts ├── .gitlab-ci.yml ├── .prettierignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── SUMMARY.md ├── common ├── .env-default ├── .eslintrc ├── Gruntfile.plugin.ts ├── Gruntfile.ts ├── create-wp-react-app │ ├── README.md │ ├── grunt-index-php.tmpl │ └── grunt-readme-txt.tmpl ├── generate-launch-json.ts ├── hookdoc.json ├── jest.base.js ├── jest.setupAfterEnv.js ├── patch-package │ ├── @lerna+conventional-commits+3.22.0.patch │ ├── @lerna+version+3.22.1.patch │ └── README.md ├── php-scope-stub.ts ├── php-scoper.php ├── phpcs.xml ├── phpunit.base.php ├── postcss-plugin-clean.ts ├── stubs.php ├── tsconfig.json ├── webpack-loader-noop.js ├── webpack.factory.ts └── webpack.multi.ts ├── devops ├── .gitlab │ ├── stage-build.ts │ ├── stage-containerize.ts │ ├── stage-release.ts │ ├── stage-test.ts │ └── stage-validate.ts ├── docker-compose │ ├── docker-compose.e2e.yml │ ├── docker-compose.local.yml │ ├── docker-compose.traefik.yml │ └── docker-compose.yml ├── docker │ └── gitlab-ci │ │ └── Dockerfile └── scripts │ ├── container-wordpress-cli-entrypoint.sh │ ├── container-wordpress-command.sh │ ├── custom-php.ini │ ├── e2e-tests-autologin-plugin.php │ ├── lerna-ready-ci.sh │ ├── purge-ci.sh │ ├── task-xdebug-start.sh │ ├── task-xdebug-stop.sh │ └── wordpress-startup.sh ├── docs ├── advanced │ ├── build-production-plugin.md │ ├── create-add-on.md │ ├── create-package.md │ ├── extend-compose-webpack.md │ ├── how-cachebuster-works.md │ ├── license-checker.md │ ├── persistent-database-snapshot.md │ ├── showcase.md │ └── tests.md ├── gitlab-integration │ ├── deploy-wp-org.md │ ├── extend-gitlab-ci-pipeline.md │ ├── predefined-pipeline.md │ ├── review-applications.md │ └── use-own-runner.md ├── php-development │ ├── add-classes-hooks-libraries.md │ ├── debugging.md │ ├── example-implementations.md │ ├── localization.md │ ├── predefined-classes.md │ └── predefined-constants.md ├── typescript-development │ ├── add-external-library.md │ ├── consume-php-variable.md │ ├── example-implementations.md │ ├── localization.md │ ├── using-entrypoints.md │ └── utils-package.md └── usage │ ├── available-commands │ ├── index.md │ ├── package.md │ ├── plugin.md │ └── root.md │ ├── folder-structure │ ├── index.md │ ├── plugin.md │ └── root.md │ └── getting-started.md ├── package.json ├── packages └── utils │ ├── CHANGELOG.md │ ├── LICENSE │ ├── LICENSE_3RD_PARTY_JS.md │ ├── LICENSE_3RD_PARTY_PHP.md │ ├── README.md │ ├── composer.json │ ├── composer.lock │ ├── devops │ └── .gitlab │ │ ├── .gitlab-ci.ts │ │ ├── stage-build.ts │ │ ├── stage-test.ts │ │ └── stage-validate.ts │ ├── languages │ ├── backend │ │ ├── utils-de_DE.mo │ │ ├── utils-de_DE.po │ │ └── utils.pot │ └── frontend │ │ └── utils.pot │ ├── lib │ ├── components │ │ ├── button.tsx │ │ ├── index.tsx │ │ └── notice.tsx │ ├── factory │ │ ├── ajax │ │ │ ├── commonRequest.tsx │ │ │ ├── commonUrlBuilder.tsx │ │ │ ├── corruptRestApi.tsx │ │ │ ├── createRequestFactory.tsx │ │ │ ├── index.tsx │ │ │ ├── parseResult.tsx │ │ │ └── routeHttpVerbEnum.tsx │ │ ├── context.tsx │ │ ├── i18n.tsx │ │ └── index.tsx │ ├── helpers.tsx │ ├── index.tsx │ ├── options.tsx │ ├── types │ │ └── global.d.ts │ └── wp-api │ │ ├── index.tsx │ │ └── rest.plugin.get.tsx │ ├── package.json │ ├── scripts │ ├── Gruntfile.ts │ └── webpack.config.ts │ ├── src │ ├── Activator.php │ ├── Assets.php │ ├── Base.php │ ├── Core.php │ ├── Localization.php │ ├── PackageLocalization.php │ ├── PluginReceiver.php │ └── Service.php │ ├── test │ ├── jest.config.js │ ├── jest │ │ ├── __mocks__ │ │ │ └── wp.tsx │ │ ├── components │ │ │ ├── __snapshots__ │ │ │ │ ├── button.test.tsx.snap │ │ │ │ └── notice.test.tsx.snap │ │ │ ├── button.test.tsx │ │ │ └── notice.test.tsx │ │ ├── factory │ │ │ ├── ajax │ │ │ │ ├── commonRequest.test.tsx │ │ │ │ ├── commonUrlBuilder.test.tsx │ │ │ │ ├── corrupRestApi.test.tsx │ │ │ │ ├── createRequestFactory.test.tsx │ │ │ │ └── parseResult.test.tsx │ │ │ └── i18n.test.tsx │ │ ├── helpers.test.tsx │ │ ├── helpers │ │ │ ├── index.tsx │ │ │ └── provider.tsx │ │ └── options.test.tsx │ ├── patchwork.json │ ├── phpunit.bootstrap.php │ ├── phpunit.xdebug.php │ ├── phpunit.xml │ └── phpunit │ │ ├── ActivatorTest.php │ │ ├── AssetsTest.php │ │ ├── BaseTest.php │ │ ├── CoreTest.php │ │ ├── LocalizationTest.php │ │ ├── PackageLocalizationTest.php │ │ ├── PluginReceiverTest.php │ │ └── ServiceTest.php │ └── tsconfig.json ├── plugins └── wp-reactjs-starter │ ├── CHANGELOG.md │ ├── LICENSE │ ├── LICENSE_3RD_PARTY_JS.md │ ├── LICENSE_3RD_PARTY_PHP.md │ ├── composer.json │ ├── composer.lock │ ├── cypress.json │ ├── devops │ ├── .gitlab │ │ ├── .gitlab-ci.ts │ │ ├── stage-build-production.ts │ │ ├── stage-build.ts │ │ ├── stage-deploy.ts │ │ ├── stage-test.ts │ │ └── stage-validate.ts │ ├── docker-compose │ │ ├── docker-compose.e2e.yml │ │ ├── docker-compose.local.yml │ │ ├── docker-compose.traefik.yml │ │ └── docker-compose.yml │ └── scripts │ │ └── wordpress-startup.sh │ ├── package.json │ ├── scripts │ ├── Gruntfile.ts │ └── webpack.config.ts │ ├── src │ ├── inc │ │ ├── Activator.php │ │ ├── Assets.php │ │ ├── Core.php │ │ ├── Localization.php │ │ ├── base │ │ │ ├── Core.php │ │ │ ├── UtilsProvider.php │ │ │ ├── index.php │ │ │ └── others │ │ │ │ ├── fallback-php-version.php │ │ │ │ ├── fallback-rest-api.php │ │ │ │ ├── fallback-wp-version.php │ │ │ │ ├── index.php │ │ │ │ └── start.php │ │ ├── index.php │ │ ├── rest │ │ │ ├── HelloWorld.php │ │ │ └── index.php │ │ └── view │ │ │ ├── index.php │ │ │ ├── menu │ │ │ ├── Page.php │ │ │ └── index.php │ │ │ └── widget │ │ │ ├── Widget.php │ │ │ └── index.php │ ├── index.php │ ├── languages │ │ ├── wp-reactjs-starter-de_DE.mo │ │ ├── wp-reactjs-starter-de_DE.po │ │ └── wp-reactjs-starter.pot │ ├── public │ │ ├── languages │ │ │ ├── json │ │ │ │ ├── wp-reactjs-starter-de_DE-1458bc3eb855dd3b40a27bc171a5aed9.json │ │ │ │ └── wp-reactjs-starter-de_DE-d6c7c71371fa4fbe7cc75f0a20f23d0e.json │ │ │ ├── wp-reactjs-starter-de_DE.mo │ │ │ ├── wp-reactjs-starter-de_DE.po │ │ │ └── wp-reactjs-starter.pot │ │ └── ts │ │ │ ├── admin.tsx │ │ │ ├── components │ │ │ ├── index.tsx │ │ │ ├── page.tsx │ │ │ ├── todo.tsx │ │ │ └── todoItem.tsx │ │ │ ├── models │ │ │ ├── index.tsx │ │ │ └── todoModel.tsx │ │ │ ├── store │ │ │ ├── index.tsx │ │ │ ├── option.tsx │ │ │ ├── stores.tsx │ │ │ └── todo.tsx │ │ │ ├── style │ │ │ ├── admin.scss │ │ │ └── widget.scss │ │ │ ├── types │ │ │ └── global.d.ts │ │ │ ├── utils │ │ │ └── index.tsx │ │ │ ├── widget.tsx │ │ │ ├── widget │ │ │ └── index.tsx │ │ │ └── wp-api │ │ │ ├── hello.get.tsx │ │ │ └── index.tsx │ └── uninstall.php │ ├── test │ ├── cypress │ │ ├── integration │ │ │ └── adminPage.feature │ │ ├── plugins │ │ │ └── index.ts │ │ ├── step-definitions │ │ │ ├── adminPage │ │ │ │ ├── AdminPageObject.ts │ │ │ │ └── adminPage.ts │ │ │ └── common │ │ │ │ ├── common.ts │ │ │ │ └── index.ts │ │ ├── support │ │ │ ├── commands.ts │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── jest.config.js │ ├── jest │ │ └── store │ │ │ ├── __mocks__ │ │ │ └── wp.tsx │ │ │ ├── option.test.tsx │ │ │ └── stores.test.tsx │ ├── patchwork.json │ ├── phpunit.bootstrap.php │ ├── phpunit.xdebug.php │ ├── phpunit.xml │ └── phpunit │ │ ├── ActivatorTest.php │ │ ├── AssetsTest.php │ │ ├── CoreTest.php │ │ ├── LocalizationTest.php │ │ └── base │ │ └── CoreTest.php │ ├── tsconfig.json │ └── wordpress.org │ ├── README.wporg.txt │ └── assets │ ├── banner-1544x500.png │ ├── banner-772x250.png │ ├── icon-128x128.png │ ├── icon-256x256.png │ └── screenshot-1.png └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # WordPress plugin 2 | plugins/*/src/public/dist 3 | plugins/*/src/public/dev 4 | plugins/*/src/public/lib 5 | plugins/*/src/inc/base/others/cachebuster* 6 | plugins/*/build 7 | plugins/*/docs 8 | plugins/*/wporg 9 | 10 | # Packages 11 | packages/*/dist 12 | packages/*/dev 13 | 14 | # Tests 15 | packages/*/coverage 16 | packages/*/test/junit 17 | plugins/*/coverage 18 | plugins/*/test/junit 19 | plugins/*/test/cypress/videos 20 | plugins/*/test/cypress/screenshots 21 | 22 | # Misc 23 | .publish 24 | 25 | # Logs 26 | logs 27 | *.log 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | *.pid.lock 37 | 38 | # Dependency directories 39 | node_modules 40 | vendor 41 | vendor-temp 42 | 43 | # Cache directories 44 | .npm 45 | .eslintcache 46 | .yarn-integrity 47 | 48 | # dotenv environment variables file 49 | .env -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # CI pipeline is dynamically created through `node-gitlab-ci`, please checkout `.gitlab-ci.ts`! 2 | 3 | ts config: 4 | image: devowliode/node-gitlab-ci:latest 5 | stage: build 6 | script: node-gitlab-ci create-yml 7 | artifacts: 8 | paths: 9 | - .gitlab-ci.ts.yml 10 | 11 | trigger pipeline: 12 | stage: test 13 | trigger: 14 | strategy: depend 15 | include: 16 | - artifact: .gitlab-ci.ts.yml 17 | job: ts config 18 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | common/create-wp-react-app 2 | **/CHANGELOG.md 3 | **/*.lock 4 | plugins/*/languages 5 | plugins/*/public/languages 6 | plugins/*/build 7 | **/LICENSE* -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "valeryanm.vscode-phpsab", 4 | "felixfbecker.php-debug", 5 | "esbenp.prettier-vscode", 6 | "dbaeumer.vscode-eslint", 7 | "bmewburn.vscode-intelephense-client", 8 | "mrorz.language-gettext", 9 | "steoates.autoimport", 10 | "ms-azuretools.vscode-docker", 11 | "stringham.move-ts", 12 | "streetsidesoftware.code-spell-checker", 13 | "alexkrechik.cucumberautocomplete" 14 | ], 15 | "unwantedRecommendations": ["vscode.php-language-features"] 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Listen for XDebug", 9 | "type": "php", 10 | "request": "launch", 11 | "port": 9000, 12 | "preLaunchTask": "Start xdebug in WordPress container", 13 | "postDebugTask": "Stop xdebug in WordPress container", 14 | "pathMappings": { 15 | // Do not remove that because it is used in `../common/generate-launch-json.js` 16 | // create-wp-react-app --> 17 | "/var/www/html/wp-content/plugins/wp-reactjs-starter": "${workspaceFolder}/plugins/wp-reactjs-starter/src", 18 | "/var/www/html/wp-content/packages/utils/src": "${workspaceFolder}/packages/utils/src" 19 | // <-- create-wp-react-app 20 | }, 21 | "xdebugSettings": { 22 | "max_data": 65535, 23 | "show_hidden": 1, 24 | "max_children": 100, 25 | "max_depth": 5 26 | } 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Cucumber 3 | "cucumberautocomplete.steps": ["plugins/*/test/cypress/step-definitions/**/*.ts"], 4 | "cucumberautocomplete.syncfeatures": "plugins/*/test/cypress/integration/*feature", 5 | "cucumberautocomplete.strictGherkinCompletion": true, 6 | // cSpell 7 | "cSpell.enabled": true, 8 | // ESLint 9 | "eslint.enable": true, 10 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], 11 | // PHP CodeSniffer 12 | "phpsab.composerJsonPath": "packages/utils/composer.json", // use utils package as binary because its the first available package (composer does not support hoisting for monorepos) 13 | "phpsab.fixerEnable": true, // disable automatic fixer because it is done by runOnSave 14 | "phpsab.snifferEnable": true, 15 | "phpsab.snifferMode": "onType", 16 | "phpsab.snifferTypeDelay": 500, 17 | "phpsab.standard": "common/phpcs.xml", 18 | // Editor 19 | "editor.useTabStops": false, 20 | "editor.formatOnSave": false, 21 | "editor.tabSize": 4, 22 | "editor.defaultFormatter": "esbenp.prettier-vscode", 23 | "editor.formatOnSaveTimeout": 3000, // Increase because phpcs can take longer 24 | // Prettier 25 | "prettier.prettierPath": "./node_modules/prettier", 26 | // Intelephense 27 | "intelephense.files.maxSize": 4000000, // increase max file size so WordPress sub is considered, too 28 | // Per language 29 | "[typescript]": { 30 | "editor.formatOnSave": true, 31 | "editor.codeActionsOnSave": { 32 | "source.fixAll.eslint": true 33 | } 34 | }, 35 | "[typescriptreact]": { 36 | "editor.formatOnSave": true, 37 | "editor.codeActionsOnSave": { 38 | "source.fixAll.eslint": true 39 | } 40 | }, 41 | "[javascript]": { 42 | "editor.formatOnSave": true, 43 | "editor.codeActionsOnSave": { 44 | "source.fixAll.eslint": true 45 | } 46 | }, 47 | "[jsonc]": { 48 | "editor.formatOnSave": true, 49 | "editor.codeActionsOnSave": { 50 | "source.fixAll.eslint": true 51 | } 52 | }, 53 | "[php]": { 54 | "editor.defaultFormatter": "valeryanm.vscode-phpsab", 55 | "editor.formatOnSave": true 56 | }, 57 | "[markdown]": { 58 | "editor.formatOnSave": true 59 | }, 60 | "[json]": { 61 | "editor.formatOnSave": true 62 | }, 63 | "[yaml]": { 64 | "editor.formatOnSave": true 65 | }, 66 | "[xml]": { 67 | "editor.formatOnSave": true 68 | }, 69 | // Hide additional files/folders from sidebar because they are caused through Docker volumes 70 | "files.exclude": { 71 | "**/.git": true, 72 | "**/.DS_Store": true, 73 | "devops/scripts/plugins/": true, 74 | "plugins/*/src/vendor/": true 75 | }, 76 | // Exclude files from watcher, it can be expensive 77 | "files.watcherExclude": { 78 | "**/.git/objects/**": true, 79 | "**/.git/subtree-cache/**": true, 80 | "**/node_modules/*/**": true, 81 | "**/vendor/*/**": true, 82 | "**/vendor-temp/*/**": true 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Start xdebug in WordPress container", 8 | "command": "${workspaceFolder}/devops/scripts/task-xdebug-start.sh", 9 | "presentation": { 10 | "reveal": "never", 11 | "showReuseMessage": false 12 | } 13 | }, 14 | { 15 | "label": "Stop xdebug in WordPress container", 16 | "command": "${workspaceFolder}/devops/scripts/task-xdebug-stop.sh", 17 | "presentation": { 18 | "reveal": "never", 19 | "showReuseMessage": false 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The software "WP React Starter" is comprised of multiple parts 2 | (files in these directories and subdirectories, if the subdirectory 3 | be under no other licence): 4 | 5 | /: ISC License (ISC) 6 | /.vscode: ISC License (ISC) 7 | /common: ISC License (ISC) 8 | /devops: ISC License (ISC) 9 | /docs: ISC License (ISC) 10 | /packages/utils: GNU General Public License v3.0 or later (GPL-3.0-or-later) 11 | /plugins/wp-reactjs-starter: GNU General Public License v3.0 or later (GPL-3.0-or-later) 12 | 13 | You can find a copy of both licenses at: 14 | 15 | ISC License: https://opensource.org/licenses/ISC 16 | GNU General Public License v3.0: https://www.gnu.org/licenses/gpl-3.0.en.html 17 | 18 | In practice, this means that the build of the WordPress plugins 19 | that you create with WP React Starter is only licensed under 20 | GNU General Public License v3.0 or later. Most or all devtools 21 | are licensed under the ISC license. 22 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | ## Usage 4 | 5 | - [Getting started](docs/usage/getting-started.md) 6 | - [Folder structure](docs/usage/folder-structure/index.md) 7 | - [Root](docs/usage/folder-structure/root.md) 8 | - [Plugin](docs/usage/folder-structure/plugin.md) 9 | - [Available commands](docs/usage/available-commands/index.md) 10 | - [Root](docs/usage/available-commands/root.md) 11 | - [Plugin](docs/usage/available-commands/plugin.md) 12 | - [Package](docs/usage/available-commands/package.md) 13 | 14 | ## PHP development 15 | 16 | - [Predefined constants](docs/php-development/predefined-constants.md) 17 | - [Predefined classes](docs/php-development/predefined-classes.md) 18 | - [Example implementations](docs/php-development/example-implementations.md) 19 | - [Add new classes, hooks and libraries](docs/php-development/add-classes-hooks-libraries.md) 20 | - [Localization](docs/php-development/localization.md) 21 | - [Debugging](docs/php-development/debugging.md) 22 | 23 | ## TypeScript development 24 | 25 | - [Utils package](docs/typescript-development/utils-package.md) 26 | - [Example implementations](docs/typescript-development/example-implementations.md) 27 | - [Add external library](docs/typescript-development/add-external-library.md) 28 | - [Consume PHP variable](docs/typescript-development/consume-php-variable.md) 29 | - [Using entrypoints](docs/typescript-development/using-entrypoints.md) 30 | - [Localization](docs/typescript-development/localization.md) 31 | 32 | ## Advanced 33 | 34 | - [Build production plugin](docs/advanced/build-production-plugin.md) 35 | - [How cachebuster works](docs/advanced/how-cachebuster-works.md) 36 | - [Tests](docs/advanced/tests.md) 37 | - [Extend Compose and Webpack](docs/advanced/extend-compose-webpack.md) 38 | - [Create package](docs/advanced/create-package.md) 39 | - [Create Add-On](docs/advanced/create-add-on.md) 40 | - [Persistent database snapshot](docs/advanced/persistent-database-snapshot.md) 41 | - [Showcase](docs/advanced/showcase.md) 42 | - [License checker](docs/advanced/license-checker.md) 43 | 44 | ## GitLab integration 45 | 46 | - [Predefined pipeline](docs/gitlab-integration/predefined-pipeline.md) 47 | - [Extend GitLab CI pipeline](docs/gitlab-integration/extend-gitlab-ci-pipeline.md) 48 | - [Use own runner](docs/gitlab-integration/use-own-runner.md) 49 | - [Review applications](docs/gitlab-integration/review-applications.md) 50 | - [Deploy wordpress.org](docs/gitlab-integration/deploy-wp-org.md) 51 | -------------------------------------------------------------------------------- /common/.env-default: -------------------------------------------------------------------------------- 1 | WP_LOCAL_INSTALL_URL= 2 | -------------------------------------------------------------------------------- /common/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": ["eslint:recommended", "plugin:react/recommended", "prettier", "plugin:prettier/recommended"], 8 | "parserOptions": { 9 | "ecmaFeatures": { 10 | "jsx": true 11 | }, 12 | "ecmaVersion": 2018, 13 | "sourceType": "module" 14 | }, 15 | "settings": { 16 | "react": { 17 | "version": "detect" 18 | } 19 | }, 20 | "overrides": [ 21 | { 22 | "files": ["**/*.{tsx,ts}"], 23 | "extends": [ 24 | "eslint:recommended", 25 | "plugin:react/recommended", 26 | "prettier", 27 | "plugin:prettier/recommended", 28 | "plugin:@typescript-eslint/eslint-recommended", 29 | "plugin:@typescript-eslint/recommended" 30 | ], 31 | "parser": "@typescript-eslint/parser", 32 | "plugins": ["react", "@typescript-eslint", "import"], 33 | "rules": { 34 | "one-var": ["error", "never"], 35 | "react/react-in-jsx-scope": 0, 36 | "react/prop-types": 0, 37 | "prefer-arrow-callback": "error", 38 | "prefer-destructuring": "warn", 39 | "prefer-template": "warn", 40 | "import/no-extraneous-dependencies": "error", 41 | "import/no-default-export": "warn", 42 | //"sort-imports": "warn", 43 | "@typescript-eslint/no-explicit-any": 0, 44 | "@typescript-eslint/explicit-function-return-type": 0, 45 | "@typescript-eslint/explicit-module-boundary-types": 0, 46 | "@typescript-eslint/ban-ts-ignore": 0, 47 | "@typescript-eslint/member-ordering": "error", 48 | "@typescript-eslint/ban-types": [ 49 | "error", 50 | { 51 | "types": { 52 | "{}": false 53 | }, 54 | "extendDefaults": true 55 | } 56 | ] 57 | } 58 | }, 59 | { 60 | "files": ["**/jest/**/*.test.{tsx,ts}"], 61 | "plugins": ["jest"], 62 | "extends": ["plugin:jest/recommended", "plugin:jest/style"], 63 | "rules": { 64 | "@typescript-eslint/ban-ts-comment": 0 65 | } 66 | }, 67 | { 68 | "files": ["**/*.test.{tsx,ts}", "**/cypress/plugins/*"], 69 | "rules": { 70 | "@typescript-eslint/no-var-requires": 0 71 | } 72 | } 73 | //{ 74 | // // Those who use plain JavaScript have lost control over their lives ;-) 75 | // "files": ["**/*.{jsx,js}"], 76 | // "extends": ["eslint:recommended", "plugin:react/recommended", "prettier", "plugin:prettier/recommended"], 77 | // "rules": {} 78 | //} 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /common/create-wp-react-app/README.md: -------------------------------------------------------------------------------- 1 | # create-wp-react-app folder 2 | 3 | This folder is required and should be committed to your git repository. It is used for further cli commands like create-wp-react-app create-plugin. 4 | -------------------------------------------------------------------------------- /common/create-wp-react-app/grunt-index-php.tmpl: -------------------------------------------------------------------------------- 1 | =') ? 'start.php' : 'fallback-php-version.php'); 44 | -------------------------------------------------------------------------------- /common/create-wp-react-app/grunt-readme-txt.tmpl: -------------------------------------------------------------------------------- 1 | === ${pluginName} === 2 | Contributors: ${author} 3 | Tags: 4 | Donate link: 5 | Stable tag: trunk 6 | Requires at least: ${minWp} 7 | Requires PHP: ${minPhp} 8 | Tested up to: 5.2 9 | License: GPLv2 10 | License: 11 | License URI: 12 | 13 | ${pluginDesc} 14 | 15 | == Description == 16 | 17 | Put your long description here. 18 | 19 | == Installation == 20 | 21 | 1. Goto your wordpress backend 22 | 2. Navigate to Plugins > Add new 23 | 3. Search for "${pluginName}" 24 | 4. "Install" 25 | 26 | OR 27 | 28 | 1. Download the plugin from this site (wordpress.org) 29 | 2. Copy the extracted folder into your `wp-content/plugins` folder 30 | 3. Activate the "${pluginName}" plugin via the plugins admin page 31 | 32 | == Frequently Asked Questions == 33 | 34 | 35 | == Screenshots == 36 | 37 | 38 | == Changelog == 39 | [include:CHANGELOG.md] 40 | 41 | == Upgrade Notice == 42 | 43 | -------------------------------------------------------------------------------- /common/generate-launch-json.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is used in yarn debug:php:generate and generates the launch.json `pathMapping` 3 | * for xdebug. Usually you do not have to use this script because it is used through create-wp-react-app. 4 | */ 5 | 6 | import { resolve } from "path"; 7 | import { readdirSync, readFileSync, writeFileSync } from "fs"; 8 | 9 | // Read plugins 10 | async function generate() { 11 | const pathMapping = {} as any; 12 | 13 | // Generate plugins' path mapping 14 | const plugins = readdirSync(resolve(process.env.PWD, "plugins")); 15 | plugins.forEach( 16 | (plugin) => 17 | (pathMapping[`/var/www/html/wp-content/plugins/${plugin}`] = `\${workspaceFolder}/plugins/${plugin}/src`) 18 | ); 19 | 20 | // Generate packages' path mappings 21 | const packages = readdirSync(resolve(process.env.PWD, "packages")); 22 | packages.forEach( 23 | (packagee) => 24 | (pathMapping[ 25 | `/var/www/html/wp-content/packages/${packagee}/src` 26 | ] = `\${workspaceFolder}/packages/${packagee}/src`) 27 | ); 28 | 29 | // Regenerate launch.json 30 | const launchJsonPath = resolve(process.env.PWD, ".vscode/launch.json"); 31 | let launchJson = readFileSync(launchJsonPath, { encoding: "UTF-8" }); 32 | let stringedPathMapping = JSON.stringify(pathMapping).substr(1); 33 | stringedPathMapping = stringedPathMapping.substring(0, stringedPathMapping.length - 1); 34 | launchJson = launchJson.replace( 35 | /(\/\/ create-wp-react-app -->\n)(.*)(\/\/ <-- create-wp-react-app)/gms, 36 | `$1${stringedPathMapping}\n$3` 37 | ); 38 | 39 | writeFileSync(launchJsonPath, launchJson, { encoding: "UTF-8" }); 40 | } 41 | 42 | generate(); 43 | -------------------------------------------------------------------------------- /common/hookdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "opts": { 3 | "destination": "docs/hooks", 4 | "template": "../node_modules/wp-hookdoc/template", 5 | "recurse": true 6 | }, 7 | "source": { 8 | "includePattern": ".+\\.(php|inc)?$" 9 | }, 10 | "plugins": ["wp-hookdoc/plugin"], 11 | "templates": { 12 | "cleverLinks": false, 13 | "monospaceLinks": false, 14 | "default": { 15 | "outputSourceFiles": true, 16 | "outputSourcePath": true 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /common/jest.base.js: -------------------------------------------------------------------------------- 1 | // Base jest configuration (https://jestjs.io/docs/en/configuration#coveragedirectory-string) 2 | 3 | const path = require("path"); 4 | 5 | const pkg = require(path.resolve(process.env.PWD, "package.json")); 6 | const isPlugin = !!pkg.slug; 7 | const rootName = pkg.name.match(/^@(.*)\//)[1]; 8 | 9 | module.exports = { 10 | roots: ["/test/jest/"].concat(isPlugin ? ["/src/public/ts"] : ["/lib"]), 11 | testRegex: "(/test/jest/.*(\\.|/)(test|spec))\\.tsx$", 12 | setupFilesAfterEnv: ["/../../common/jest.setupAfterEnv.js"], 13 | transform: { 14 | "^.+\\.tsx?$": [ 15 | "babel-jest", 16 | { 17 | babelrc: true, 18 | babelrcRoots: [ 19 | ".", // Keep the root as a root 20 | // Also consider monorepo packages "root" and load their .babelrc files. 21 | "../../packages/*", 22 | "../../plugins/*" 23 | ] 24 | } 25 | ] 26 | }, 27 | transformIgnorePatterns: ["node_modules/(?!@" + rootName + ")"], 28 | collectCoverage: false, 29 | reporters: [ 30 | "default", 31 | [ 32 | "jest-junit", 33 | { 34 | outputDirectory: "./test/junit", 35 | outputName: "jest.xml" 36 | } 37 | ] 38 | ], 39 | coverageDirectory: "/coverage/jest", 40 | collectCoverageFrom: [ 41 | "/lib/**/*.{tsx,ts}", // Packages 42 | "/src/public/ts/**/*.{tsx,ts}", // Plugins 43 | "!**/wp-api/*.{get,post,patch,put,delete,option,head}.{tsx,ts}" 44 | ], 45 | coverageThreshold: { 46 | global: { 47 | lines: 80 48 | } 49 | }, 50 | snapshotSerializers: [], 51 | moduleNameMapper: { 52 | "\\.(css|less|scss)$": "identity-obj-proxy" 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /common/jest.setupAfterEnv.js: -------------------------------------------------------------------------------- 1 | /* global afterEach jest */ 2 | 3 | // Make current executing package available as environment variable 4 | process.env.PACKAGE_SCOPE = process.env.npm_package_name.split("/")[0]; 5 | 6 | // Clear all mocks after each test 7 | afterEach(() => { 8 | jest.clearAllMocks(); 9 | }); 10 | -------------------------------------------------------------------------------- /common/patch-package/@lerna+conventional-commits+3.22.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@lerna/conventional-commits/lib/update-changelog.js b/node_modules/@lerna/conventional-commits/lib/update-changelog.js 2 | index e62a332..010b960 100644 3 | --- a/node_modules/@lerna/conventional-commits/lib/update-changelog.js 4 | +++ b/node_modules/@lerna/conventional-commits/lib/update-changelog.js 5 | @@ -11,7 +11,7 @@ const readExistingChangelog = require("./read-existing-changelog"); 6 | 7 | module.exports = updateChangelog; 8 | 9 | -function updateChangelog(pkg, type, { changelogPreset, rootPath, tagPrefix = "v", version }) { 10 | +function updateChangelog(pkg, type, { changelogPreset, conventionalChangelog = {}, rootPath, tagPrefix = "v", version }) { 11 | log.silly(type, "for %s at %s", pkg.name, pkg.location); 12 | 13 | return getChangelogConfig(changelogPreset, rootPath).then(config => { 14 | @@ -55,7 +55,13 @@ function updateChangelog(pkg, type, { changelogPreset, rootPath, tagPrefix = "v" 15 | } 16 | 17 | // generate the markdown for the upcoming release. 18 | - const changelogStream = conventionalChangelogCore(options, context, gitRawCommitsOpts); 19 | + const changelogStream = conventionalChangelogCore( 20 | + Object.assign(options, conventionalChangelog.options), 21 | + Object.assign(context, conventionalChangelog.context), 22 | + Object.assign(gitRawCommitsOpts, conventionalChangelog.gitRawCommitsOpts), 23 | + conventionalChangelog.parserOpts, 24 | + conventionalChangelog.writerOpts 25 | + ); 26 | 27 | return Promise.all([ 28 | getStream(changelogStream).then(makeBumpOnlyFilter(pkg)), 29 | -------------------------------------------------------------------------------- /common/patch-package/@lerna+version+3.22.1.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@lerna/version/index.js b/node_modules/@lerna/version/index.js 2 | index 30836fe..dc8e232 100644 3 | --- a/node_modules/@lerna/version/index.js 4 | +++ b/node_modules/@lerna/version/index.js 5 | @@ -493,7 +493,7 @@ class VersionCommand extends Command { 6 | } 7 | 8 | updatePackageVersions() { 9 | - const { conventionalCommits, changelogPreset, changelog = true } = this.options; 10 | + const { conventionalCommits, changelogPreset, conventionalChangelog, changelog = true } = this.options; 11 | const independentVersions = this.project.isIndependent(); 12 | const rootPath = this.project.manifest.location; 13 | const changedFiles = new Set(); 14 | @@ -551,6 +551,7 @@ class VersionCommand extends Command { 15 | actions.push(pkg => 16 | ConventionalCommitUtilities.updateChangelog(pkg, type, { 17 | changelogPreset, 18 | + conventionalChangelog, 19 | rootPath, 20 | tagPrefix: this.tagPrefix, 21 | }).then(({ logPath, newEntry }) => { 22 | @@ -586,6 +587,7 @@ class VersionCommand extends Command { 23 | chain = chain.then(() => 24 | ConventionalCommitUtilities.updateChangelog(this.project.manifest, "root", { 25 | changelogPreset, 26 | + conventionalChangelog, 27 | rootPath, 28 | tagPrefix: this.tagPrefix, 29 | version: this.globalVersion, 30 | -------------------------------------------------------------------------------- /common/patch-package/README.md: -------------------------------------------------------------------------------- 1 | # Patches 2 | 3 | The tool [`patch-package`](https://github.com/ds300/patch-package) allows to override coding in dependencies. 4 | 5 | ## `@lerna/version` and `@lerna/conventional-commits` 6 | 7 | This patch allows to pass a `conventionalChangelog` object in `lerna.json`. See also this: https://github.com/lerna/lerna/pull/2343 8 | 9 | You can customize the changelog generation ([reference](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-core#conventionalchangelogcoreoptions-context-gitrawcommitsopts-parseropts-writeropts)) in your `lerna.json`: 10 | 11 | ```json 12 | { 13 | "command": { 14 | "version": { 15 | "conventionalCommits": true, 16 | "conventionalChangelog": { 17 | "options": {}, 18 | "context": {}, 19 | "gitRawCommitsOpts": {}, 20 | "parserOpts": {}, 21 | "writerOpts": {} 22 | } 23 | } 24 | } 25 | }, 26 | ``` 27 | -------------------------------------------------------------------------------- /common/php-scope-stub.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "fs"; 2 | import phpParser from "php-parser"; 3 | 4 | const ALLOWED = ["class", "interface", "function", "trait"]; 5 | 6 | function findKinds(obj: any, key: string, namespace = "") { 7 | let list: any[] = []; 8 | if (!obj) return list; 9 | if (obj instanceof Array) { 10 | for (const i in obj) { 11 | list = list.concat(findKinds(obj[i], key, namespace)); 12 | } 13 | return list; 14 | } 15 | 16 | if (ALLOWED.indexOf(obj[key]) > -1 && obj.name) list.push({ ...obj.name, inNamespace: namespace }); 17 | 18 | if (typeof obj == "object" && obj !== null) { 19 | const children = Object.keys(obj); 20 | if (children.length > 0) { 21 | // Correctly set namespace for next children 22 | const appendNamespace = 23 | obj.kind === "namespace" && typeof obj.name === "string" 24 | ? `${obj.name.split("\\").filter(Boolean).join("\\")}\\` 25 | : namespace; 26 | for (let i = 0; i < children.length; i++) { 27 | list = list.concat(findKinds(obj[children[i]], key, appendNamespace)); 28 | } 29 | } 30 | } 31 | return list; 32 | } 33 | 34 | /** 35 | * Due to the fact that php-scoper does not support external global dependencies like WordPress 36 | * functions and classes we need to whitelist them. The best approach is to use the already 37 | * used stubs. Stubs are needed for PHP Intellisense so they need to be up2date, too. 38 | * 39 | * @see https://github.com/humbug/php-scoper/issues/303 40 | * @see https://github.com/humbug/php-scoper/issues/378 41 | */ 42 | function extractGlobalStubIdentifiers(files: string[]) { 43 | const result: string[] = []; 44 | 45 | const parser = new phpParser({ 46 | parser: { 47 | extractDoc: true 48 | }, 49 | ast: { 50 | withSource: false, 51 | withPositions: false 52 | } 53 | }); 54 | 55 | for (const file of files) { 56 | const parsed = parser.parseCode(readFileSync(file, { encoding: "UTF-8" })); 57 | result.push( 58 | ...findKinds(parsed, "kind").map((id: { name: string; inNamespace: string }) => id.inNamespace + id.name) 59 | ); 60 | } 61 | 62 | return result; 63 | } 64 | 65 | export { extractGlobalStubIdentifiers }; 66 | -------------------------------------------------------------------------------- /common/postcss-plugin-clean.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | /** 3 | * Additional PostCSS plugin for clean-css. 4 | */ 5 | 6 | import postcss from "postcss"; 7 | import CleanCss from "clean-css"; 8 | 9 | // Inspired by postcss-clean, use CJS export way because the other one does not work as expected 10 | module.exports = postcss.plugin("clean-css", (options = {}) => { 11 | const cleaner = new CleanCss(options); 12 | return (css, res) => 13 | new Promise((resolve, reject) => 14 | cleaner.minify(css.toString(), (error, result) => { 15 | if (error) { 16 | return reject(new Error(error)); 17 | } 18 | 19 | for (const w of result.warnings) { 20 | res.warn(w); 21 | } 22 | 23 | res.root = postcss.parse(result.styles); 24 | resolve(); 25 | }) 26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /common/stubs.php: -------------------------------------------------------------------------------- 1 | dep.startsWith(`@${rootName}/`)) 20 | .map((dep) => join(rootCwd, "packages", dep.split("/")[1])) 21 | .concat([cwd]) 22 | : undefined; 23 | 24 | if (buildPlugin) { 25 | console.log( 26 | "You are currently building a plugin, please consider to put your webpack:done actions after the `yarn build` command for performance reasons!" 27 | ); 28 | } 29 | 30 | export default async () => { 31 | const result = []; 32 | const configs = glob 33 | .sync("{plugins,packages}/*/scripts/webpack.config.ts", { 34 | absolute: true, 35 | cwd: rootCwd 36 | }) 37 | .map((path) => ({ 38 | pwd: resolve(dirname(path), "../"), 39 | path 40 | })) 41 | .filter(({ pwd }) => { 42 | // When we need to build a plugin, only consider dependent packages and own plugin 43 | if (buildPlugin) { 44 | return buildPluginPwds.indexOf(pwd) > -1; 45 | } 46 | return true; 47 | }); 48 | 49 | for (const config of configs) { 50 | const { pwd, path } = config; 51 | process.env.DOCKER_START_PWD = pwd; 52 | process.env.NODE_ENV = buildPlugin ? "production" : "development"; 53 | result.push(require(path).default); 54 | } 55 | 56 | return result.flat(); 57 | }; 58 | -------------------------------------------------------------------------------- /devops/docker-compose/docker-compose.e2e.yml: -------------------------------------------------------------------------------- 1 | # This file overrides the file from docker-compose.yml 2 | 3 | version: "3" 4 | 5 | services: 6 | phpmyadmin: 7 | entrypoint: ["echo", "Service phpmyadmin disabled"] 8 | restart: "no" 9 | 10 | wordpress: 11 | environment: 12 | WP_CI_INSTALL_URL: http://wordpress 13 | -------------------------------------------------------------------------------- /devops/docker-compose/docker-compose.local.yml: -------------------------------------------------------------------------------- 1 | # This file overrides the file from docker-compose.yml 2 | 3 | version: "3" 4 | 5 | services: 6 | phpmyadmin: 7 | ports: 8 | - "8079:80" 9 | 10 | wordpress: 11 | depends_on: [dockerhost] 12 | ports: 13 | - "8080:80" 14 | environment: 15 | WP_CI_INSTALL_URL: ${WP_LOCAL_INSTALL_URL} 16 | XDEBUG_CONFIG: remote_host=dockerhost remote_port=9000 remote_enable=1 17 | 18 | # Make the host machine available in WordPress environment through internal host `dockerhost`. 19 | # This is needed for debugging purposes. 20 | dockerhost: 21 | image: qoomon/docker-host 22 | cap_add: ["NET_ADMIN", "NET_RAW"] 23 | restart: on-failure 24 | networks: 25 | - locl 26 | -------------------------------------------------------------------------------- /devops/docker-compose/docker-compose.traefik.yml: -------------------------------------------------------------------------------- 1 | # This file overrides the file from docker-compose.yml 2 | 3 | version: "3" 4 | 5 | networks: 6 | traefik: 7 | external: true 8 | 9 | services: 10 | phpmyadmin: 11 | networks: 12 | - traefik 13 | labels: 14 | - traefik.enable=true 15 | - traefik.frontend.rule=Host:pma-${WP_CI_INSTALL_URL} 16 | - traefik.frontend.auth.basic.users=${CI_TRAEFIK_BAUTH} 17 | - traefik.docker.network=traefik 18 | - traefik.port=80 19 | 20 | wordpress: 21 | networks: 22 | - traefik 23 | labels: 24 | - traefik.enable=true 25 | - traefik.frontend.rule=Host:${WP_CI_INSTALL_URL} 26 | - traefik.frontend.auth.basic.users=${CI_TRAEFIK_BAUTH} 27 | - traefik.docker.network=traefik 28 | - traefik.port=80 29 | environment: 30 | WP_CI_INSTALL_URL: http://${WP_CI_INSTALL_URL} 31 | -------------------------------------------------------------------------------- /devops/docker-compose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # This file defines all needed services for the local development environment without any host-specific options like "ports" or "volumes". 2 | # 3 | # docker-compose.e2e.yml: 4 | # This file gets used when running E2E tests in CI in GitLab, it also should not export any "ports". 5 | # docker-compose.traefik.yml: 6 | # This file is used for review applications which are managed on the same server as gitlab-runner and traefik. 7 | # It's the same like E2E but this containers are stopped when the GitLab environments emits to stop 8 | # docker-compose.local.yml 9 | # This file is used for local development and there you should for example expose "ports". 10 | # 11 | # If you add any service please also adjust the docker-compose.e2e.yml and docker-compose.traefik.yml. 12 | 13 | version: "3" 14 | 15 | networks: 16 | locl: {} 17 | 18 | services: 19 | mysql: 20 | image: mariadb:10.3 21 | networks: 22 | - locl 23 | volumes: 24 | - database:/var/lib/mysql 25 | restart: on-failure 26 | environment: 27 | MYSQL_ROOT_PASSWORD: wordpress 28 | MYSQL_DATABASE: wordpress 29 | MYSQL_USER: wordpress 30 | MYSQL_PASSWORD: wordpress 31 | 32 | phpmyadmin: 33 | image: phpmyadmin/phpmyadmin 34 | networks: 35 | - locl 36 | depends_on: 37 | - mysql 38 | environment: 39 | PMA_HOST: mysql 40 | 41 | wordpress: 42 | image: wordpress:5.4.0-apache 43 | networks: 44 | - locl 45 | volumes: 46 | # Do not include the plugin volume itself because it must be loaded via `docker cp` to avoid build and git clean issues 47 | - www:/var/www/html 48 | - ../scripts:/scripts 49 | # Allow symlinking for composer "path" repositories 50 | - ../../packages:/var/www/html/wp-content/packages 51 | - ../scripts/custom-php.ini:/usr/local/etc/php/conf.d/custom-php.ini 52 | restart: on-failure 53 | depends_on: 54 | - mysql 55 | command: bash /scripts/container-wordpress-command.sh 56 | environment: 57 | WORDPRESS_DB_HOST: mysql:3306 58 | # If you adjust username / password you also have to change username / password in ../scripts/e2e-tests-autologin-plugin.php 59 | WORDPRESS_DB_USER: wordpress 60 | WORDPRESS_DB_PASSWORD: wordpress 61 | WORDPRESS_DB_NAME: wordpress 62 | PLUGIN_SLUGS: $PLUGIN_SLUGS 63 | 64 | wordpress-cli: 65 | image: wordpress:cli 66 | user: xfs 67 | networks: 68 | - locl 69 | volumes: 70 | - www:/var/www/html 71 | - ../scripts:/scripts 72 | entrypoint: /scripts/container-wordpress-cli-entrypoint.sh 73 | depends_on: 74 | - mysql 75 | - wordpress 76 | 77 | volumes: 78 | database: {} 79 | www: {} 80 | -------------------------------------------------------------------------------- /devops/docker/gitlab-ci/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM devowliode/wp-react-starter-gitlab-ci:php-7.3-cli-stretch 2 | 3 | # Prepare our dependencies and cache 4 | ARG GL_CI_WORKDIR 5 | ENV CYPRESS_CACHE_FOLDER=/tmp$GL_CI_WORKDIR/.cypress 6 | ENV YARN_CACHE_FOLDER=/tmp$GL_CI_WORKDIR/.yarn 7 | 8 | # Set composer github token to avoid API rate limit (https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens) 9 | ARG PHP_COMPOSER_GITHUB_TOKEN 10 | RUN (test $PHP_COMPOSER_GITHUB_TOKEN && \ 11 | composer config -g github-oauth.github.com $PHP_COMPOSER_GITHUB_TOKEN) || : 12 | 13 | # Install our dependencies into our gitlab runner 14 | WORKDIR /tmp$GL_CI_WORKDIR 15 | 16 | COPY install.tar . 17 | 18 | RUN tar -xvf install.tar && \ 19 | yarn bootstrap && \ 20 | yarn cypress install 21 | 22 | # Avoid too many progress messages 23 | ENV CI=1 -------------------------------------------------------------------------------- /devops/scripts/container-wordpress-cli-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copy the installed WP-CLI binary so it can be used through the wordpress environment 4 | 5 | cp /usr/local/bin/wp /var/www/html/ -------------------------------------------------------------------------------- /devops/scripts/custom-php.ini: -------------------------------------------------------------------------------- 1 | upload_max_filesize = 64M 2 | post_max_size = 64M -------------------------------------------------------------------------------- /devops/scripts/e2e-tests-autologin-plugin.php: -------------------------------------------------------------------------------- 1 | 'wordpress', 16 | 'user_password' => 'wordpress', 17 | 'remember' => true 18 | ], 19 | false 20 | ); 21 | 22 | if (!is_wp_error($autologin_user)) { 23 | header('Location: wp-admin'); 24 | } 25 | } 26 | } 27 | add_action('after_setup_theme', 'autologin'); 28 | -------------------------------------------------------------------------------- /devops/scripts/lerna-ready-ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Make the `lerna` command possible in GitLab CI environment 4 | 5 | # Unshallow to make git-describe.sync work https://app.clickup.com/t/a6qmdj 6 | git fetch --unshallow 7 | 8 | # Use unsafe-perm so lerna lifecycle events work. Why: https://git.io/JeQdu 9 | yarn config set unsafe-perm true 10 | 11 | # lerna needs to detect the branch name without detached HEAD, Why: https://gitlab.com/gitlab-org/gitlab/issues/15409#note_214809980 12 | [ ! "$CI_BUILD_TAG" ] && git checkout -B "$CI_COMMIT_REF_NAME" "$CI_COMMIT_SHA" 13 | 14 | # Allow NPM publishing if enabled 15 | [ $NPM_TOKEN ] && npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN -q 16 | 17 | # Make `origin` in current repository work again, so push back to repo is possible. See https://gitlab.com/gitlab-org/gitlab/issues/14101#note_261684708 18 | git remote set-url origin "https://gitlab-ci-token:$GITLAB_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git" -------------------------------------------------------------------------------- /devops/scripts/purge-ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Purge a given composition of docker containers with volumes and networks. Useful for review applications and E2E tests. 4 | 5 | # Remove running containers 6 | echo "[CONTAINERS]" 7 | # docker ps -a 8 | export CURRENT_CONTAINERS="$(docker ps -a --format "{{.ID}} {{.Names}}" | awk '$2~/'"$COMPOSE_PROJECT_NAME$COMPOSE_PROJECT_NAME_SUFFIX-$CI_COMMIT_REF_SLUG"'/{print $1}')" 9 | test "$CURRENT_CONTAINERS" && echo "Removing..." && docker rm -f -v $CURRENT_CONTAINERS 10 | 11 | # Remove available volumes 12 | echo 13 | echo "[VOLUMES]" 14 | # docker volume ls 15 | export CURRENT_VOLUMES="$(docker volume ls --format "{{.Name}}" | awk '$1~/'"$COMPOSE_PROJECT_NAME$COMPOSE_PROJECT_NAME_SUFFIX-$CI_COMMIT_REF_SLUG"'/{print $1}')" 16 | test "$CURRENT_VOLUMES" && echo "Removing..." && docker volume remove -f $CURRENT_VOLUMES 17 | 18 | # Remove available networks 19 | echo 20 | echo "[NETWORKS]" 21 | # docker network ls 22 | export CURRENT_NETWORKS="$(docker network list --format "{{.Name}}" | awk '$1~/'"$COMPOSE_PROJECT_NAME$COMPOSE_PROJECT_NAME_SUFFIX-$CI_COMMIT_REF_SLUG"'/{print $1}')" 23 | test "$CURRENT_NETWORKS" && echo "Removing..." && docker network remove $CURRENT_NETWORKS 24 | 25 | echo 26 | echo "Purged" -------------------------------------------------------------------------------- /devops/scripts/task-xdebug-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | yarn debug:php:start -------------------------------------------------------------------------------- /devops/scripts/task-xdebug-stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | yarn debug:php:stop -------------------------------------------------------------------------------- /devops/scripts/wordpress-startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Run your commands for startup of WordPress in the docker environment. 4 | # If you want to do more customizations you should have a look at container-wordpress-command.sh 5 | 6 | # Activate all the plugins 7 | for slug in $PLUGIN_SLUGS 8 | do 9 | wp --allow-root plugin activate $slug 10 | 11 | # Note: Before putting plugin-specific commands here, have a look at plugins/your-plugin/devops/scripts/wordpress-startup.sh! 12 | done 13 | 14 | # Delete unnecessery plugins 15 | rm -rf wp-content/plugins/akismet 16 | rm -rf wp-content/plugins/hello.php -------------------------------------------------------------------------------- /docs/advanced/build-production-plugin.md: -------------------------------------------------------------------------------- 1 | # Build production plugin 2 | 3 | {% hint style="warning" %} 4 | Never adjust `package.json`'s `version` and `CHANGELOG.md` **manually**! 5 | {% endhint %} 6 | 7 | Building a production plugin is **completely covered by the GitLab CI**. That means if you push changes to the `master` branch the pipeline automatically generates the installable `.zip` file and syncs it to wordpress.org (if configured). The plugin itself can be downloaded through [GitLab Releases](https://docs.gitlab.com/ee/user/project/releases/) or Job artifacts. ` package.json``index.php ` and `CHANGELOG.md` is automatically generated by `yarn lerna publish` and [`yarn build`](../usage/available-commands/plugin.md#build). 8 | 9 | {% page-ref page="../gitlab-integration/predefined-pipeline.md" %} 10 | 11 | {% page-ref page="../gitlab-integration/deploy-wp-org.md" %} 12 | 13 | {% hint style="info" %} 14 | Ensure `GITLAB_TOKEN` is set with a [personal token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html) in your GitLab CI variables. Learn more [here](../gitlab-integration/extend-gitlab-ci-pipeline.md#available-variables). 15 | {% endhint %} 16 | 17 | {% hint style="success" %} 18 | Nothing more to say, simply follow the Git flow and automate the release management! 19 | {% endhint %} 20 | 21 | If you really want to build the plugin locally, you can run [`yarn build`](../usage/available-commands/plugin.md#build) in your plugin folder. 22 | -------------------------------------------------------------------------------- /docs/advanced/create-add-on.md: -------------------------------------------------------------------------------- 1 | # Create Add-On 2 | 3 | What's an Add-On? In our boilerplate context an Add-On is a plugin **relying on another plugin**. That means, for example use PHP API functions from another plugin or connect to a MobX store of another plugin. 4 | 5 | First of all, create a new plugin within your workspace: 6 | 7 | ``` 8 | create-wp-react-app create-plugin 9 | ``` 10 | 11 | You will get some prompts and afterwards follow the below steps. 12 | 13 | ## Link and usage 14 | 15 | 1. Run `yarn lerna add @wp-reactjs-multi-starter/wp-reactjs-starter --scope @wp-reactjs-multi-starter/my-addon` so `lerna` adds a plugin as dependency to the add-on. 16 | 1. Run `yarn lerna link` to link the plugins together 17 | 1. (optional) Add the entrypoint `wp-reactjs-starter-admin` to the `Assets` `$scriptDeps` array so the Add-On's `.js` is only enqueued when the other entrypoint is enqueued. 18 | 1. Navigate to your plugin which consumes the dependency and add the dependency path to [`.wprjss only changes`](../gitlab-integration/extend-gitlab-ci-pipeline.md#plugin) 19 | 20 | ```typescript 21 | import { stores } from "@wp-reactjs-multi-starter/wp-reactjs-starter/src/public/ts/admin"; 22 | 23 | console.log("Stores from my parent plugin: ", stores); 24 | ``` 25 | 26 | {% hint style="success" %} 27 | Wow! Additionally our [`webpack.factory.ts`](../usage/folder-structure/root.md#folder-structure) prevents bundling the plugins' code into the Add-On. That means, it is registered as external and directly uses their coding. 28 | {% endhint %} 29 | 30 | {% hint style="info" %} 31 | You have to replace `wp-reactjs-multi-starter` and `wp-reactjs-starter` with your names. 32 | {% endhint %} 33 | 34 | ## Provide public PHP API 35 | 36 | If you aim to provide a public PHP API to your WordPress users you mostly create prefixed functions, for example `wp_rml_get_object()`. This is a recommend way but you should not create too much functions, furthermore work with factory functions and let the user work with class instances. Never mind, usually all PHP files in your plugin are scoped but there is one exception: `plugins/*/src/inc/api/**/*.php` files. Create all your public functions there and they will be available in the global scope. 37 | 38 | ## Deploy types to npmjs.com 39 | 40 | Perhaps it can be interesting making types available to third-party developers so they are able to extend your plugin. For this, you need to do the following: 41 | 42 | 1. Remove `private` in your plugins' `package.json` 43 | 1. Run `tsc` in your plugin folder to generate `types` folder with `.d.ts` files 44 | 1. Repeat the two steps above for the `utils` package, too 45 | 1. Commit the files and the rest is doing `lerna` when merging to `master` 46 | 47 | {% hint style="info" %} 48 | Make sure you have configured the GitLab CI `NPM_TOKEN` variable with the [npm token](https://docs.npmjs.com/about-authentication-tokens). Learn more [here](../gitlab-integration/extend-gitlab-ci-pipeline.md#available-variables). 49 | {% endhint %} 50 | -------------------------------------------------------------------------------- /docs/advanced/extend-compose-webpack.md: -------------------------------------------------------------------------------- 1 | # Extend Compose and Webpack 2 | 3 | You are an advanced Docker Compose and Webpack user? Then heads up, read on how to extend the boilerplate. 4 | 5 | ## Docker Compose 6 | 7 | The `docker-compose.yml` file from [`devops/docker-compose/`](../usage/folder-structure/root.md#folder-structure) is deep merged with all found `{plugins/packages}/*/devops/docker-compose/docker-compose.yml` files. So, it is pretty easy to add a plugin specific container. 8 | 9 | To see which `docker-compose.yml` files are merged, run the root command [`yarn workspace:compose-files`](../usage/available-commands/root.md#workspace). 10 | 11 | To see the result of the merged `docker-compose.yml` file, you can run [`yarn docker-compose config`](../usage/available-commands/root.md#misc). 12 | 13 | ## Webpack 14 | 15 | Webpack provides a lot of [configurations](https://webpack.js.org/configuration/). We do not prevent you from using them together with [`webpack.factory.ts`](../usage/folder-structure/root.md#folder-structure). Simply pass a callback to the factory and do what you want in [`plugins/your-plugin/scripts/webpack.config.ts`](../usage/folder-structure/plugin.md#folder-structure): 16 | 17 | ```typescript 18 | import { createDefaultSettings } from "../../../common/webpack.factory"; 19 | 20 | export default createDefaultSettings("plugin", { 21 | override: (settings) => { 22 | // Do something with settings. 23 | } 24 | }); 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/advanced/how-cachebuster-works.md: -------------------------------------------------------------------------------- 1 | # How cachebuster works 2 | 3 | What is cachebusting? Simply said, its a dynamic `GET` parameter added to your `.js` or `.css` file so the browser is **forced to refetch** the resource when there is a known change. 4 | 5 | The [`Assets`](../php-development/predefined-classes.md#assets) class provides a few scenarios of cachebusting when enqueueing scripts and styles. 6 | 7 | ## NPM Dependency 8 | 9 | 1. Adding a dependency to `package.json` (using `yarn add`) 10 | 1. Copy to `plugins/your-plugin/src/public/lib/` (using [`yarn grunt libs:copy`](../usage/available-commands/plugin.md#development)) 11 | 1. Use [`Assets#enqueueLibraryScript()`](../php-development/predefined-classes.md#enqueue-external-library) to enqueue the handle 12 | 13 | {% hint style="success" %} 14 | The cachebuster is applied with the package version of the dependency. 15 | {% endhint %} 16 | 17 | {% page-ref page="../typescript-development/add-external-library.md" %} 18 | 19 | ## Entrypoints 20 | 21 | 1. Start developing on your entrypoints with [`yarn docker:start`](../usage/available-commands/root.md#development)\`\` 22 | 1. Change some code and webpack will transform the entrypoints automatically to production / development code 23 | 1. Use [`Assets#enqueueScript()`](../php-development/predefined-classes.md#enqueue-entrypoint) to enqueue the handle 24 | 25 | {% hint style="success" %} 26 | The cachebuster is applied with a hash (hash comes from [webpack](https://webpack.js.org/configuration/output/#outputhashdigest)). 27 | {% endhint %} 28 | 29 | {% page-ref page="../typescript-development/using-entrypoints.md" %} 30 | 31 | ## Unknown resources 32 | 33 | You want to use a JavaScript library which is not installable through `yarn` 34 | 35 | 1. Put the files manually to [`plugins/your-plugin/src/public/lib`](../usage/folder-structure/plugin.md#folder-structure)\`\` 36 | 1. Use [`Assets#enqueLibraryScript()`](../php-development/predefined-classes.md#enqueue-external-library) (or `wp_enqueue_script` directly) to enqueue the handle 37 | 38 | {% hint style="success" %} 39 | The cachebuster is applied with your plugins' version. 40 | {% endhint %} 41 | -------------------------------------------------------------------------------- /docs/advanced/license-checker.md: -------------------------------------------------------------------------------- 1 | # License checker 2 | 3 | Licenses can be a mess. So, we decided to do this job for you, too. We introduced two license scanners not allowing you to use dependencies with unwanted licenses. All packages and plugins are licensed out-of-the-box `GPL-3.0-or-later` (see `LICENSE` files and `license` key in `package.json``composer.json`). 4 | 5 | {% hint style="warning" %} 6 | We give no guarantee of legal validity! 7 | {% endhint %} 8 | 9 | ## JavaScript 10 | 11 | As we use `yarn` as dependency manager, we can rely on [`yarn licenses`](https://yarnpkg.com/lang/en/docs/cli/licenses/) scanning all used dependencies and report back if there is an issue (on [CI side](../gitlab-integration/predefined-pipeline.md#validate)). A generated disclaimer will be saved to [`LICENSE_3RD_PARTY_JS.md`](../usage/folder-structure/plugin.md#folder-structure). 12 | 13 | Allowed licenses and packages can be configured in [`package.json#license-check`](../usage/folder-structure/plugin.md#folder-structure) or the root `package.json`. 14 | 15 | {% hint style="warning" %} 16 | Root dependencies are not checked! Make sure to add all your license-relevant dependencies to your subpackage [`package.json`](../usage/folder-structure/plugin.md#folder-structure). 17 | {% endhint %} 18 | 19 | ## PHP 20 | 21 | As we use `composer` as dependency manager, we can rely on the following packages: 22 | 23 | - [`composer-plugin-license-check`](https://packagist.org/packages/metasyntactical/composer-plugin-license-check): Checks licenses due to a whitelist defined in [`composer.json#extra.metasyntactical/composer-plugin-license-check`](../usage/folder-structure/folder.md#folder-structure) and and reports if there is an issue (on [CI side](../gitlab-integration/predefined-pipeline.md#validate)) 24 | - [`php-legal-licenses`](https://packagist.org/packages/comcast/php-legal-licenses): Additionally a [`LICENSE_3RD_PARTY_PHP.md`](../usage/folder-structure/plugin.md#folder-structure) file will be generated. 25 | -------------------------------------------------------------------------------- /docs/advanced/persistent-database-snapshot.md: -------------------------------------------------------------------------------- 1 | # Persistent database snapshot 2 | 3 | Sometimes it can be useful and necessery to import initial database entries to the WordPress database. The boilerplate comes with a mechanism that allows you to define tables, they get dumped into a single file and are imported automatically with the next WordPress installation. 4 | 5 | {% hint style="info" %} 6 | It can be extremely helpful using initial imports for [E2E tests](tests.md#E2E). 7 | {% endhint %} 8 | 9 | Here is a simple scenario you can adopt to your use case: 10 | 11 | 1. [`yarn docker:start`](../usage/available-commands/root.md#development) to start your WordPress installation 12 | 1. [Login](../usage/getting-started#open-wordpress) to your WordPress instance and create a new post 13 | 1. Define tables you want to snapshot for the startup in [`package.json#db:snapshot`](../usage/folder-structure/root.md#folder-structure) like this `"db:snapshot": ["wp_posts", "wp_postmeta"]` 14 | 1. [`yarn db:snapshot-import-on-startup`](../usage/available-commands/root.md#database) to export the defined database tables into a file in [`devops/scripts/startup.sql`](../usage/folder-structure/root.md#folder-structure) 15 | 1. [`yarn docker:purge`](../usage/available-commands/root.md#development) removes your current WordPress installation completely 16 | 1. [`yarn docker:start`](../usage/available-commands/root.md#development) again and you will see that post is immediatly available 17 | -------------------------------------------------------------------------------- /docs/advanced/showcase.md: -------------------------------------------------------------------------------- 1 | # Showcase 2 | 3 | Who is using this boilerplate? That's a valid question! 4 | 5 | - [WP Real Media Library](https://matthias-web.com/go/codecanyon/13155134) 6 | - [WP Real Physical Media](https://matthias-web.com/go/codecanyon/23104206) 7 | - [WP Real Thumbnail Generator](https://matthias-web.com/go/codecanyon/18937507) 8 | - [WP Real Categories Management](https://matthias-web.com/go/codecanyon/13580393) 9 | 10 | {% hint style="info" %} 11 | You are using it? Let us know and we will add your plugin to the list! Just contribute to that file or contact us directly. 12 | {% endhint %} 13 | -------------------------------------------------------------------------------- /docs/gitlab-integration/deploy-wp-org.md: -------------------------------------------------------------------------------- 1 | # Deploy wordpress.org 2 | 3 | ## Initial release 4 | 5 | In this section it is explained how to release a new plugin to wordpress.org. Generally the initial release needs to be reviewed by the wordpress.org team so you have to prepare the installable plugin as `.zip` file locally. Later - when updating the plugin - the GitLab CI is used. 6 | 7 | - Add functionality to your plugin and prepare for release 8 | - Adjust [`wordpress.org/README.wporg.txt`](../usage/folder-structure/plugin.md#folder-structure) files (you can use [README validator](https://wordpress.org/plugins/developers/readme-validator/)) 9 | - Prepare you images (header, icon, screenshots) in [`wordpress.org/assets`](../usage/folder-structure/plugin.md#folder-structure) 10 | - When you think it is ready to release 11 | - Commit your work 12 | - Let the CI/CD system [build the plugin for you](../advanced/build-production-plugin.md) 13 | - Download the installable `.zip` from the Job artifacts 14 | - Navigate to [`build`](../usage/folder-structure/plugin.md#folder-structure) and you will se a generated `.zip` file 15 | - Upload file to https://wordpress.org/plugins/developers/add/ for review 16 | - Wait for approval 17 | 18 | {% hint style="warning" %} 19 | Sometimes it is important to directly send a an email to the review team because they should adjust the slug for you. By default the slug is generated through the `Plugin Name` in `index.php`. Imagine, your plugin is named `WP Real Media Library` your generated slug will be `wp-real-media-library`. But you want `real-media-library` to be the slug, send an email directly after upload process! 20 | {% endhint %} 21 | 22 | ## Enable SVN deploy 23 | 24 | When the above initial review got approved you can go on with deployment via CI/CD: 25 | 26 | - In your repository navigate to Settings > CI / CD > Variables 27 | - Add [variable](./extend-gitlab-ci-pipeline.md#available-variables) `WPORG_SVN_URL`: When the plugin gots approved you will get a SVN url, put it here 28 | - Add [variable](./extend-gitlab-ci-pipeline.md#available-variables) `WPORG_SVN_USERNAME`: The username of your wordpress.org user 29 | - Add [variable](./extend-gitlab-ci-pipeline.md#available-variables) `WPORG_SVN_PASSWORD`: The password of your wordpress.org user. You have to protect and - mask it. Note: If you password does not meet the requirements of [Masked Variables](https://gitlab.com/help/ci/variables/README#masked-variables) it does not work. It depends on you: Change your password so it works or leave it unmasked 30 | - Put some changes to `develop` branche and merge it to `master` 31 | - The CI/CD automatically deploys to wordpress.org 32 | -------------------------------------------------------------------------------- /docs/php-development/debugging.md: -------------------------------------------------------------------------------- 1 | # Debugging 2 | 3 | If you are using **VSCode** you are ready to use preconfigured debug settings for PHP files. Just navigate to the ant icon left in your VSCode sidebar and click the play button. Read more about [here](https://code.visualstudio.com/docs/editor/debugging). 4 | 5 | Generally, the following things gets done when it comes to debugging: 6 | 7 | 1. Start your environment with [`yarn docker:start`](../usage/available-commands/root.md#development) 8 | 1. Start the debugger with the play icon 9 | 1. Wait a moment until the debugger is ready. This can take a while for the first time because XDebug will be installed in the WordPress container 10 | 1. Create a breakpoint in a PHP file 11 | 1. Do something so the code gets executed 12 | 1. The browser freezes and VSCode shows you that a breakpoint is reached 13 | 1. Do something 14 | 1. Stop the debugger 15 | 1. XDebug gets deactivated in the WordPress container automatically 16 | 17 | {% hint style="warning" %} 18 | Do you get an error while starting the debugger? Please refer to this [thread](https://github.com/qoomon/docker-host/issues/21#issuecomment-497831038). 19 | {% endhint %} 20 | 21 | ## Remote development 22 | 23 | If you are using the [Remote SSH extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh) already you do not need to take further configurations. Just connect with your server via SSH and start the debugger. If you notice that the debugger "freezes" or "hangs" please make sure the debug port (in our case `9000`) can be opened. Check your firewall, read more [here](https://serverfault.com/a/309111). 24 | -------------------------------------------------------------------------------- /docs/php-development/example-implementations.md: -------------------------------------------------------------------------------- 1 | # Example implementations 2 | 3 | The PHP server-side comes with some example implementations so you can better understand how things work. 4 | 5 | All the example implementations contain a client-side (TypeScript) implementation, too: 6 | 7 | {% page-ref page="../typescript-development/example-implementations.md" %} 8 | 9 | ## Menu page 10 | 11 | When [opening your WordPress](../usage/getting-started.md#open-wordpress) `wp-admin` and activate the plugin, you will notice a new page on the left sidebar with the name of your plugin. This example is located in [`src/inc/view/menu/Page.php`](../usage/folder-structure/plugin.md#folder-structure) and initialized through [`Core`](predefined-classes.md#core). 12 | 13 | Additionally it enqueues the following scripts in [`Assets`](predefined-classes.md#enqueue-entrypoint): 14 | 15 | ```php 16 | $handle = WPRJSS_SLUG . '-admin'; 17 | $this->enqueueScript($handle, 'admin.js', $scriptDeps); 18 | $this->enqueueStyle($handle, 'admin.css'); 19 | ``` 20 | 21 | Following, the TypeScript entrypoint from [`src/public/ts/admin.tsx`](../typescript-development/using-entrypoints.md) is executed when visiting that page. Opening that menu page, some notices and a todo list is visible (under the scenes it uses a MobX store). Additionally the [Hello World REST](example-implementations.md#rest-endpoint) endpoint ([`src/inc/rest/HelloWorld.php`](../usage/folder-structure/plugin.md#folder-structure)) can be requested through a link. See also [this](../typescript-development/example-implementations.md#menu-page). 22 | 23 | ## Widget 24 | 25 | In WordPress widgets section you should also notice a new widget "React Demo Widget". If a page contains the widget, the TypeScript coding from [`src/public/ts/widget.tsx`](../usage/folder-structure/plugin.md#folder-structure) is executed. It simply shows a "Hello World" ReactJS component. 26 | 27 | The widget is initialized through [`Core#widgets_init`](predefined-classes.md#core) and enqueued in [`Assets`](predefined-classes.md#enqueue-entrypoint). See also [this](../typescript-development/example-implementations.md#widget). 28 | 29 | ## REST endpoint 30 | 31 | All your REST API endpoints should be put into [`src/inc/rest`](../usage/folder-structure/plugin.md#folder-structure). A Hello World endpoint is implemented in [`src/inc/rest/HelloWorld.php`](../usage/folder-structure/plugin.md#folder-structure). It follows the standard WP REST API specification defined [here](https://developer.wordpress.org/rest-api/). 32 | 33 | So, if you request a `GET` (simply open in web browser) of `localhost:{your-port}/wp-json/your-plugin/hello-world` you will get a response. See also [this](../typescript-development/example-implementations.md#rest-endpoint). 34 | 35 | {% hint style="warning" %} 36 | The API endpoints does not automatically ensure a logged-in user because it relies not on the current logged in user. It is handled through the so-called `_wpnonce`. Read more about it [here](https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/). 37 | {% endhint %} 38 | -------------------------------------------------------------------------------- /docs/php-development/localization.md: -------------------------------------------------------------------------------- 1 | # Localization 2 | 3 | There are two types of localization (i18n) coming with this boilerplate. In this part we explain the PHP localization. Just have a look at [`src/languages/your-plugin.pot`](../usage/folder-structure/plugin.md#folder-structure) - that's the main PHP i18n file. 4 | 5 | To localize your plugin use the i18n functions coming with [**WordPress core**](https://developer.wordpress.org/plugins/internationalization/localization/): `__`, `_n` and so on ([difference](https://wpengineer.com/2237/whats-the-difference-between-__-_e-_x-and-_ex/)). The `.pot` file is automatically generated while using [`yarn docker:start`](../usage/available-commands/root.md#development). To generate the file manually you can use the command [`yarn i18n:generate:backend`](../usage/available-commands/plugin.md#localization) in your plugin folder. 6 | 7 | {% hint style="info" %} 8 | When using the above functions always use your own text domain constant `WPRJSS_TD` as context parameter. 9 | {% endhint %} 10 | 11 | {% page-ref page="../typescript-development/localization.md" %} 12 | -------------------------------------------------------------------------------- /docs/typescript-development/consume-php-variable.md: -------------------------------------------------------------------------------- 1 | # Consume PHP variable 2 | 3 | Sometimes it is needed to consume PHP variables in your frontend TypeScript coding. You are covered! The boilerplate comes with a mechanism to get a typed object. 4 | 5 | ## Predefined variables 6 | 7 | If you have a look at [`src/public/ts/store/option.tsx`](../usage/folder-structure/plugin.md#folder-structure) you will find a typed `OptionStore`. You also notice that it extends from `BaseOptions`. The boilerplate comes out-of-the-box with important variables you can already use: 8 | 9 | ```typescript 10 | public slug: string; // Plugin slug 11 | public textDomain: string; // Plugin text domain, needed for i18n factory 12 | public version: string; // Plugin version 13 | public restUrl?: string; // REST API Url 14 | public restNamespace?: string; // Plugin REST API namespace 15 | public restRoot?: string; // REST API root path 16 | public restQuery?: {}; // REST API query sent with each request to the backend (GET) 17 | public restNonce?: string; // REST API authentication nonce 18 | public publicUrl?: string; // Public url localhost:{your-port}/wp-content/plugins/your-plugin/public 19 | ``` 20 | 21 | ## Access variables 22 | 23 | The `OptionStore` can be used by React in that way (it relies to the context factory): 24 | 25 | ```typescript 26 | () => { 27 | const { optionStore } = useStores(); 28 | return {optionStore.slug}; 29 | }; 30 | ``` 31 | 32 | It can also read directly (relies on the root store [`src/public/ts/store/stores.tsx`](../usage/folder-structure/plugin.md#folder-structure)): 33 | 34 | ```typescript 35 | console.log(rootStore.optionStore.slug); 36 | ``` 37 | 38 | ## Add own variable 39 | 40 | Assume we want to know if the user is allowed to install plugins (`install_plugins`). Adjust the [`Assets#overrideLocalizeScript()`](../php-development/predefined-classes.md#pass-variables-to-client-side) method and additionally make the variable only be exposed for a given context (site, admin): 41 | 42 | ```php 43 | public function overrideLocalizeScript($context) { 44 | if ($context === base\Assets::TYPE_ADMIN) { 45 | return [ 46 | 'canInstallPlugins' => current_user_can('install_plugins') 47 | ]; 48 | } elseif ($context === base\Assets::TYPE_FRONTEND) { 49 | // [...] 50 | } 51 | 52 | return []; 53 | } 54 | ``` 55 | 56 | To make it available in TypeScript we need to adjust the `OptionStore#others` property: 57 | 58 | ```typescript 59 | class OptionStore extends BaseOptions { 60 | // [...] 61 | 62 | @observable 63 | public others: { canInstallPlugins: boolean } = { 64 | // Defaults (optional) 65 | canInstallPlugins = false 66 | }; 67 | } 68 | ``` 69 | 70 | Let's access it: 71 | 72 | ```typescript 73 | console.log(rootStore.optionStore.others.canInstallPlugins); 74 | ``` 75 | -------------------------------------------------------------------------------- /docs/typescript-development/example-implementations.md: -------------------------------------------------------------------------------- 1 | # Example implementations 2 | 3 | The TypeScript client-side comes with some example implementations so you can better understand how things work. 4 | 5 | All the example implementations contain a server-side (PHP) implementation, too: 6 | 7 | {% page-ref page="../php-development/example-implementations.md" %} 8 | 9 | ## Menu page 10 | 11 | There is a PHP example implementation of a [menu page](../php-development/example-implementations.md#menu-page), but it needs more coding not only on server-side. The entrypoint [`src/public/ts/admin.ts`](using-entrypoints.md) comes into game. It detects if the page is visible and automatically renders content with ReactJS. It includes - as mentioned previously - some notices and a todo list connected to a MobX store. We recommend to have a look at that files directly before we copy & paste all the stuff here again: 12 | 13 | - 📁 `ts` 14 | - 📁 `components/*` 15 | - 📁 `models/*` 16 | - 📁 `store/*` 17 | - 📁 `style` 18 | - 📄 `admin.scss` 19 | - 📁 `utils/*` 20 | - 📄 `admin.tsx` 21 | 22 | ## Widget 23 | 24 | There is a PHP example implementation of a [widget](../php-development/example-implementations.md#widget). A widget always needs a "visible part" so TypeScript together with ReactJS can do the job. We recommend to have a look at that files directly before we copy & paste all the stuff here again: 25 | 26 | - 📁 `ts` 27 | - 📁 `style` 28 | - 📄 `widget.scss` 29 | - 📁 `widget/*` 30 | - 📄 `widget.tsx` 31 | 32 | {% hint style="info" %} 33 | You must determine if you need server-side rendered HTML output or ReactJS. It has something to do with SEO. If SEO is important to your plugin, it is recommend to use server-side rendering. For example, if you want to create a dashboard only for logged-in users you can surely use ReactJS. 34 | {% endhint %} 35 | 36 | ## REST endpoint 37 | 38 | There is a PHP example implementation of a [Hello World endpoint](../php-development/example-implementations.md#rest-endpoint). In this case it is important to "type" that endpoint with TypeScript interfaces: 39 | 40 | `ts/wp-api/hello-world.get.tsx`: Describes the `GET` **request**, **parameters** and **response**: 41 | 42 | ```typescript 43 | import { 44 | RouteLocationInterface, 45 | RouteHttpVerb, 46 | RouteResponseInterface, 47 | RouteRequestInterface, 48 | RouteParamsInterface 49 | } from "@wp-reactjs-multi-starter/utils"; 50 | 51 | export const locationRestHelloGet: RouteLocationInterface = { 52 | path: "/hello", 53 | method: RouteHttpVerb.GET 54 | }; 55 | 56 | export type RequestRouteHelloGet = RouteRequestInterface; 57 | 58 | export type ParamsRouteHelloGet = RouteParamsInterface; 59 | 60 | export interface ResponseRouteHelloGet extends RouteResponseInterface { 61 | hello: string; 62 | } 63 | ``` 64 | 65 | To request the endpoint you can simply do this by: 66 | 67 | ```typescript 68 | const result = await request({ 69 | location: locationRestHelloGet 70 | }); 71 | ``` 72 | 73 | The resulting object will be of type `ResponseRouteHelloGet` and you can easily access `result.hello`. 74 | -------------------------------------------------------------------------------- /docs/typescript-development/localization.md: -------------------------------------------------------------------------------- 1 | # Localization 2 | 3 | ## Localize TypeScript 4 | 5 | There are two types of localization (i18n) coming with this boilerplate. In this part we explain the TypeScript localization. Just have a look at [`src/public/languages/your-plugin.pot`](../usage/folder-structure/plugin.md#folder-structure) - that's the main TypeScript i18n file. 6 | 7 | To localize your plugin use the i18n functions coming through the utils package: `__`, `_n` and so on. Under the hood [@wordpress/i18n](https://www.npmjs.com/package/@wordpress/i18n) is used. The `.pot` file is **automatically generated** when using [`yarn docker:start`](../usage/available-commands/root.md#development) in plugin. To generate the file manually you can use the command [`yarn i18n:generate:frontend`](../usage/available-commands/plugin.md#localization) in your plugin folder. 8 | 9 | {% hint style="info" %} 10 | When using the above functions it differs from the [PHP localization](../php-development/localization.md). You do not need to pass any context parameter because it is automatically respected through the factory function from the utils package. 11 | {% endhint %} 12 | 13 | ## Interpolation 14 | 15 | To explain "Interpolation" as best, follow below example. Before writing the following ReactJS code: 16 | 17 | ```javascript 18 | // [...] 19 | () => ( 20 |
21 | Hi{" "} 22 | 23 | bob. 24 | 25 | ! 26 |
27 | ); 28 | // [...] 29 | ``` 30 | 31 | ... you should use the `_i` function coming from the [utils package factory](utils-package.md#factories): 32 | 33 | ```javascript 34 | const username = "bob"; 35 | 36 | // [...] 37 | () => 38 | _i( 39 | // Translate parameters into the string 40 | __("Hi {{a}}%(username)s{{/a}}!", { 41 | username: bob 42 | }), 43 | // Translate components 44 | { 45 | a: 46 | } 47 | ); 48 | // [...] 49 | ``` 50 | 51 | What does this solve? Translating a HTML string does not work because ReactJS can not and **should not** recognize this automatically (for security reasons). Under the hood, [i18n-calypso](https://www.npmjs.com/package/i18n-calypso) is used. 52 | 53 | {% hint style="success" %} 54 | **Awesome**! Do no longer work with i18n keys in your frontend!. 55 | {% endhint %} 56 | -------------------------------------------------------------------------------- /docs/typescript-development/using-entrypoints.md: -------------------------------------------------------------------------------- 1 | # Using entrypoints 2 | 3 | If you get deeper into plugin development you will notice that some frontend coding is not needed for all pages. Some scenarios: 4 | 5 | - **Gutenberg block**: Only load JS / CSS when the Gutenberg block is loaded 6 | - **Shortcode**: Only load JS / CSS when the Gutenberg block is loaded 7 | - **Widget**: A "big" widget should outsource coding to its own entrypoint 8 | - **WP Admin page**: Each page should only load their JS / CSS 9 | 10 | The boilerplate and example implementations use the simplest scenario and loads a single `admin.js` on `wp-admin` and `widget.js` on the website itself. 11 | 12 | For this, you should consider **conditional loading** of JS / CSS files. Let's go with an example, which should only be loaded on `wp-admin/edit.php`. Now, how to create a **new entrypoint**? In our boilerplate context, an "entrypoint" is each file in [`plugins/your-plugin/src/public/ts/*`](../usage/folder-structure/plugin.md#folder-structure) (**first level, no sub folders**). Create a new file [`plugins/your-plugin/src/public/ts/edit.tsx`](../usage/folder-structure/plugin.md#folder-structure): 13 | 14 | ```text 15 | console.log("I am only loaded in posts edit page!"); 16 | ``` 17 | 18 | Afterwards run [`yarn docker:start`](../usage/available-commands/root.md#development) and the new entrypoint `edit.js` is compiled to [`src/public/{dev,dist}`](../usage/folder-structure/plugin.md#folder-structure). Open [`Assets#enqueue_scripts_and_styles`](../php-development/predefined-classes.md#enqueue-entrypoint) and add this: 19 | 20 | ```php 21 | public function enqueue_scripts_and_styles($type) { 22 | if ($this->isScreenBase("edit")) { 23 | $this->enqueueScript(WPRJSS_SLUG . '-edit', 'edit.js'); 24 | } 25 | } 26 | ``` 27 | 28 | {% hint style="info" %} 29 | If you can not use [`Assets#enqueue_scripts_and_styles`](../php-development/predefined-classes.md#enqueue-entrypoint) to enqueue your conditional files (e. g. Shortcode) you can always `$this->getCore()->getAssets()` to use the boilerplate enqueue methods. 30 | {% endhint %} 31 | -------------------------------------------------------------------------------- /docs/typescript-development/utils-package.md: -------------------------------------------------------------------------------- 1 | # Utils package 2 | 3 | As we are using a **multi-package-/mono repository** we have the ability and advantage of **modular** package **development**. That means, coding which should be used in multiple plugins can be **outsourced** to an own package. This can also be done for PHP ([`packages/utils/src`](../usage/folder-structure/root.md#folder-structure)), but this does not matter here. 4 | 5 | {% page-ref page="../advanced/create-package.md" %} 6 | 7 | With [`create-wp-react-app create-workspace`](../usage/getting-started.md#create-workspace) a main utils package in [`packages/utils`](../usage/folder-structure/root.md#folder-structure) is generated automatically. 8 | 9 | ## Factories 10 | 11 | All of your plugins can use the factories defined in [`packages/utils/lib/factory`](../usage/folder-structure/root.md#folder-structure). They do a lot of work for you and implement a high standard for the following topics: 12 | 13 | - AJAX / `XMLHttpRequest` handler with predefined interfaces so all your WP REST API endpoints are typed, see also [this](example-implementations.md#rest-endpoint). 14 | - ReactJS [context](https://reactjs.org/docs/context.html) creation with a single function. That context must be used together with React [hooks](https://reactjs.org/docs/hooks-intro.html). 15 | - [Localization / i18n](localization.md#localize-typescript) 16 | - Object-orientated class to [consume options](consume-php-variable.md#predefined-variables) coming from a localized PHP variable. 17 | 18 | {% hint style="info" %} 19 | You do not need to call that factories manually because they are consumed automatically in your generated plugin file [`plugins/your-plugin/src/public/ts/utils/index.tsx`](../usage/folder-structure/plugin.md#folder-structure). Just use them. 20 | 21 | It is recommend to not extend that utils package by yourself. Imagine you want to upgrade to a newer version of WP ReactJS Starter you need to copy & paste all your customizations manually. Create another package! 22 | {% endhint %} 23 | -------------------------------------------------------------------------------- /docs/usage/available-commands/index.md: -------------------------------------------------------------------------------- 1 | # Available commands 2 | 3 | `wp-react-starter` comes with many commands (implemented as NPM scripts), which simplifies how you can control and use all the functions this boilerplate provides. 4 | 5 | Here is a complete list of all available commands with a short description, separated into commands for the root project (applies to the compleat monorepo) and single plugins. 6 | 7 | {% page-ref page="root.md" %} 8 | 9 | {% page-ref page="plugin.md" %} 10 | 11 | {% page-ref page="package.md" %} 12 | -------------------------------------------------------------------------------- /packages/utils/LICENSE: -------------------------------------------------------------------------------- 1 | Utility functionality for all your WordPress plugins. 2 | Copyright (C) 2020 devowl.io GmbH 3 | 4 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. 5 | 6 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 7 | 8 | You should have received a copy of the GNU General Public License along with this program. If not, see . -------------------------------------------------------------------------------- /packages/utils/LICENSE_3RD_PARTY_PHP.md: -------------------------------------------------------------------------------- 1 | # Project Licenses 2 | This file was generated by the [PHP Legal Licenses](https://github.com/Comcast/php-legal-licenses) utility. It contains the name, version and commit sha, description, homepage, and license information for every dependency in this project. 3 | 4 | ## Dependencies 5 | 6 | -------------------------------------------------------------------------------- /packages/utils/README.md: -------------------------------------------------------------------------------- 1 | # `@wp-reactjs-multi-starter/utils` 2 | 3 | Some utility functionality for your WordPress plugins. 4 | 5 | ## TypeScript 6 | 7 | TypeScript coding should be placed in `lib`. It is not compiled from TS to ES6 code because it is directly consumed through the `babel-loader` in your webpack configuration. If you want to add functionality to this module be sure that this is needed for all your plugins - otherwise put it directly to the plugin. 8 | 9 | ## PHP 10 | 11 | PHP coding should be placed in `src`. 12 | -------------------------------------------------------------------------------- /packages/utils/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp-reactjs-multi-starter/utils", 3 | "description": "Utility functionality for all your WordPress plugins", 4 | "type": "library", 5 | "license": "GPL-3.0-or-later", 6 | "config": { 7 | "sort-packages": true 8 | }, 9 | "archive": { 10 | "exclude": [ 11 | "lib", 12 | "tests" 13 | ] 14 | }, 15 | "authors": [ 16 | { 17 | "name": "Matthias Günter" 18 | } 19 | ], 20 | "extra": { 21 | "clean-all-except": [ 22 | "src", 23 | "dist", 24 | "dev", 25 | "languages" 26 | ], 27 | "metasyntactical/composer-plugin-license-check": { 28 | "whitelist": [ 29 | "MIT", 30 | "ISC", 31 | "BSD-2-Clause", 32 | "BSD-3-Clause", 33 | "BSD", 34 | "Apache-2.0", 35 | "Apache2", 36 | "Artistic-2.0", 37 | "WTFPL", 38 | "CC-0", 39 | "CC0-1.0", 40 | "MPL-2.0", 41 | "ZLib", 42 | "Unlicense", 43 | "GPL-2.0", 44 | "GPL-2.0-or-later", 45 | "GPL-3", 46 | "GPL-3.0-or-later", 47 | "LGPL-3.0-or-later" 48 | ], 49 | "blacklist": [ 50 | "*" 51 | ], 52 | "packages": [ 53 | "ignore-packages-here@1.0.0" 54 | ] 55 | } 56 | }, 57 | "autoload": { 58 | "psr-4": { 59 | "MatthiasWeb\\Utils\\": "src/" 60 | } 61 | }, 62 | "autoload-dev": { 63 | "psr-4": { 64 | "MatthiasWeb\\Utils\\Test\\": "test/phpunit/" 65 | } 66 | }, 67 | "minimum-stability": "dev", 68 | "require-dev": { 69 | "10up/wp_mock": "^0.4.2", 70 | "antecedent/patchwork": "^2.1.12", 71 | "comcast/php-legal-licenses": "^1.1.2", 72 | "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", 73 | "metasyntactical/composer-plugin-license-check": "^0.5.0", 74 | "php-stubs/wordpress-stubs": "^5.3.2", 75 | "phpcompatibility/php-compatibility": "^9.3", 76 | "phpunit/phpunit": "^7", 77 | "rregeer/phpunit-coverage-check": "^0.3.1", 78 | "slevomat/coding-standard": "^6.0@dev", 79 | "squizlabs/php_codesniffer": "^3.5", 80 | "wp-coding-standards/wpcs": "^2.2" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/utils/devops/.gitlab/.gitlab-ci.ts: -------------------------------------------------------------------------------- 1 | import { ExtendConfigFunction } from "node-gitlab-ci"; 2 | import { createPackageJobs } from "../../../../.gitlab-ci"; 3 | 4 | const extendConfig: ExtendConfigFunction = async (config) => { 5 | createPackageJobs(config, __dirname, "packages"); 6 | 7 | await config.include(__dirname, ["./stage-*.ts"]); 8 | }; 9 | 10 | export { extendConfig }; 11 | -------------------------------------------------------------------------------- /packages/utils/devops/.gitlab/stage-build.ts: -------------------------------------------------------------------------------- 1 | import { ExtendConfigFunction } from "node-gitlab-ci"; 2 | import { createPackageJobs } from "../../../../.gitlab-ci"; 3 | import { EsLintMacroArgs, PhpCsMacroArgs } from "../../../../devops/.gitlab/stage-build"; 4 | 5 | const extendConfig: ExtendConfigFunction = async (config) => { 6 | const { prefix } = createPackageJobs(config, __dirname, "packages"); 7 | 8 | // Lint JavaScript/TypeScript coding 9 | config.from("lint eslint", { prefix }); 10 | 11 | // Lint PHP coding 12 | config.from("lint phpcs", { prefix }); 13 | }; 14 | 15 | export { extendConfig }; 16 | -------------------------------------------------------------------------------- /packages/utils/devops/.gitlab/stage-test.ts: -------------------------------------------------------------------------------- 1 | import { ExtendConfigFunction } from "node-gitlab-ci"; 2 | import { createPackageJobs } from "../../../../.gitlab-ci"; 3 | import { PhpUnitMacroArgs, JestMacroArgs } from "../../../../devops/.gitlab/stage-test"; 4 | 5 | const extendConfig: ExtendConfigFunction = async (config) => { 6 | const { prefix } = createPackageJobs(config, __dirname, "packages"); 7 | 8 | // Test PHPUnit 9 | config.from("phpunit", { prefix }); 10 | 11 | // Test Jest 12 | config.from("jest", { prefix }); 13 | }; 14 | 15 | export { extendConfig }; 16 | -------------------------------------------------------------------------------- /packages/utils/devops/.gitlab/stage-validate.ts: -------------------------------------------------------------------------------- 1 | import { ExtendConfigFunction } from "node-gitlab-ci"; 2 | import { createPackageJobs } from "../../../../.gitlab-ci"; 3 | import { YarnLicensesMacroArgs, ComposerLicensesMacroArgs } from "../../../../devops/.gitlab/stage-validate"; 4 | 5 | const extendConfig: ExtendConfigFunction = async (config) => { 6 | const { prefix } = createPackageJobs(config, __dirname, "packages"); 7 | 8 | // Validate licenses for yarn packages 9 | config.from("yarn licenses", { prefix }); 10 | 11 | // Validate licenses for composer packages 12 | config.from("composer licenses", { prefix }); 13 | }; 14 | 15 | export { extendConfig }; 16 | -------------------------------------------------------------------------------- /packages/utils/languages/backend/utils-de_DE.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devowlio/wp-react-starter/96f8fd6afcb68e9b921e66c9984b4451abb24937/packages/utils/languages/backend/utils-de_DE.mo -------------------------------------------------------------------------------- /packages/utils/languages/backend/utils-de_DE.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "Language-Team: \n" 6 | "MIME-Version: 1.0\n" 7 | "Content-Type: text/plain; charset=UTF-8\n" 8 | "Content-Transfer-Encoding: 8bit\n" 9 | "POT-Creation-Date: n/a\n" 10 | "PO-Revision-Date: 2020-07-13 09:37+0200\n" 11 | "X-Generator: Poedit 2.2.4\n" 12 | "Last-Translator: \n" 13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 14 | "Language: de_DE\n" 15 | 16 | #. translators: 17 | #: Service.php:81 18 | msgid "" 19 | "One or more WordPress plugins tried to call the WordPress REST API, which " 20 | "failed. Most likely a security plugin%s or a web server configuration " 21 | "disabled the REST API. Please make sure that the following REST API " 22 | "namespaces are reachable to use your plugin without problems:" 23 | msgstr "" 24 | "Ein oder mehrere WordPress-Plugins versuchten, die WordPress REST API " 25 | "aufzurufen, was fehlschlug. Höchstwahrscheinlich hat ein Sicherheits-" 26 | "Plugin%s oder eine Webserver-Konfiguration die REST-API deaktiviert. " 27 | "Bitte stelle sicher, dass die folgenden REST-API-Namensräume erreichbar " 28 | "sind, um das jeweilige Plugin ohne Probleme nutzen zu können:" 29 | 30 | #. translators: 31 | #: Service.php:89 32 | msgid "" 33 | "What is the WordPress REST API and how to enable it? %1$sLearn more%2$s." 34 | msgstr "" 35 | "Was ist die WordPress REST API und wie kann sie aktiviert werden? " 36 | "%1$sKlicke hier, um mehr darüber zu erfahren%2$s." 37 | 38 | #: Service.php:92 39 | msgid "" 40 | "https://devowl.io/knowledge-base/i-only-see-a-loading-spinner-what-can-i-" 41 | "do/" 42 | msgstr "" 43 | "https://devowl.io/knowledge-base/i-only-see-a-loading-spinner-what-can-i-" 44 | "do/" 45 | -------------------------------------------------------------------------------- /packages/utils/languages/backend/utils.pot: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "Last-Translator: FULL NAME \n" 6 | "Language-Team: LANGUAGE \n" 7 | "MIME-Version: 1.0\n" 8 | "Content-Type: text/plain; charset=UTF-8\n" 9 | "Content-Transfer-Encoding: 8bit\n" 10 | "POT-Creation-Date: n/a\n" 11 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 12 | "X-Generator: WP-CLI 2.4.0\n" 13 | 14 | #. translators: 15 | #: Service.php:81 16 | msgid "One or more WordPress plugins tried to call the WordPress REST API, which failed. Most likely a security plugin%s or a web server configuration disabled the REST API. Please make sure that the following REST API namespaces are reachable to use your plugin without problems:" 17 | msgstr "" 18 | 19 | #. translators: 20 | #: Service.php:89 21 | msgid "What is the WordPress REST API and how to enable it? %1$sLearn more%2$s." 22 | msgstr "" 23 | 24 | #: Service.php:92 25 | msgid "https://devowl.io/knowledge-base/i-only-see-a-loading-spinner-what-can-i-do/" 26 | msgstr "" 27 | -------------------------------------------------------------------------------- /packages/utils/languages/frontend/utils.pot: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "Last-Translator: FULL NAME \n" 6 | "Language-Team: LANGUAGE \n" 7 | "MIME-Version: 1.0\n" 8 | "Content-Type: text/plain; charset=UTF-8\n" 9 | "Content-Transfer-Encoding: 8bit\n" 10 | "POT-Creation-Date: n/a\n" 11 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 12 | "X-Generator: WP-CLI 2.4.0\n" 13 | -------------------------------------------------------------------------------- /packages/utils/lib/components/button.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { FC, ReactNode } from "react"; 3 | import classNames from "classnames"; 4 | 5 | enum ButtonType { 6 | Primary, 7 | Secondary 8 | } 9 | 10 | interface ButtonProps { 11 | [key: string]: any; 12 | className?: string; 13 | type?: ButtonType; 14 | children: ReactNode; 15 | } 16 | 17 | const Button: FC = ({ className, type, children, ...rest }) => { 18 | const buttonClassName = classNames(className, { 19 | "button-primary": type === ButtonType.Primary, 20 | "button-secondary": type === ButtonType.Secondary || !type 21 | }); 22 | return ( 23 | 26 | ); 27 | }; 28 | 29 | export { Button, ButtonType }; 30 | -------------------------------------------------------------------------------- /packages/utils/lib/components/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./button"; 2 | export * from "./notice"; 3 | -------------------------------------------------------------------------------- /packages/utils/lib/components/notice.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ReactNode, FC } from "react"; 3 | import classNames from "classnames"; 4 | 5 | enum NoticeType { 6 | Error = "Error", 7 | Info = "Info", 8 | Success = "Success" 9 | } 10 | 11 | interface NoticeProps { 12 | type?: NoticeType; 13 | children: ReactNode; 14 | } 15 | 16 | const Notice: FC = ({ type, children }) => { 17 | const classes = classNames({ 18 | notice: true, 19 | "notice-error": type === NoticeType.Error, 20 | "notice-info": type === NoticeType.Info, 21 | "notice-success": type === NoticeType.Success 22 | }); 23 | 24 | return ( 25 |
26 |

{children}

27 |
28 | ); 29 | }; 30 | 31 | export { Notice, NoticeType }; 32 | -------------------------------------------------------------------------------- /packages/utils/lib/factory/ajax/commonRequest.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | WP_REST_API_USE_GLOBAL_METHOD, 3 | RouteRequestInterface, 4 | RouteParamsInterface, 5 | RouteResponseInterface, 6 | RequestArgs, 7 | commonUrlBuilder, 8 | RouteHttpVerb 9 | } from "./"; 10 | import deepMerge from "deepmerge"; 11 | import Url from "url-parse"; 12 | import "whatwg-fetch"; // window.fetch polyfill 13 | import { parseResult } from "."; 14 | 15 | /** 16 | * Build and execute a specific REST query. 17 | * 18 | * @see urlBuilder 19 | * @returns Result of REST API 20 | * @throws 21 | */ 22 | async function commonRequest< 23 | TRequest extends RouteRequestInterface, 24 | TParams extends RouteParamsInterface, 25 | TResponse extends RouteResponseInterface 26 | >({ 27 | location, 28 | options, 29 | request: routeRequest, 30 | params, 31 | settings = {} 32 | }: { 33 | request?: TRequest; 34 | params?: TParams; 35 | settings?: Partial<{ -readonly [P in keyof Request]: Request[P] }>; 36 | } & RequestArgs): Promise { 37 | const url = commonUrlBuilder({ location, params, nonce: false, options }); 38 | 39 | // Use global parameter (see https://developer.wordpress.org/rest-api/using-the-rest-api/global-parameters/) 40 | if (WP_REST_API_USE_GLOBAL_METHOD && location.method && location.method !== RouteHttpVerb.GET) { 41 | settings.method = RouteHttpVerb.POST; 42 | } else { 43 | settings.method = RouteHttpVerb.GET; 44 | } 45 | 46 | // Request with GET/HEAD method cannot have body 47 | const apiUrl = new Url(url, true); 48 | const allowBody = ["HEAD", "GET"].indexOf(settings.method) === -1; 49 | if (!allowBody && routeRequest) { 50 | apiUrl.set("query", deepMerge(apiUrl.query, routeRequest)); 51 | } 52 | 53 | const apiUrlBuilt = apiUrl.toString(); 54 | const result = await window.fetch( 55 | apiUrlBuilt, 56 | deepMerge.all([ 57 | settings, 58 | { 59 | headers: { 60 | "Content-Type": "application/json;charset=utf-8", 61 | "X-WP-Nonce": options.restNonce 62 | }, 63 | body: allowBody ? JSON.stringify(routeRequest) : undefined 64 | } 65 | ]) 66 | ); 67 | 68 | // `window.fetch` does not throw an error if the server response an error code. 69 | if (!result.ok) { 70 | let responseJSON = undefined; 71 | try { 72 | responseJSON = await parseResult(apiUrlBuilt, result); 73 | } catch (e) { 74 | // Silence is golden. 75 | } 76 | 77 | // Set this request as failing so the endpoint is probably corrupt (see `corrupRestApi.tsx`) 78 | settings.method === RouteHttpVerb.GET && 79 | (window.detectCorrupRestApiFailed = (window.detectCorrupRestApiFailed || 0) + 1); 80 | 81 | const resultAny = result as any; 82 | resultAny.responseJSON = responseJSON; 83 | throw resultAny; 84 | } 85 | 86 | return parseResult(apiUrlBuilt, result); 87 | } 88 | 89 | export { commonRequest }; 90 | -------------------------------------------------------------------------------- /packages/utils/lib/factory/ajax/corruptRestApi.tsx: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | /** 4 | * This number indicates the failed `GET` requests for all REST API calls. 5 | * See also `commonRequest.tsx`. 6 | */ 7 | detectCorrupRestApiFailed: number; 8 | } 9 | } 10 | 11 | const WAIT_TO_TEST = 10000; 12 | 13 | const NOTICE_ID = "notice-corrupt-rest-api"; 14 | 15 | /** 16 | * Register a new endpoint which needs to resolve to a valid JSON result. In this way we 17 | * can detect a corrupt REST API namespace e. g. it is blocked through a security plugin. 18 | */ 19 | function handleCorrupRestApi(resolve: Record Promise>, forceRerequest = false) { 20 | // Initially set 21 | window.detectCorrupRestApiFailed = window.detectCorrupRestApiFailed || 0; 22 | 23 | setTimeout(async () => { 24 | const notice = document.getElementById(NOTICE_ID); 25 | 26 | // Only in backend and when a corrupt REST API detected 27 | if (notice && (window.detectCorrupRestApiFailed > 0 || forceRerequest)) { 28 | for (const namespace of Object.keys(resolve)) { 29 | try { 30 | await resolve[namespace](); 31 | } catch (e) { 32 | notice.style.display = "block"; 33 | const li = document.createElement("li"); 34 | li.innerHTML = `- ${namespace}`; 35 | notice.childNodes[1].appendChild(li); 36 | } 37 | } 38 | } 39 | }, WAIT_TO_TEST); 40 | } 41 | 42 | export { handleCorrupRestApi }; 43 | -------------------------------------------------------------------------------- /packages/utils/lib/factory/ajax/createRequestFactory.tsx: -------------------------------------------------------------------------------- 1 | import { BaseOptions } from "../../options"; 2 | import { WithOptional } from "../.."; 3 | import { 4 | commonUrlBuilder, 5 | RouteRequestInterface, 6 | RouteParamsInterface, 7 | RouteResponseInterface, 8 | commonRequest 9 | } from "./"; 10 | 11 | /** 12 | * Create a uri builder and request function for your specific plugin depending 13 | * on the rest root and additional parameters. 14 | * 15 | * @param options 16 | * @see urlBuilder 17 | * @see request 18 | */ 19 | function createRequestFactory(options: BaseOptions) { 20 | const urlBuilder = (passOptions: WithOptional[0], "options">) => 21 | commonUrlBuilder({ 22 | ...passOptions, 23 | options: { 24 | restNamespace: options.restNamespace, 25 | restNonce: options.restNonce, 26 | restQuery: options.restQuery, 27 | restRoot: options.restRoot 28 | } 29 | }); 30 | 31 | const request = < 32 | Request extends RouteRequestInterface, 33 | Params extends RouteParamsInterface, 34 | Response extends RouteResponseInterface 35 | >( 36 | passOptions: WithOptional[0], "options"> & { 37 | params?: Params; 38 | request?: Request; 39 | } 40 | ): Promise => 41 | commonRequest({ 42 | ...passOptions, 43 | options: { 44 | restNamespace: options.restNamespace, 45 | restNonce: options.restNonce, 46 | restQuery: options.restQuery, 47 | restRoot: options.restRoot 48 | } 49 | }); 50 | 51 | return { 52 | urlBuilder, 53 | request 54 | }; 55 | } 56 | 57 | export { createRequestFactory }; 58 | -------------------------------------------------------------------------------- /packages/utils/lib/factory/ajax/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./commonUrlBuilder"; 2 | export * from "./commonRequest"; 3 | export * from "./createRequestFactory"; 4 | export * from "./routeHttpVerbEnum"; 5 | export * from "./parseResult"; 6 | export * from "./corruptRestApi"; 7 | -------------------------------------------------------------------------------- /packages/utils/lib/factory/ajax/parseResult.tsx: -------------------------------------------------------------------------------- 1 | import { RouteResponseInterface } from "."; 2 | 3 | /** 4 | * Get the result of the `Response`. It also handles multiline responses, e. g. 5 | * a PHP `Notice:` message is output through a conflicting plugin: 6 | */ 7 | async function parseResult(url: string, result: Response) { 8 | const cloneForFallback = result.clone(); 9 | try { 10 | return (await result.json()) as TResponse; 11 | } catch (e) { 12 | // Something went wrong, try each line as result of a JSON string 13 | const body = await cloneForFallback.text(); 14 | console.warn(`The response of ${url} contains unexpected JSON, try to resolve the JSON line by line...`, { 15 | body 16 | }); 17 | let lastError: any; 18 | for (const line of body.split("\n")) { 19 | if (line.startsWith("[") || line.startsWith("{")) { 20 | try { 21 | return JSON.parse(line) as TResponse; 22 | } catch (e) { 23 | lastError = e; 24 | } 25 | } 26 | } 27 | throw lastError; 28 | } 29 | } 30 | 31 | export { parseResult }; 32 | -------------------------------------------------------------------------------- /packages/utils/lib/factory/ajax/routeHttpVerbEnum.tsx: -------------------------------------------------------------------------------- 1 | enum RouteHttpVerb { 2 | GET = "GET", 3 | POST = "POST", 4 | PUT = "PUT", 5 | DELETE = "DELETE", 6 | PATCH = "PATCH" 7 | } 8 | 9 | export { RouteHttpVerb }; 10 | -------------------------------------------------------------------------------- /packages/utils/lib/factory/context.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, FC, useContext } from "react"; 2 | 3 | /* istanbul ignore next: no logic in this factory! */ 4 | /** 5 | * Create context relevant objects to use within React. 6 | * 7 | * @param object 8 | * @returns 9 | */ 10 | function createContextFactory(object: T) { 11 | /** 12 | * MobX stores collection 13 | */ 14 | const StoreContext = createContext(object); 15 | 16 | /** 17 | * MobX HOC to get the context via hook. 18 | * 19 | * @param children 20 | */ 21 | const StoreProvider: FC<{}> = ({ children }) => ( 22 | {children} 23 | ); 24 | 25 | /** 26 | * Get all the MobX stores via a single hook. 27 | */ 28 | const useStores = () => useContext(StoreContext); 29 | 30 | return { StoreContext, StoreProvider, useStores }; 31 | } 32 | 33 | export { createContextFactory }; 34 | -------------------------------------------------------------------------------- /packages/utils/lib/factory/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./context"; 2 | export * from "./ajax"; 3 | export * from "./i18n"; 4 | -------------------------------------------------------------------------------- /packages/utils/lib/helpers.tsx: -------------------------------------------------------------------------------- 1 | const untrailingslashit = (str: string): string => 2 | str.endsWith("/") || str.endsWith("\\") ? untrailingslashit(str.slice(0, -1)) : str; 3 | const trailingslashit = (str: string): string => `${untrailingslashit(str)}/`; 4 | 5 | // Allows to make an interface extension and make some properties optional (https://git.io/JeK6J) 6 | type AllKeyOf = T extends never ? never : keyof T; 7 | type Optional = { [P in Extract]?: T[P] }; 8 | type WithOptional> = T extends never ? never : Omit & Optional; 9 | 10 | export { untrailingslashit, trailingslashit, AllKeyOf, Optional, WithOptional }; 11 | -------------------------------------------------------------------------------- /packages/utils/lib/index.tsx: -------------------------------------------------------------------------------- 1 | import "setimmediate"; // Polyfill for yielding 2 | 3 | export * from "./options"; 4 | export * from "./helpers"; 5 | export * from "./factory"; 6 | export * from "./wp-api"; 7 | export * from "./components"; 8 | -------------------------------------------------------------------------------- /packages/utils/lib/options.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * See PHP file inc/Assets.php. 3 | */ 4 | abstract class BaseOptions { 5 | public slug: string; 6 | public textDomain: string; 7 | public version: string; 8 | public restUrl?: string; 9 | public restNamespace?: string; 10 | public restRoot?: string; 11 | public restQuery?: {}; 12 | public restNonce?: string; 13 | public publicUrl?: string; 14 | 15 | /** 16 | * Convert a slug like "my-plugin" to "myPlugin". This can 17 | * be useful for library naming (window[""] is bad because the hyphens). 18 | * 19 | * @param slug 20 | * @returns 21 | */ 22 | public static slugCamelCase(slug: string) { 23 | return slug.replace(/-([a-z])/g, (g) => g[1].toUpperCase()); 24 | } 25 | 26 | /** 27 | * Get the slug from the current process (webpack) instead of the PHP plugin output. 28 | * For some cases you need to use that. 29 | * 30 | * @param env 31 | * @param camelCase 32 | */ 33 | public static getPureSlug(env: typeof process.env, camelCase = false) { 34 | return camelCase ? BaseOptions.slugCamelCase(env.slug) : env.slug; 35 | } 36 | } 37 | 38 | export { BaseOptions }; 39 | -------------------------------------------------------------------------------- /packages/utils/lib/types/global.d.ts: -------------------------------------------------------------------------------- 1 | // @see https://github.com/microsoft/TypeScript/issues/15031#issuecomment-407131785 2 | declare module "*"; 3 | -------------------------------------------------------------------------------- /packages/utils/lib/wp-api/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./rest.plugin.get"; 2 | -------------------------------------------------------------------------------- /packages/utils/lib/wp-api/rest.plugin.get.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | RouteLocationInterface, 3 | RouteHttpVerb, 4 | RouteResponseInterface, 5 | RouteRequestInterface, 6 | RouteParamsInterface 7 | } from "../factory"; 8 | 9 | export const locationRestPluginGet: RouteLocationInterface = { 10 | path: "/plugin", 11 | method: RouteHttpVerb.GET 12 | }; 13 | 14 | export type RequestRoutePluginGet = RouteRequestInterface; 15 | 16 | export type ParamsRoutePluginGet = RouteParamsInterface; 17 | 18 | export interface ResponseRoutePluginGet extends RouteResponseInterface { 19 | Name: string; 20 | PluginURI: string; 21 | Version: string; 22 | Description: string; 23 | Author: string; 24 | AuthorURI: string; 25 | TextDomain: string; 26 | DomainPath: string; 27 | Network: boolean; 28 | Title: string; 29 | AuthorName: string; 30 | } 31 | -------------------------------------------------------------------------------- /packages/utils/scripts/Gruntfile.ts: -------------------------------------------------------------------------------- 1 | import { applyDefaultRunnerConfiguration } from "../../../common/Gruntfile"; 2 | 3 | function setupGrunt(grunt: IGrunt) { 4 | // Project configuration (the base path is set to the projects root, so ../ level up) 5 | grunt.initConfig({}); 6 | 7 | // Load WP ReactJS Starter initial tasks 8 | applyDefaultRunnerConfiguration(grunt); 9 | } 10 | 11 | // See https://github.com/samuelneff/grunt-script-template/blob/2864d3cb1cf424d9ab83fdd2ed5a6c24917cf19b/Gruntfile.ts#L54 12 | export = setupGrunt; 13 | -------------------------------------------------------------------------------- /packages/utils/scripts/webpack.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-default-export */ 2 | import { createDefaultSettings } from "../../../common/webpack.factory"; 3 | 4 | export default createDefaultSettings("package"); 5 | -------------------------------------------------------------------------------- /packages/utils/src/Activator.php: -------------------------------------------------------------------------------- 1 | show_errors(false); 38 | $suppress_errors = $wpdb->suppress_errors(false); 39 | $errorLevel = error_reporting(0); 40 | } 41 | 42 | if ($installThisCallable === null) { 43 | $this->dbDelta($errorlevel); 44 | } else { 45 | call_user_func($installThisCallable); 46 | } 47 | 48 | if ($errorlevel === false) { 49 | $wpdb->show_errors($show_errors); 50 | $wpdb->suppress_errors($suppress_errors); 51 | error_reporting($errorLevel); 52 | } 53 | 54 | if ($installThisCallable === null) { 55 | update_option( 56 | $this->getPluginConstant(PluginReceiver::$PLUGIN_CONST_OPT_PREFIX) . '_db_version', 57 | $this->getPluginConstant(PluginReceiver::$PLUGIN_CONST_VERSION) 58 | ); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/utils/src/Base.php: -------------------------------------------------------------------------------- 1 | getPluginConstant(self::$PLUGIN_CONST_DEBUG)) { 24 | $log = 25 | (empty($methodOrFunction) ? '' : '(' . $methodOrFunction . ')') . 26 | ': ' . 27 | (is_string($message) ? $message : json_encode($message)); 28 | $log = $this->getPluginConstant() . '_DEBUG ' . $log; 29 | error_log($log); 30 | return $log; 31 | } 32 | return ''; 33 | } 34 | 35 | /** 36 | * Get a plugin relevant table name depending on the _DB_PREFIX constant. 37 | * 38 | * @param string $name Append this name to the plugins relevant table with _{$name}. 39 | * @return string 40 | */ 41 | public function getTableName($name = '') { 42 | global $wpdb; 43 | return $wpdb->prefix . 44 | $this->getPluginConstant(self::$PLUGIN_CONST_DB_PREFIX) . 45 | (empty($name) ? '' : '_' . $name); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/utils/src/PackageLocalization.php: -------------------------------------------------------------------------------- 1 | rootSlug = $rootSlug; 29 | $this->packageDir = $packageDir; 30 | } 31 | 32 | /** 33 | * Put your language overrides here! 34 | * 35 | * @param string $locale 36 | * @return string 37 | */ 38 | protected function override($locale) { 39 | switch ($locale) { 40 | // Put your overrides here! 41 | case 'de_AT': 42 | case 'de_CH': 43 | case 'de_CH_informal': 44 | case 'de_DE_formal': 45 | return 'de_DE'; 46 | break; 47 | default: 48 | break; 49 | } 50 | return $locale; 51 | } 52 | 53 | /** 54 | * Get the directory where the languages folder exists. 55 | * 56 | * @param string $type 57 | * @return string[] 58 | */ 59 | protected function getPackageInfo($type) { 60 | $textdomain = $this->getRootSlug() . '-' . $this->getPackage(); 61 | if ($type === Localization::$PACKAGE_INFO_BACKEND) { 62 | return [path_join($this->getPackageDir(), 'languages/backend'), $textdomain, $this->getPackage()]; 63 | } else { 64 | return [path_join($this->getPackageDir(), 'languages/frontend/json'), $textdomain, $this->getPackage()]; 65 | } 66 | } 67 | 68 | /** 69 | * Getter. 70 | * 71 | * @return string 72 | * @codeCoverageIgnore 73 | */ 74 | public function getRootSlug() { 75 | return $this->rootSlug; 76 | } 77 | 78 | /** 79 | * Get package name. 80 | * 81 | * @return string 82 | */ 83 | public function getPackage() { 84 | return basename($this->getPackageDir()); 85 | } 86 | 87 | /** 88 | * Getter. 89 | * 90 | * @return string 91 | * @codeCoverageIgnore 92 | */ 93 | public function getPackageDir() { 94 | return $this->packageDir; 95 | } 96 | 97 | /** 98 | * New instance. 99 | * 100 | * @param string $rootSlug 101 | * @param string $packageDir 102 | * @return PackageLocalization 103 | * @codeCoverageIgnore Instance getter 104 | */ 105 | public static function instance($rootSlug, $packageDir) { 106 | return new PackageLocalization($rootSlug, $packageDir); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /packages/utils/src/PluginReceiver.php: -------------------------------------------------------------------------------- 1 | getPluginConstantPrefix(); 35 | if ($name === null) { 36 | return $prefix; 37 | } 38 | 39 | $cname = $prefix . '_' . $name; 40 | return defined($cname) ? constant($cname) : null; 41 | } 42 | 43 | /** 44 | * Get a new instance of a plugin class from string (supports namespaces, too). 45 | * 46 | * @param string $name 47 | * @param mixed $parameter,... Parameters to the method 48 | * @return mixed 49 | */ 50 | public function getPluginClassInstance($name) { 51 | $fqn = $this->getPluginConstant(self::$PLUGIN_CONST_NS) . '\\' . $name; 52 | $parameters = array_slice(func_get_args(), 1); 53 | return new $fqn(...$parameters); 54 | } 55 | 56 | /** 57 | * Get the functions instance. 58 | * 59 | * @return mixed 60 | */ 61 | public function getCore() { 62 | return call_user_func([$this->getPluginConstant(self::$PLUGIN_CONST_NS) . '\\Core', 'getInstance']); 63 | } 64 | 65 | /** 66 | * Get the plugins' constant prefix. Will be overwritten by the UtilsProvider class. 67 | * 68 | * @return string 69 | */ 70 | public function getPluginConstantPrefix() { 71 | if (defined('CONSTANT_PREFIX')) { 72 | return constant('CONSTANT_PREFIX'); 73 | } else { 74 | // @codeCoverageIgnoreStart 75 | error_log(__FILE__ . ': Something went wrong with a newly installed plugin.'); 76 | exit(1); 77 | // @codeCoverageIgnoreEnd 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/utils/test/jest.config.js: -------------------------------------------------------------------------------- 1 | // Unfortunately the jest config can not be placed directly to package.json 2 | // because then it does not support inheritance. 3 | 4 | const base = require("../../../common/jest.base"); 5 | 6 | module.exports = base; 7 | -------------------------------------------------------------------------------- /packages/utils/test/jest/__mocks__/wp.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-default-export */ 2 | export default { 3 | i18n: { 4 | setLocaleData: jest.fn() 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /packages/utils/test/jest/components/__snapshots__/button.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`button should render button with default arguments 1`] = ` 4 | 9 | `; 10 | 11 | exports[`button should render button with primary type 1`] = ` 12 | 17 | `; 18 | 19 | exports[`button should render button with title attribute 1`] = ` 20 | 26 | `; 27 | -------------------------------------------------------------------------------- /packages/utils/test/jest/components/__snapshots__/notice.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`notice should render notice with default arguments 1`] = ` 4 |
7 |

8 | Test 9 |

10 |
11 | `; 12 | 13 | exports[`notice should render notice with error type 1`] = ` 14 |
17 |

18 | Test 19 |

20 |
21 | `; 22 | 23 | exports[`notice should render notice with info type 1`] = ` 24 |
27 |

28 | Test 29 |

30 |
31 | `; 32 | 33 | exports[`notice should render notice with success type 1`] = ` 34 |
37 |

38 | Test 39 |

40 |
41 | `; 42 | -------------------------------------------------------------------------------- /packages/utils/test/jest/components/button.test.tsx: -------------------------------------------------------------------------------- 1 | import renderer from "react-test-renderer"; 2 | import React from "react"; 3 | import { Button, ButtonType } from "../../../lib"; 4 | import { Provider } from "../helpers"; 5 | 6 | describe("button", () => { 7 | it("should render button with default arguments", () => { 8 | const tree = renderer 9 | .create( 10 | 11 | 12 | 13 | ) 14 | .toJSON(); 15 | expect(tree).toMatchSnapshot(); 16 | }); 17 | 18 | it("should render button with primary type", () => { 19 | const tree = renderer 20 | .create( 21 | 22 | 23 | 24 | ) 25 | .toJSON(); 26 | expect(tree).toMatchSnapshot(); 27 | }); 28 | 29 | it("should render button with title attribute", () => { 30 | const tree = renderer 31 | .create( 32 | 33 | 34 | 35 | ) 36 | .toJSON(); 37 | expect(tree).toMatchSnapshot(); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/utils/test/jest/components/notice.test.tsx: -------------------------------------------------------------------------------- 1 | import renderer from "react-test-renderer"; 2 | import React from "react"; 3 | import { Notice, NoticeType } from "../../../lib"; 4 | import { Provider } from "../helpers"; 5 | 6 | describe("notice", () => { 7 | it("should render notice with default arguments", () => { 8 | const tree = renderer 9 | .create( 10 | 11 | Test 12 | 13 | ) 14 | .toJSON(); 15 | expect(tree).toMatchSnapshot(); 16 | }); 17 | 18 | it("should render notice with error type", () => { 19 | const tree = renderer 20 | .create( 21 | 22 | Test 23 | 24 | ) 25 | .toJSON(); 26 | expect(tree).toMatchSnapshot(); 27 | }); 28 | 29 | it("should render notice with info type", () => { 30 | const tree = renderer 31 | .create( 32 | 33 | Test 34 | 35 | ) 36 | .toJSON(); 37 | expect(tree).toMatchSnapshot(); 38 | }); 39 | 40 | it("should render notice with success type", () => { 41 | const tree = renderer 42 | .create( 43 | 44 | Test 45 | 46 | ) 47 | .toJSON(); 48 | expect(tree).toMatchSnapshot(); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /packages/utils/test/jest/factory/ajax/createRequestFactory.test.tsx: -------------------------------------------------------------------------------- 1 | import { createRequestFactory } from "../../../../lib/factory/ajax/createRequestFactory"; 2 | 3 | jest.mock("../../../../lib/factory/ajax/commonUrlBuilder"); 4 | jest.mock("../../../../lib/factory/ajax/commonRequest"); 5 | 6 | const { commonUrlBuilder } = require("../../../../lib/factory/ajax/commonUrlBuilder"); 7 | const { commonRequest } = require("../../../../lib/factory/ajax/commonRequest"); 8 | 9 | describe("createRequestFactory", () => { 10 | const restOptions = { 11 | restRoot: "http://localhost/wp-json/", 12 | restQuery: {}, 13 | restNamespace: "jest/v1", 14 | restNonce: "jd§4,dwD" 15 | }; 16 | const opts = { 17 | slug: "jest", 18 | textDomain: "jest", 19 | version: "1.0.0", 20 | ...restOptions 21 | }; 22 | const urlBuilderOpts = { 23 | location: { 24 | path: "/users" 25 | } 26 | }; 27 | const url = opts.restRoot + opts.restNamespace + urlBuilderOpts.location.path; 28 | const response = [ 29 | { 30 | id: "5", 31 | name: "John Smith" 32 | } 33 | ]; 34 | 35 | it("should create correct factory urlBuilder", () => { 36 | commonUrlBuilder.mockImplementation(() => url); 37 | 38 | const { urlBuilder } = createRequestFactory(opts); 39 | const actual = urlBuilder(urlBuilderOpts); 40 | 41 | expect(commonUrlBuilder).toHaveBeenCalledWith({ ...urlBuilderOpts, options: restOptions }); 42 | expect(actual).toEqual(url); 43 | }); 44 | 45 | it("should create correct factory request", () => { 46 | commonRequest.mockImplementation(() => response); 47 | 48 | const { request } = createRequestFactory(opts); 49 | const actual = request(urlBuilderOpts); 50 | 51 | expect(commonRequest).toHaveBeenCalledWith({ ...urlBuilderOpts, options: restOptions }); 52 | expect(actual).toEqual(response); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /packages/utils/test/jest/helpers.test.tsx: -------------------------------------------------------------------------------- 1 | import { untrailingslashit, trailingslashit } from "../../lib"; 2 | 3 | describe("helpers", () => { 4 | describe("untrailingslashit", () => { 5 | const should = "/home/foo/bar"; 6 | it("should remove last slash", () => { 7 | const path = "/home/foo/bar/"; 8 | 9 | const actual = untrailingslashit(path); 10 | 11 | expect(actual).toBe(should); 12 | }); 13 | 14 | it("should remove double slash", () => { 15 | const path = "/home/foo/bar//"; 16 | 17 | const actual = untrailingslashit(path); 18 | 19 | expect(actual).toBe(should); 20 | }); 21 | 22 | it("should do nothing", () => { 23 | const path = "/home/foo/bar"; 24 | 25 | const actual = untrailingslashit(path); 26 | 27 | expect(actual).toBe(should); 28 | }); 29 | }); 30 | 31 | describe("trailingslashit", () => { 32 | const should = "/home/foo/bar/"; 33 | 34 | it("should add slash", () => { 35 | const path = "/home/foo/bar"; 36 | 37 | const actual = trailingslashit(path); 38 | 39 | expect(actual).toBe(should); 40 | }); 41 | 42 | it("should do nothing", () => { 43 | const path = "/home/foo/bar/"; 44 | 45 | const actual = trailingslashit(path); 46 | 47 | expect(actual).toBe(should); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /packages/utils/test/jest/helpers/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./provider"; 2 | -------------------------------------------------------------------------------- /packages/utils/test/jest/helpers/provider.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | /** 4 | * Use this common Provider in all your snapshot tests with all 5 | * your available providers. Currently there is no provider for the utils package. 6 | * 7 | * @param children 8 | */ 9 | const Provider: React.FC = ({ children }) => <>{children}; 10 | 11 | export { Provider }; 12 | -------------------------------------------------------------------------------- /packages/utils/test/jest/options.test.tsx: -------------------------------------------------------------------------------- 1 | import { BaseOptions } from "../../lib"; 2 | 3 | describe("BaseOptions", () => { 4 | describe("slugCamelCase", () => { 5 | it("should convert slug to camel case with hyphen", () => { 6 | const slug = "my-plugin"; 7 | const should = "myPlugin"; 8 | 9 | const actual = BaseOptions.slugCamelCase(slug); 10 | 11 | expect(actual).toBe(should); 12 | }); 13 | 14 | it("should convert slug to camel case without hyphen", () => { 15 | const slug = "myplugin"; 16 | const should = "myplugin"; 17 | 18 | const actual = BaseOptions.slugCamelCase(slug); 19 | 20 | expect(actual).toBe(should); 21 | }); 22 | }); 23 | 24 | describe("getPureSlug", () => { 25 | const env = { slug: "my-plugin" }; 26 | 27 | it("should return slug from current process", () => { 28 | const should = String(env.slug); 29 | 30 | const actual = BaseOptions.getPureSlug(env); 31 | 32 | expect(actual).toBe(should); 33 | }); 34 | 35 | it("should return slug from current process with camel case", () => { 36 | const should = "myPlugin"; 37 | 38 | const actual = BaseOptions.getPureSlug(env, true); 39 | 40 | expect(actual).toBe(should); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/utils/test/patchwork.json: -------------------------------------------------------------------------------- 1 | { 2 | "redefinable-internals": [ 3 | "call_user_func", 4 | "error_log", 5 | "error_reporting", 6 | "file_exists", 7 | "include", 8 | "defined", 9 | "constant", 10 | "is_readable", 11 | "realpath", 12 | "glob", 13 | "basename", 14 | "filemtime", 15 | "file_get_contents", 16 | "is_dir" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/utils/test/phpunit.bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | phpunit 15 | 16 | 17 | 18 | 19 | ../src 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../common/tsconfig.json", 3 | "include": ["lib/**/*", "scripts/**/*.ts", "test/jest/**/*"], 4 | "compilerOptions": { 5 | "outDir": "types/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/LICENSE: -------------------------------------------------------------------------------- 1 | WordPress Plugin Boilerplate using new web technologies. 2 | Copyright (C) 2020 devowl.io GmbH 3 | 4 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. 5 | 6 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 7 | 8 | You should have received a copy of the GNU General Public License along with this program. If not, see . -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/LICENSE_3RD_PARTY_PHP.md: -------------------------------------------------------------------------------- 1 | # Project Licenses 2 | This file was generated by the [PHP Legal Licenses](https://github.com/Comcast/php-legal-licenses) utility. It contains the name, version and commit sha, description, homepage, and license information for every dependency in this project. 3 | 4 | ## Dependencies 5 | 6 | ### wp-reactjs-multi-starter/utils (Version dev-feat/multipackage | no sha) 7 | Utility functionality for all your WordPress plugins 8 | Homepage: Not configured. 9 | Licenses Used: MIT 10 | Utility functionality for all your WordPress plugins. 11 | Copyright (C) 2020 devowl.io GmbH 12 | 13 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. 14 | 15 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License along with this program. If not, see . 18 | 19 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "WordPress Plugin Boilerplate using new web technologies.", 3 | "license": "GPL-3.0-or-later", 4 | "config": { 5 | "sort-packages": true 6 | }, 7 | "authors": [ 8 | { 9 | "name": "Matthias Günter" 10 | } 11 | ], 12 | "extra": { 13 | "metasyntactical/composer-plugin-license-check": { 14 | "whitelist": [ 15 | "MIT", 16 | "ISC", 17 | "BSD-2-Clause", 18 | "BSD-3-Clause", 19 | "BSD", 20 | "Apache-2.0", 21 | "Apache2", 22 | "Artistic-2.0", 23 | "WTFPL", 24 | "CC-0", 25 | "CC0-1.0", 26 | "MPL-2.0", 27 | "ZLib", 28 | "Unlicense", 29 | "GPL-2.0", 30 | "GPL-2.0-or-later", 31 | "GPL-3", 32 | "GPL-3.0-or-later", 33 | "LGPL-3.0-or-later" 34 | ], 35 | "blacklist": [ 36 | "*" 37 | ], 38 | "packages": [ 39 | "ignore-packages-here@1.0.0" 40 | ] 41 | } 42 | }, 43 | "require": { 44 | "wp-reactjs-multi-starter/utils": "@dev" 45 | }, 46 | "require-dev": { 47 | "10up/wp_mock": "^0.4.2", 48 | "antecedent/patchwork": "^2.1.12", 49 | "comcast/php-legal-licenses": "^1.1.2", 50 | "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", 51 | "metasyntactical/composer-plugin-license-check": "^0.5.0", 52 | "php-stubs/wordpress-stubs": "^5.3.2", 53 | "phpcompatibility/php-compatibility": "^9.3", 54 | "phpdocumentor/phpdocumentor": "dev-master", 55 | "phpunit/phpunit": "^7", 56 | "rregeer/phpunit-coverage-check": "^0.3.1", 57 | "slevomat/coding-standard": "^6.0@dev", 58 | "squizlabs/php_codesniffer": "^3.5", 59 | "wp-coding-standards/wpcs": "^2.2" 60 | }, 61 | "minimum-stability": "dev", 62 | "autoload": { 63 | "psr-4": { 64 | "MatthiasWeb\\WPRJSS\\": [ 65 | "inc/", 66 | "src/inc/" 67 | ] 68 | } 69 | }, 70 | "autoload-dev": { 71 | "psr-4": { 72 | "MatthiasWeb\\WPRJSS\\Test\\": [ 73 | "test/phpunit/" 74 | ] 75 | } 76 | }, 77 | "repositories": [ 78 | { 79 | "type": "path", 80 | "url": "../../packages/*", 81 | "options": { 82 | "symlink": true 83 | } 84 | } 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "integrationFolder": "test/cypress/integration", 3 | "pluginsFile": "test/cypress/plugins/index.ts", 4 | "//": "// The cypress configuration is dynamically created through a plugin, see ./test/cypress/plugins/index" 5 | } 6 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/devops/.gitlab/.gitlab-ci.ts: -------------------------------------------------------------------------------- 1 | import { ExtendConfigFunction } from "node-gitlab-ci"; 2 | import { createPackageJobs } from "../../../../.gitlab-ci"; 3 | 4 | const extendConfig: ExtendConfigFunction = async (config) => { 5 | createPackageJobs(config, __dirname, "plugins"); 6 | 7 | await config.include(__dirname, ["./stage-*.ts"]); 8 | }; 9 | 10 | export { extendConfig }; 11 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/devops/.gitlab/stage-build-production.ts: -------------------------------------------------------------------------------- 1 | import { ExtendConfigFunction } from "node-gitlab-ci"; 2 | import { createPackageJobs } from "../../../../.gitlab-ci"; 3 | 4 | const extendConfig: ExtendConfigFunction = async (config) => { 5 | const { prefix } = createPackageJobs(config, __dirname, "plugins"); 6 | 7 | // Build production ready WP plugin 8 | config.extends([`.${prefix} jobs`, `.only production`, `.build plugin`], `${prefix} build production`, { 9 | stage: "build production", 10 | // Always rely on "release" so ".lerna changes" works correctly 11 | dependencies: [`${prefix} yarn licenses`, `${prefix} composer licenses`, "release", "semver"] 12 | }); 13 | }; 14 | 15 | export { extendConfig }; 16 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/devops/.gitlab/stage-build.ts: -------------------------------------------------------------------------------- 1 | import { ExtendConfigFunction } from "node-gitlab-ci"; 2 | import { createPackageJobs } from "../../../../.gitlab-ci"; 3 | import { EsLintMacroArgs, PhpCsMacroArgs, BuildPluginMacroArgs } from "../../../../devops/.gitlab/stage-build"; 4 | 5 | const extendConfig: ExtendConfigFunction = async (config) => { 6 | const { prefix } = createPackageJobs(config, __dirname, "plugins"); 7 | 8 | // Generate technical documents 9 | config.extends([`.${prefix} jobs`, `.${prefix} only changes`, `.docs`], `${prefix} docs`, {}); 10 | 11 | // Lint JavaScript/TypeScript coding 12 | config.from("lint eslint", { prefix }); 13 | 14 | // Lint PHP coding 15 | config.from("lint phpcs", { prefix }); 16 | 17 | // Create build files and run it through docker 18 | config.from("build plugin", { 19 | prefix 20 | }); 21 | }; 22 | 23 | export { extendConfig }; 24 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/devops/.gitlab/stage-deploy.ts: -------------------------------------------------------------------------------- 1 | import { ExtendConfigFunction } from "node-gitlab-ci"; 2 | import { createPackageJobs } from "../../../../.gitlab-ci"; 3 | 4 | const extendConfig: ExtendConfigFunction = async (config) => { 5 | const { prefix } = createPackageJobs(config, __dirname, "plugins"); 6 | 7 | // Do your deployments here, for example upload builds to your license server and publish docs 8 | config.extends([`.${prefix} jobs`, `.only production`, `.lerna changes`], `${prefix} docs deploy`, { 9 | stage: "deploy", 10 | cache: {}, 11 | // Always rely on "release" so ".lerna changes" works correctly 12 | dependencies: [`${prefix} docs`, "release"], 13 | script: ["ls -la plugins/$JOB_PACKAGE_NAME/docs/"] 14 | }); 15 | 16 | config.extends([`.${prefix} jobs`, `.only production`, `.lerna changes`], `${prefix} build deploy`, { 17 | stage: "deploy", 18 | cache: {}, 19 | // Always rely on "release" so ".lerna changes" works correctly 20 | dependencies: [`${prefix} build production`, "release"], 21 | script: ["ls -la plugins/$JOB_PACKAGE_NAME/build/"] 22 | }); 23 | 24 | // Publish the plugin changes to wordpress.org/plugins (not depending on lerna changes because it should simply reflect the complete SVN) 25 | config.extends( 26 | [`.${prefix} jobs`, `.${prefix} only changes`, `.lerna changes`, ".only production", ".wordpress.org"], 27 | `${prefix} wordpress.org`, 28 | { 29 | variables: { 30 | COPY_BUILD_FOLDER: prefix 31 | }, 32 | // Here we need to rely on "semver" so the updated CHANGELOG.md and package.json is applied 33 | dependencies: [`${prefix} build production`, "release"] 34 | } 35 | ); 36 | }; 37 | 38 | export { extendConfig }; 39 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/devops/.gitlab/stage-test.ts: -------------------------------------------------------------------------------- 1 | import { ExtendConfigFunction } from "node-gitlab-ci"; 2 | import { createPackageJobs } from "../../../../.gitlab-ci"; 3 | import { PhpUnitMacroArgs, JestMacroArgs } from "../../../../devops/.gitlab/stage-test"; 4 | 5 | const extendConfig: ExtendConfigFunction = async (config) => { 6 | const { prefix } = createPackageJobs(config, __dirname, "plugins"); 7 | 8 | // Start the cypress e2e test 9 | config.extends( 10 | [`.${prefix} jobs`, `.${prefix} only changes`, `.docker e2e cypress`], 11 | `${prefix} docker e2e cypress`, 12 | { 13 | // only: # Add dependent plugins so you run also the test if another plugin changes 14 | // changes: ["plugins/{dependent-plugin1,dependent-plugin2}/**/*"] 15 | } 16 | ); 17 | 18 | // Test PHPUnit 19 | config.from("phpunit", { prefix }); 20 | 21 | // Test Jest 22 | config.from("jest", { prefix }); 23 | }; 24 | 25 | export { extendConfig }; 26 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/devops/.gitlab/stage-validate.ts: -------------------------------------------------------------------------------- 1 | import { ExtendConfigFunction } from "node-gitlab-ci"; 2 | import { createPackageJobs } from "../../../../.gitlab-ci"; 3 | import { YarnLicensesMacroArgs, ComposerLicensesMacroArgs } from "../../../../devops/.gitlab/stage-validate"; 4 | 5 | const extendConfig: ExtendConfigFunction = async (config) => { 6 | const { prefix } = createPackageJobs(config, __dirname, "plugins"); 7 | 8 | // Validate licenses for yarn packages 9 | config.from("yarn licenses", { prefix }); 10 | 11 | // Validate licenses for composer packages 12 | config.from("composer licenses", { prefix }); 13 | }; 14 | 15 | export { extendConfig }; 16 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/devops/docker-compose/docker-compose.e2e.yml: -------------------------------------------------------------------------------- 1 | # This file overrides the file from docker-compose.yml 2 | 3 | version: "3" 4 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/devops/docker-compose/docker-compose.local.yml: -------------------------------------------------------------------------------- 1 | # This file overrides the file from docker-compose.yml 2 | 3 | version: "3" 4 | 5 | services: 6 | wordpress: 7 | volumes: 8 | # Composer PHP files 9 | - ../../plugins/wp-reactjs-starter/vendor:/var/www/html/wp-content/plugins/wp-reactjs-starter/vendor 10 | # The main plugin source 11 | - ../../plugins/wp-reactjs-starter/src:/var/www/html/wp-content/plugins/wp-reactjs-starter 12 | # Devops scripts 13 | - ../../plugins/wp-reactjs-starter/devops/scripts:/scripts/plugins/wp-reactjs-starter 14 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/devops/docker-compose/docker-compose.traefik.yml: -------------------------------------------------------------------------------- 1 | # This file overrides the file from docker-compose.yml 2 | 3 | version: "3" 4 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/devops/docker-compose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # This file overrides all available configurations (local, traefik, e2e) 2 | # and should only be used for plugin relevant configuration. 3 | 4 | version: "3" 5 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/devops/scripts/wordpress-startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Run your commands for startup of WordPress for this specific plugin in the main docker environment. 4 | 5 | PLUGIN_SLUG="wp-reactjs-starter" 6 | echo "Initiate $PLUGIN_SLUG..." -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/scripts/webpack.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-default-export */ 2 | import { createDefaultSettings } from "../../../common/webpack.factory"; 3 | 4 | export default createDefaultSettings("plugin"); 5 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/inc/Activator.php: -------------------------------------------------------------------------------- 1 | getTableName(); 43 | $sql = "CREATE TABLE $table_name ( 44 | id mediumint(9) NOT NULL AUTO_INCREMENT, 45 | UNIQUE KEY id (id) 46 | ) $charset_collate;"; 47 | dbDelta( $sql ); 48 | 49 | if ($errorlevel) { 50 | $wpdb->print_error(); 51 | }*/ 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/inc/Core.php: -------------------------------------------------------------------------------- 1 | getAssets(), 'admin_enqueue_scripts']); 40 | add_action('wp_enqueue_scripts', [$this->getAssets(), 'wp_enqueue_scripts']); 41 | add_action('admin_menu', [Page::instance(), 'admin_menu']); 42 | } 43 | 44 | /** 45 | * Register widgets. 46 | * 47 | * @codeCoverageIgnore Example implementations gets deleted the most time after plugin creation! 48 | */ 49 | public function widgets_init() { 50 | register_widget(Widget::class); 51 | } 52 | 53 | /** 54 | * Get singleton core class. 55 | * 56 | * @return Core 57 | */ 58 | public static function getInstance() { 59 | return !isset(self::$me) ? (self::$me = new Core()) : self::$me; 60 | } 61 | } 62 | 63 | // Inherited from packages/utils/src/Service 64 | /** 65 | * See API docs. 66 | * 67 | * @api {get} /wp-reactjs-starter/v1/plugin Get plugin information 68 | * @apiHeader {string} X-WP-Nonce 69 | * @apiName GetPlugin 70 | * @apiGroup Plugin 71 | * 72 | * @apiSuccessExample {json} Success-Response: 73 | * { 74 | * Name: "My plugin", 75 | * PluginURI: "https://example.com/my-plugin", 76 | * Version: "0.1.0", 77 | * Description: "This plugin is doing something.", 78 | * Author: "
John Smith", 79 | * AuthorURI: "https://example.com", 80 | * TextDomain: "my-plugin", 81 | * DomainPath: "/languages", 82 | * Network: false, 83 | * Title: "My plugin", 84 | * AuthorName: "John Smith" 85 | * } 86 | * @apiVersion 0.1.0 87 | */ 88 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/inc/Localization.php: -------------------------------------------------------------------------------- 1 | getPluginData('TextDomain')); 26 | define('WPRJSS_VERSION', $this->getPluginData('Version')); 27 | $this->construct(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/inc/base/UtilsProvider.php: -------------------------------------------------------------------------------- 1 | 13 |

' . 14 | $data['Name'] . 15 | ' could not be initialized because you need minimum PHP version ' . 16 | WPRJSS_MIN_PHP . 17 | ' ... you are running: ' . 18 | phpversion() . 19 | '. 20 | '; 21 | } 22 | } 23 | } 24 | add_action('admin_notices', 'wprjss_skip_php_admin_notice'); 25 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/inc/base/others/fallback-rest-api.php: -------------------------------------------------------------------------------- 1 | 14 |

' . 15 | $data['Name'] . 16 | ' could not be initialized because you are running WordPress < 4.7 (' . 17 | $wp_version . 18 | '). If WordPress < 4.7 the plugin needs another plugin WordPress REST API (Version 2) to provide needed functionality. 19 | Show in plugin finder or 22 | install plugin directly. 25 | '; 26 | } 27 | } 28 | } 29 | add_action('admin_notices', 'wprjss_skip_rest_admin_notice'); 30 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/inc/base/others/fallback-wp-version.php: -------------------------------------------------------------------------------- 1 | 14 |

' . 15 | $data['Name'] . 16 | ' could not be initialized because you need minimum WordPress version ' . 17 | WPRJSS_MIN_WP . 18 | ' ... you are running: ' . 19 | $wp_version . 20 | '. 21 | Update WordPress now. 24 | '; 25 | } 26 | } 27 | } 28 | add_action('admin_notices', 'wprjss_skip_wp_admin_notice'); 29 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/inc/base/others/index.php: -------------------------------------------------------------------------------- 1 | =')) { 11 | $load_core = false; 12 | 13 | // Check minimum WordPress REST API 14 | if (version_compare($wp_version, '4.7.0', '>=')) { 15 | $load_core = true; 16 | } else { 17 | // Check WP REST API plugin is active 18 | require_once ABSPATH . 'wp-admin/includes/plugin.php'; 19 | $load_core = is_plugin_active('rest-api/plugin.php'); 20 | } 21 | 22 | // Load core 23 | if ($load_core) { 24 | // Composer autoload 25 | $composer_path = path_join(WPRJSS_PATH, 'vendor/autoload.php'); 26 | if (file_exists($composer_path)) { 27 | require_once $composer_path; 28 | } 29 | 30 | // Dependents scoper autoload (PHP Scoper) 31 | $depAutoloaders = glob(path_join(WPRJSS_PATH, 'vendor/*/*/vendor/scoper-autoload.php')); 32 | foreach ($depAutoloaders as $composer_path) { 33 | require_once $composer_path; 34 | } 35 | 36 | // Dependents autoload 37 | $depAutoloaders = glob(path_join(WPRJSS_PATH, 'vendor/' . WPRJSS_ROOT_SLUG . '/*/vendor/autoload.php')); 38 | foreach ($depAutoloaders as $composer_path) { 39 | require_once $composer_path; 40 | } 41 | 42 | Core::getInstance(); 43 | } else { 44 | // WP REST API version not reached 45 | require_once WPRJSS_INC . 'base/others/fallback-rest-api.php'; 46 | } 47 | } else { 48 | // Min WP version not reached 49 | require_once WPRJSS_INC . 'base/others/fallback-wp-version.php'; 50 | } 51 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/inc/index.php: -------------------------------------------------------------------------------- 1 | 'GET', 34 | 'callback' => [$this, 'routeHello'], 35 | 'permission_callback' => '__return_true' 36 | ]); 37 | } 38 | 39 | /** 40 | * See API docs. 41 | * 42 | * @api {get} /wp-reactjs-starter/v1/hello Say hello 43 | * @apiHeader {string} X-WP-Nonce 44 | * @apiName SayHello 45 | * @apiGroup HelloWorld 46 | * 47 | * @apiSuccessExample {json} Success-Response: 48 | * { 49 | * "hello": "world" 50 | * } 51 | * @apiVersion 0.1.0 52 | */ 53 | public function routeHello() { 54 | return new WP_REST_Response(['hello' => 'world']); 55 | } 56 | 57 | /** 58 | * New instance. 59 | */ 60 | public static function instance() { 61 | return new HelloWorld(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/inc/rest/index.php: -------------------------------------------------------------------------------- 1 | getCore()->getPluginData()['Name']; 31 | add_menu_page($pluginName, $pluginName, 'manage_options', self::COMPONENT_ID, [ 32 | $this, 33 | 'render_component_library' 34 | ]); 35 | } 36 | 37 | /** 38 | * Render the content of the menu page. 39 | */ 40 | public function render_component_library() { 41 | echo '

'; 42 | } 43 | 44 | /** 45 | * New instance. 46 | */ 47 | public static function instance() { 48 | return new Page(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/inc/view/menu/index.php: -------------------------------------------------------------------------------- 1 | _n( 25 | 'A widget that demonstrates using React.', 26 | 'Widgets that demonstrates using React.', 27 | 1, 28 | WPRJSS_TD 29 | ) 30 | ]; 31 | parent::__construct(WPRJSS_TD . 'react-demo', 'React Demo Widget', $widget_ops); 32 | } 33 | 34 | /** 35 | * Output the widget content. 36 | * 37 | * @param mixed $args 38 | * @param array $instance 39 | */ 40 | public function widget($args, $instance) { 41 | echo $args['before_widget']; ?> 42 |
43 | =') ? 'start.php' : 'fallback-php-version.php'); 44 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/languages/wp-reactjs-starter-de_DE.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devowlio/wp-react-starter/96f8fd6afcb68e9b921e66c9984b4451abb24937/plugins/wp-reactjs-starter/src/languages/wp-reactjs-starter-de_DE.mo -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/languages/wp-reactjs-starter-de_DE.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020 Matthias Guenter 2 | # This file is distributed under the same license as the WP ReactJS Starter plugin. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: WP ReactJS Starter 1.0.0\n" 6 | "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/src\n" 7 | "Language-Team: \n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "POT-Creation-Date: 2020-01-15T15:52:45+01:00\n" 12 | "PO-Revision-Date: 2020-01-15 16:24+0100\n" 13 | "X-Generator: Poedit 2.2.4\n" 14 | "Last-Translator: \n" 15 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 16 | "Language: de_DE\n" 17 | 18 | #. Plugin Name of the plugin 19 | msgid "WP ReactJS Starter" 20 | msgstr "WP ReactJS Starter" 21 | 22 | #. Plugin URI of the plugin 23 | msgid "https://matthias-web.com/wordpress" 24 | msgstr "https://matthias-web.com/wordpress" 25 | 26 | #. Description of the plugin 27 | msgid "" 28 | "This WordPress plugin demonstrates how to setup a plugin that uses React and " 29 | "ES6 in a WordPress plugin." 30 | msgstr "" 31 | "Dieses WordPress-Plugin demonstriert, wie man ein Plugin aufbaut, das React " 32 | "und ES6 in einem WordPress-Plugin verwendet." 33 | 34 | #. Author of the plugin 35 | msgid "Matthias Guenter" 36 | msgstr "Matthias Günter" 37 | 38 | #. Author URI of the plugin 39 | msgid "https://matthias-web.com" 40 | msgstr "https://matthias-web.com/wordpress" 41 | 42 | #: inc/view/widget/Widget.php:23 43 | msgid "A widget that demonstrates using React." 44 | msgid_plural "Widgets that demonstrates using React." 45 | msgstr[0] "Ein Widget, das die Verwendung von React demonstriert." 46 | msgstr[1] "Widgets, die React demonstrieren." 47 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/languages/wp-reactjs-starter.pot: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020 Matthias Guenter 2 | # This file is distributed under the same license as the WP ReactJS Starter plugin. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: WP ReactJS Starter 1.1.2\n" 6 | "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/src\n" 7 | "Last-Translator: FULL NAME \n" 8 | "Language-Team: LANGUAGE \n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "POT-Creation-Date: n/a\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "X-Generator: WP-CLI 2.4.0\n" 15 | 16 | #. Plugin Name of the plugin 17 | msgid "WP ReactJS Starter" 18 | msgstr "" 19 | 20 | #. Plugin URI of the plugin 21 | msgid "https://matthias-web.com/wordpress" 22 | msgstr "" 23 | 24 | #. Description of the plugin 25 | msgid "This WordPress plugin demonstrates how to setup a plugin that uses React and ES6 in a WordPress plugin." 26 | msgstr "" 27 | 28 | #. Author of the plugin 29 | msgid "Matthias Guenter" 30 | msgstr "" 31 | 32 | #. Author URI of the plugin 33 | msgid "https://matthias-web.com" 34 | msgstr "" 35 | 36 | #: inc/view/widget/Widget.php:24 37 | msgid "A widget that demonstrates using React." 38 | msgid_plural "Widgets that demonstrates using React." 39 | msgstr[0] "" 40 | msgstr[1] "" 41 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/languages/json/wp-reactjs-starter-de_DE-1458bc3eb855dd3b40a27bc171a5aed9.json: -------------------------------------------------------------------------------- 1 | {"translation-revision-date":"2020-01-15 16:30+0100","generator":"WP-CLI\/2.4.0","source":"widget.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"de_DE","plural-forms":"nplurals=2; plural=(n != 1);"},"Hello, World!":["Hallo Welt!"],"I got generated from your new plugin!":["Ich wurde von Ihrem neuen Plugin generiert!"]}}} -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/languages/json/wp-reactjs-starter-de_DE-d6c7c71371fa4fbe7cc75f0a20f23d0e.json: -------------------------------------------------------------------------------- 1 | {"translation-revision-date":"2020-01-15 16:30+0100","generator":"WP-CLI\/2.4.0","source":"admin.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"de_DE","plural-forms":"nplurals=2; plural=(n != 1);"},"The text domain of the plugin is: %(textDomain)s (localized variable)":["Die Textdom\u00e4ne des Plugins ist: %(textDomain)s (lokalisierte Variable)"],"The WP REST API URL of the plugin is: {{a}}%(restUrl)s{{\/a}} (localized variable, click for hello world example)":["Die WP REST API URL des Plugins lautet: {{a}}%(restUrl)s{{\/a}} (lokalisierte Variable, klicken Sie f\u00fcr Hallo Welt Beispiel)"],"The is an informative notice":["Das ist eine informative Mitteilung"],"Your action was successful":["Ihre Aktion war erfolgreich"],"An unexpected error has occurred":["Ein unerwarteter Fehler ist aufgetreten"],"Todo list":["Todo-Liste"],"This section demonstrates a MobX Todo list (no peristence to server).":["Dieser Abschnitt demonstriert eine MobX Todo-Liste (keine Persistenz zum Server)."],"What needs to be done?":["Was muss getan werden?"],"Add":["Hinzuf\u00fcgen"],"Remove":["Entfernen"]}}} -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/languages/wp-reactjs-starter-de_DE.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devowlio/wp-react-starter/96f8fd6afcb68e9b921e66c9984b4451abb24937/plugins/wp-reactjs-starter/src/public/languages/wp-reactjs-starter-de_DE.mo -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/languages/wp-reactjs-starter-de_DE.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "Language-Team: \n" 6 | "MIME-Version: 1.0\n" 7 | "Content-Type: text/plain; charset=UTF-8\n" 8 | "Content-Transfer-Encoding: 8bit\n" 9 | "POT-Creation-Date: 2020-01-15T15:53:15+01:00\n" 10 | "PO-Revision-Date: 2020-01-15 16:30+0100\n" 11 | "X-Generator: Poedit 2.2.4\n" 12 | "Last-Translator: \n" 13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 14 | "Language: de_DE\n" 15 | 16 | #: admin.js:1800 17 | msgid "The text domain of the plugin is: %(textDomain)s (localized variable)" 18 | msgstr "" 19 | "Die Textdomäne des Plugins ist: %(textDomain)s (lokalisierte Variable)" 20 | 21 | #: admin.js:1804 22 | msgid "" 23 | "The WP REST API URL of the plugin is: {{a}}%(restUrl)s{{/a}} (localized " 24 | "variable, click for hello world example)" 25 | msgstr "" 26 | "Die WP REST API URL des Plugins lautet: {{a}}%(restUrl)s{{/a}} " 27 | "(lokalisierte Variable, klicken Sie für Hallo Welt Beispiel)" 28 | 29 | #: admin.js:1813 30 | msgid "The is an informative notice" 31 | msgstr "Das ist eine informative Mitteilung" 32 | 33 | #: admin.js:1815 34 | msgid "Your action was successful" 35 | msgstr "Ihre Aktion war erfolgreich" 36 | 37 | #: admin.js:1817 38 | msgid "An unexpected error has occurred" 39 | msgstr "Ein unerwarteter Fehler ist aufgetreten" 40 | 41 | #: admin.js:1872 42 | msgid "Todo list" 43 | msgstr "Todo-Liste" 44 | 45 | #: admin.js:1872 46 | msgid "This section demonstrates a MobX Todo list (no peristence to server)." 47 | msgstr "" 48 | "Dieser Abschnitt demonstriert eine MobX Todo-Liste (keine Persistenz zum " 49 | "Server)." 50 | 51 | #: admin.js:1879 52 | msgid "What needs to be done?" 53 | msgstr "Was muss getan werden?" 54 | 55 | #: admin.js:1886 56 | msgid "Add" 57 | msgstr "Hinzufügen" 58 | 59 | #: admin.js:1950 60 | msgid "Remove" 61 | msgstr "Entfernen" 62 | 63 | #: widget.js:793 64 | msgid "Hello, World!" 65 | msgstr "Hallo Welt!" 66 | 67 | #: widget.js:793 68 | msgid "I got generated from your new plugin!" 69 | msgstr "Ich wurde von Ihrem neuen Plugin generiert!" 70 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/languages/wp-reactjs-starter.pot: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "Last-Translator: FULL NAME \n" 6 | "Language-Team: LANGUAGE \n" 7 | "MIME-Version: 1.0\n" 8 | "Content-Type: text/plain; charset=UTF-8\n" 9 | "Content-Transfer-Encoding: 8bit\n" 10 | "POT-Creation-Date: n/a\n" 11 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 12 | "X-Generator: WP-CLI 2.4.0\n" 13 | 14 | #: admin.js:300 15 | msgid "The text domain of the plugin is: %(textDomain)s (localized variable)" 16 | msgstr "" 17 | 18 | #: admin.js:304 19 | msgid "The WP REST API URL of the plugin is: {{a}}%(restUrl)s{{/a}} (localized variable, click for hello world example)" 20 | msgstr "" 21 | 22 | #: admin.js:313 23 | msgid "The is an informative notice" 24 | msgstr "" 25 | 26 | #: admin.js:315 27 | msgid "Your action was successful" 28 | msgstr "" 29 | 30 | #: admin.js:317 31 | msgid "An unexpected error has occurred" 32 | msgstr "" 33 | 34 | #: admin.js:372 35 | msgid "Todo list" 36 | msgstr "" 37 | 38 | #: admin.js:372 39 | msgid "This section demonstrates a MobX Todo list (no peristence to server)." 40 | msgstr "" 41 | 42 | #: admin.js:379 43 | msgid "What needs to be done?" 44 | msgstr "" 45 | 46 | #: admin.js:386 47 | msgid "Add" 48 | msgstr "" 49 | 50 | #: admin.js:450 51 | msgid "Remove" 52 | msgstr "" 53 | 54 | #: widget.js:654 55 | msgid "Hello, World!" 56 | msgstr "" 57 | 58 | #: widget.js:654 59 | msgid "I got generated from your new plugin!" 60 | msgstr "" 61 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/admin.tsx: -------------------------------------------------------------------------------- 1 | /* istanbul ignore file: we do not need to care about the entry point file as errors are detected through integration tests (E2E) */ 2 | 3 | /** 4 | * The entry point for the admin side wp-admin resource. 5 | */ 6 | import "@wp-reactjs-multi-starter/utils"; // Import once for startup polyfilling (e. g. setimmediate) 7 | import { render } from "react-dom"; 8 | import { ComponentLibrary } from "./components"; 9 | import { RootStore } from "./store"; 10 | import "./style/admin.scss"; 11 | 12 | const node = document.getElementById(`${RootStore.get.optionStore.slug}-component`); 13 | 14 | if (node) { 15 | render( 16 | 17 | 18 | , 19 | node 20 | ); 21 | } 22 | 23 | // Expose this functionalities to add-ons, but you need to activate the library functionality 24 | // in your webpack configuration, see also https://webpack.js.org/guides/author-libraries/ 25 | export * from "@wp-reactjs-multi-starter/utils"; 26 | export * from "./wp-api"; 27 | export * from "./store"; 28 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/components/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./todo"; 2 | export * from "./todoItem"; 3 | export * from "./page"; 4 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/components/page.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from "react"; 2 | import { observer } from "mobx-react"; 3 | import { Notice, NoticeType } from "@wp-reactjs-multi-starter/utils"; 4 | import { TodoOverview } from "./todo"; 5 | import { RequestRouteHelloGet, ParamsRouteHelloGet, ResponseRouteHelloGet, locationRestHelloGet } from "../wp-api"; 6 | import { useStores } from "../store"; 7 | import { request, urlBuilder, __, _i } from "../utils"; 8 | 9 | /* istanbul ignore next: Example implementations gets deleted the most time after plugin creation! */ 10 | /** 11 | * Do a test ajax call when clicking the REST API url. 12 | * 13 | * @param e 14 | */ 15 | async function doHelloWorldRestCall(event: React.MouseEvent) { 16 | event.persist(); 17 | const result = await request({ 18 | location: locationRestHelloGet 19 | }); 20 | const usedUrl = urlBuilder({ location: locationRestHelloGet }); 21 | alert(`${usedUrl}\n\n${JSON.stringify(result, undefined, 4)}`); 22 | event.preventDefault(); 23 | } 24 | 25 | /* istanbul ignore next: Example implementations gets deleted the most time after plugin creation! */ 26 | const ComponentLibrary: FC<{}> = observer(() => { 27 | const { optionStore } = useStores(); 28 | return ( 29 |
30 |

WP React Component Library

31 | 32 | 33 | {__("The text domain of the plugin is: %(textDomain)s (localized variable)", { 34 | textDomain: optionStore.textDomain 35 | })} 36 | 37 | 38 | {_i( 39 | __( 40 | "The WP REST API URL of the plugin is: {{a}}%(restUrl)s{{/a}} (localized variable, click for hello world example)", 41 | { 42 | restUrl: optionStore.restUrl 43 | } 44 | ), 45 | { 46 | a: ( 47 | 48 | {optionStore.restUrl} 49 | 50 | ) 51 | } 52 | )} 53 | 54 | {__("The is an informative notice")} 55 | {__("Your action was successful")} 56 | {__("An unexpected error has occurred")} 57 | 58 |
59 | ); 60 | }); 61 | 62 | export { ComponentLibrary }; 63 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/components/todo.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useState } from "react"; 2 | import { observer } from "mobx-react"; 3 | import { Button, ButtonType } from "@wp-reactjs-multi-starter/utils"; 4 | import { TodoItem } from "./todoItem"; 5 | import { useStores } from "../store"; 6 | import { __ } from "../utils"; 7 | 8 | /* istanbul ignore next: Example implementations gets deleted the most time after plugin creation! */ 9 | /** 10 | * The mobx-react package also provides the Provider component that can be used to pass down stores using 11 | * React's context mechanism. For this you have to use the custom useStores hook implemented in ../stores 12 | * in your function components. 13 | * 14 | * @see https://mobx-react.js.org/recipes-context 15 | * @see https://mobx.js.org/refguide/observer-component.html#connect-components-to-provided-stores-using-inject 16 | */ 17 | const TodoOverview: FC<{}> = observer(() => { 18 | const [inputText, setInputText] = useState(""); 19 | const { todoStore } = useStores(); 20 | 21 | return ( 22 |
23 |

24 | {__("Todo list")} ({todoStore.todos.length}) 25 |

26 |

{__("This section demonstrates a MobX Todo list (no peristence to server).")}

27 | setInputText(event.target.value)} 30 | type="text" 31 | className="regular-text" 32 | placeholder={__("What needs to be done?")} 33 | /> 34 | 37 |
    38 | {todoStore.todos.map((t) => ( 39 | 40 | ))} 41 | {!todoStore.todos.length &&
  • No entries
  • } 42 |
43 |
44 | ); 45 | }); 46 | 47 | export { TodoOverview }; 48 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/components/todoItem.tsx: -------------------------------------------------------------------------------- 1 | import classNames from "classnames"; 2 | import { useState } from "react"; 3 | import { observer } from "mobx-react"; 4 | import { TodoModel } from "../models"; 5 | import { __ } from "../utils"; 6 | 7 | /* istanbul ignore next: Example implementations gets deleted the most time after plugin creation! */ 8 | /** 9 | * Removable todo item. 10 | */ 11 | const TodoItem = observer(({ todo }: { todo: TodoModel }) => { 12 | const [isBold, setBold] = useState(false); 13 | return ( 14 |
  • setBold(true)} onMouseLeave={() => setBold(false)}> 15 | 19 |   20 | todo.destroy()}> 21 | {__("Remove")} 22 | 23 |
  • 24 | ); 25 | }); 26 | 27 | export { TodoItem }; 28 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/models/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./todoModel"; 2 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/models/todoModel.tsx: -------------------------------------------------------------------------------- 1 | import { observable, action } from "mobx"; 2 | import { TodoStore } from "../store"; 3 | 4 | /* istanbul ignore next: Example implementations gets deleted the most time after plugin creation! */ 5 | class TodoModel { 6 | @observable 7 | public title: string; 8 | 9 | @observable 10 | public completed = false; 11 | 12 | @observable 13 | public id: number; 14 | 15 | private store: TodoStore; 16 | 17 | constructor(store: TodoModel["store"], title: TodoModel["title"]) { 18 | this.id = store.todos.length + 1; 19 | this.store = store; 20 | this.title = title; 21 | } 22 | 23 | @action 24 | public toggle() { 25 | this.completed = !this.completed; 26 | } 27 | 28 | @action 29 | public destroy() { 30 | this.store.todos.splice(this.store.todos.indexOf(this), 1); 31 | } 32 | 33 | @action 34 | public setTitle(title: TodoModel["title"]) { 35 | this.title = title; 36 | } 37 | } 38 | 39 | export { TodoModel }; 40 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/store/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./todo"; 2 | export * from "./stores"; 3 | export * from "./option"; 4 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/store/option.tsx: -------------------------------------------------------------------------------- 1 | import { observable, runInAction } from "mobx"; 2 | import { BaseOptions } from "@wp-reactjs-multi-starter/utils"; 3 | import { RootStore } from "./stores"; 4 | 5 | class OptionStore extends BaseOptions { 6 | // Implement "others" property in your Assets.php; 7 | @observable 8 | public others: {} = {}; 9 | 10 | public readonly pureSlug: ReturnType; 11 | 12 | public readonly pureSlugCamelCased: ReturnType; 13 | 14 | public readonly rootStore: RootStore; 15 | 16 | constructor(rootStore: RootStore) { 17 | super(); 18 | this.rootStore = rootStore; 19 | this.pureSlug = BaseOptions.getPureSlug(process.env); 20 | this.pureSlugCamelCased = BaseOptions.getPureSlug(process.env, true); 21 | 22 | // Use the localized WP object to fill this object values. 23 | runInAction(() => Object.assign(this, (window as any)[this.pureSlugCamelCased])); 24 | } 25 | } 26 | 27 | export { OptionStore }; 28 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/store/stores.tsx: -------------------------------------------------------------------------------- 1 | import { configure } from "mobx"; 2 | import { createContextFactory } from "@wp-reactjs-multi-starter/utils"; 3 | import { TodoStore } from "./todo"; 4 | import { OptionStore } from "./option"; 5 | 6 | configure({ 7 | enforceActions: "always" 8 | }); 9 | 10 | /** 11 | * A collection of all available stores which gets available 12 | * through the custom hook useStores in your function components. 13 | * 14 | * @see https://mobx.js.org/best/store.html#combining-multiple-stores 15 | */ 16 | class RootStore { 17 | private static me: RootStore; 18 | 19 | public todoStore: TodoStore; 20 | 21 | public optionStore: OptionStore; 22 | 23 | private contextMemo: { 24 | StoreContext: React.Context; 25 | StoreProvider: React.FC<{}>; 26 | useStores: () => RootStore; 27 | }; 28 | 29 | public get context() { 30 | return this.contextMemo 31 | ? this.contextMemo 32 | : (this.contextMemo = createContextFactory((this as unknown) as RootStore)); 33 | } 34 | 35 | private constructor() { 36 | this.todoStore = new TodoStore(this); 37 | this.optionStore = new OptionStore(this); 38 | } 39 | 40 | public static get StoreProvider() { 41 | return RootStore.get.context.StoreProvider; 42 | } 43 | 44 | public static get get() { 45 | return RootStore.me ? RootStore.me : (RootStore.me = new RootStore()); 46 | } 47 | } 48 | 49 | const useStores = () => RootStore.get.context.useStores(); 50 | 51 | export { RootStore, useStores }; 52 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/store/todo.tsx: -------------------------------------------------------------------------------- 1 | import { observable, action } from "mobx"; 2 | import { TodoModel } from "../models"; 3 | import { RootStore } from "./stores"; 4 | 5 | /* istanbul ignore next: Example implementations gets deleted the most time after plugin creation! */ 6 | class TodoStore { 7 | @observable 8 | public todos: TodoModel[] = []; 9 | 10 | public readonly rootStore: RootStore; 11 | 12 | constructor(rootStore: RootStore) { 13 | this.rootStore = rootStore; 14 | } 15 | 16 | @action 17 | public add(title: TodoModel["title"]) { 18 | this.todos.push(new TodoModel(this, title)); 19 | } 20 | } 21 | 22 | export { TodoStore }; 23 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/style/admin.scss: -------------------------------------------------------------------------------- 1 | .wp-styleguide--buttons { 2 | background-color: #fff; 3 | padding: 10px; 4 | } 5 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/style/widget.scss: -------------------------------------------------------------------------------- 1 | .react-boilerplate-widget { 2 | background-color: red; 3 | } 4 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/types/global.d.ts: -------------------------------------------------------------------------------- 1 | // @see https://github.com/microsoft/TypeScript/issues/15031#issuecomment-407131785 2 | declare module "*"; 3 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/utils/index.tsx: -------------------------------------------------------------------------------- 1 | /* istanbul ignore file: this file does not contain any logic, only factory calls */ 2 | 3 | import { createRequestFactory, createLocalizationFactory } from "@wp-reactjs-multi-starter/utils"; 4 | import { RootStore } from "../store"; 5 | 6 | class UtilsFactory { 7 | private static me: UtilsFactory; 8 | 9 | private requestMemo: ReturnType; 10 | 11 | private localizationMemo: ReturnType; 12 | 13 | // Create REST API relevant stuff from factory 14 | public get request() { 15 | return this.requestMemo 16 | ? this.requestMemo 17 | : (this.requestMemo = createRequestFactory(RootStore.get.optionStore)); 18 | } 19 | 20 | // Create i18n relevant stuff from factory 21 | public get localization() { 22 | return this.localizationMemo 23 | ? this.localizationMemo 24 | : (this.localizationMemo = createLocalizationFactory(RootStore.get.optionStore.pureSlug)); 25 | } 26 | 27 | public static get get() { 28 | return UtilsFactory.me ? UtilsFactory.me : (UtilsFactory.me = new UtilsFactory()); 29 | } 30 | } 31 | 32 | const urlBuilder: UtilsFactory["requestMemo"]["urlBuilder"] = (...args) => UtilsFactory.get.request.urlBuilder(...args); 33 | const request: UtilsFactory["requestMemo"]["request"] = (...args) => UtilsFactory.get.request.request(...args); 34 | const _n: UtilsFactory["localizationMemo"]["_n"] = (...args) => UtilsFactory.get.localization._n(...args); 35 | const _nx: UtilsFactory["localizationMemo"]["_nx"] = (...args) => UtilsFactory.get.localization._nx(...args); 36 | const _x: UtilsFactory["localizationMemo"]["_x"] = (...args) => UtilsFactory.get.localization._x(...args); 37 | const __: UtilsFactory["localizationMemo"]["__"] = (...args) => UtilsFactory.get.localization.__(...args); 38 | const _i: UtilsFactory["localizationMemo"]["_i"] = (...args) => UtilsFactory.get.localization._i(...args); 39 | 40 | export { urlBuilder, request, _n, _nx, _x, __, _i }; 41 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/widget.tsx: -------------------------------------------------------------------------------- 1 | /* istanbul ignore file: we do not need to care about the entry point file as errors are detected through E2E */ 2 | 3 | /** 4 | * The entrypoint for the WordPress frontend widget. 5 | */ 6 | 7 | import "@wp-reactjs-multi-starter/utils"; // Import once for startup polyfilling (e. g. setimmediate) 8 | import { render } from "react-dom"; 9 | import { Widget } from "./widget/"; 10 | import "./style/widget.scss"; 11 | 12 | // Query DOM for all widget wrapper divs 13 | const widgets = document.querySelectorAll("div.react-demo-wrapper"); 14 | 15 | // Iterate over the DOM nodes and render a React component into each node 16 | widgets.forEach((item) => render(, item)); 17 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/widget/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { __ } from "../utils"; 3 | 4 | /* istanbul ignore next: Example implementations gets deleted the most time after plugin creation! */ 5 | const Widget: FC<{}> = () => ( 6 |
    7 |

    {__("Hello, World!")}

    8 |

    {__("I got generated from your new plugin!")}

    9 |
    10 | ); 11 | 12 | export { Widget }; 13 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/wp-api/hello.get.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | RouteLocationInterface, 3 | RouteHttpVerb, 4 | RouteResponseInterface, 5 | RouteRequestInterface, 6 | RouteParamsInterface 7 | } from "@wp-reactjs-multi-starter/utils"; 8 | 9 | export const locationRestHelloGet: RouteLocationInterface = { 10 | path: "/hello", 11 | method: RouteHttpVerb.GET 12 | }; 13 | 14 | export type RequestRouteHelloGet = RouteRequestInterface; 15 | 16 | export type ParamsRouteHelloGet = RouteParamsInterface; 17 | 18 | export interface ResponseRouteHelloGet extends RouteResponseInterface { 19 | hello: string; 20 | } 21 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/public/ts/wp-api/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./hello.get"; 2 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/src/uninstall.php: -------------------------------------------------------------------------------- 1 | div > div.wp-styleguide--buttons`); 8 | } 9 | 10 | static todoItem(eq: number) { 11 | return this.todoContainer.find(`> ul > li:eq(${eq})`); 12 | } 13 | 14 | static todoItemRemoveLink(eq: number) { 15 | return this.todoItem(eq).find("a"); 16 | } 17 | 18 | static get todoInput() { 19 | return this.todoContainer.children("input"); 20 | } 21 | 22 | static get todoAddButton() { 23 | return this.todoInput.next(); 24 | } 25 | 26 | static get menuPageLink() { 27 | return cy.get(`#toplevel_page_${PAGE_ID} > a`); 28 | } 29 | 30 | static get restApiLink() { 31 | return cy.get(`#${PAGE_ID} > div > div:nth-child(3) > p > a`); 32 | } 33 | } 34 | 35 | export { PAGE_ID, AdminPageObject }; 36 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/test/cypress/step-definitions/adminPage/adminPage.ts: -------------------------------------------------------------------------------- 1 | import { Then } from "cypress-cucumber-preprocessor/steps"; 2 | import { AdminPageObject } from "./AdminPageObject"; 3 | 4 | Then("I open admin page", () => { 5 | AdminPageObject.menuPageLink.click(); 6 | }); 7 | 8 | Then("I click on REST API link in the admin notice and alert contains {string}", (alertText: string) => { 9 | const stub = cy.stub(); 10 | cy.on("window:alert", stub); 11 | 12 | AdminPageObject.restApiLink 13 | .click() 14 | .wait(1000) 15 | .then(() => { 16 | expect(stub.getCall(0)).to.be.calledWith(Cypress.sinon.match(new RegExp(alertText))); 17 | }); 18 | }); 19 | 20 | Then("I type {string} and add todo, check it, afterwards delete it", (todo: string) => { 21 | AdminPageObject.todoInput.type(todo); 22 | AdminPageObject.todoAddButton.click(); 23 | AdminPageObject.todoItem(0).should("contain.text", "Test Todo Item").find(":checkbox").check().should("be.checked"); 24 | AdminPageObject.todoItemRemoveLink(0).click(); 25 | AdminPageObject.todoItem(0).should("have.text", "No entries"); 26 | }); 27 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/test/cypress/step-definitions/common/common.ts: -------------------------------------------------------------------------------- 1 | import { Given } from "cypress-cucumber-preprocessor/steps"; 2 | 3 | Given("I am logged in WP admin dashboard", () => { 4 | cy.visit("/wp-login.php?autologin=wordpress"); 5 | cy.url().should("contain", "wp-admin"); 6 | }); 7 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/test/cypress/step-definitions/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./common"; 2 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/test/cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/test/cypress/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import "./commands"; 18 | import "cypress-plugin-retries"; 19 | 20 | // Alternatively you can use CommonJS syntax: 21 | // require('./commands') 22 | 23 | // See https://docs.cypress.io/api/cypress-api/cookies.html#Whitelist-accepts 24 | Cypress.Cookies.defaults({ 25 | whitelist: /wordpress_/ 26 | }); 27 | 28 | // `window.fetch` does not work yet with cypress (https://git.io/JfFdL) 29 | Cypress.on("window:before:load", (win: Window) => { 30 | win.fetch = null; 31 | }); 32 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/test/cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "target": "ES5", 5 | "esModuleInterop": true, 6 | "baseUrl": "../node_modules", 7 | "types": ["cypress", "node"], 8 | "resolveJsonModule": true 9 | }, 10 | "include": ["**/*.*"] 11 | } 12 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/test/jest.config.js: -------------------------------------------------------------------------------- 1 | // Unfortunately the jest config can not be placed directly to package.json 2 | // because then it does not support inheritance. 3 | 4 | const base = require("../../../common/jest.base"); 5 | 6 | module.exports = base; 7 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/test/jest/store/__mocks__/wp.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-default-export */ 2 | export default {}; 3 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/test/jest/store/option.test.tsx: -------------------------------------------------------------------------------- 1 | import { OptionStore } from "../../../src/public/ts/store/option"; 2 | import { RootStore } from "../../../src/public/ts/store"; 3 | 4 | jest.mock(`${process.env.PACKAGE_SCOPE}/utils`); 5 | jest.mock("../../../src/public/ts/store/stores"); 6 | jest.mock("mobx", () => ({ 7 | observable: jest.fn(), 8 | runInAction: jest.fn().mockImplementation((callback) => callback()) 9 | })); 10 | 11 | const mobx = require("mobx"); 12 | const { BaseOptions } = require(`${process.env.PACKAGE_SCOPE}/utils`); 13 | 14 | describe("OptionStore", () => { 15 | it("should call the constructor correctly", () => { 16 | const slug = "jest"; 17 | 18 | BaseOptions.getPureSlug.mockImplementation(() => slug); 19 | 20 | const actual = new OptionStore({} as RootStore); 21 | 22 | expect(actual.pureSlug).toEqual(slug); 23 | expect(actual.pureSlugCamelCased).toEqual(slug); 24 | expect(actual.rootStore).toEqual({}); 25 | expect(mobx.runInAction).toHaveBeenCalledWith(expect.any(Function)); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/test/jest/store/stores.test.tsx: -------------------------------------------------------------------------------- 1 | import { RootStore, useStores } from "../../../src/public/ts/store/stores"; 2 | 3 | jest.mock(`${process.env.PACKAGE_SCOPE}/utils`, () => ({ 4 | createContextFactory: jest.fn().mockImplementation(() => ({})) 5 | })); 6 | jest.mock("../../../src/public/ts/store/option"); 7 | jest.mock("../../../src/public/ts/store/todo", () => ({ 8 | TodoStore: jest.fn() 9 | })); 10 | jest.mock("../../../src/public/ts/store/option", () => ({ 11 | OptionStore: jest.fn() 12 | })); 13 | jest.mock("mobx", () => ({ 14 | configure: jest.fn() 15 | })); 16 | 17 | const mobx = require("mobx"); 18 | const { TodoStore } = require("../../../src/public/ts/store/todo"); 19 | const { OptionStore } = require("../../../src/public/ts/store/option"); 20 | const { createContextFactory } = require("@wp-reactjs-multi-starter/utils"); 21 | 22 | describe("stores", () => { 23 | beforeEach(() => { 24 | // @ts-ignore Currently the easiest way to reset private singleton variable 25 | RootStore.me = undefined; 26 | }); 27 | 28 | describe("configure", () => { 29 | it("should call configure to always force actions", () => { 30 | expect(mobx.configure).toHaveBeenCalledWith(expect.objectContaining({ enforceActions: "always" })); 31 | }); 32 | }); 33 | 34 | describe("RootStore", () => { 35 | describe("context", () => { 36 | it("should cache context factory result", () => { 37 | RootStore.get.context; 38 | RootStore.get.context; 39 | 40 | expect(createContextFactory).toHaveBeenCalledTimes(1); 41 | }); 42 | }); 43 | 44 | describe("constructor", () => { 45 | it("should act as singleton", () => { 46 | RootStore.get; 47 | RootStore.get; 48 | 49 | expect(TodoStore).toHaveBeenCalledTimes(1); 50 | }); 51 | 52 | it("should initiate multiple stores", () => { 53 | RootStore.get; 54 | 55 | expect(TodoStore).toHaveBeenCalledWith(expect.any(Object)); 56 | expect(OptionStore).toHaveBeenCalledWith(expect.any(Object)); 57 | }); 58 | }); 59 | 60 | describe("StoreProvider", () => { 61 | it("should obtain StoreProvider from factory", () => { 62 | jest.spyOn(RootStore.prototype, "context", "get").mockReturnValue({ 63 | StoreContext: null, 64 | StoreProvider: jest.fn(), 65 | useStores: null 66 | }); 67 | 68 | const actual = RootStore.StoreProvider; 69 | 70 | expect(actual).toBeDefined(); 71 | }); 72 | }); 73 | }); 74 | 75 | describe("useStores", () => { 76 | it("should get StoreContext from factory result", () => { 77 | const mockUseStores = jest.fn(); 78 | jest.spyOn(RootStore.prototype, "context", "get").mockReturnValue({ 79 | StoreContext: null, 80 | StoreProvider: null, 81 | useStores: mockUseStores 82 | }); 83 | 84 | useStores(); 85 | 86 | expect(mockUseStores).toHaveBeenCalled(); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/test/patchwork.json: -------------------------------------------------------------------------------- 1 | { 2 | "redefinable-internals": ["define"] 3 | } 4 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/test/phpunit.bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | phpunit 15 | 16 | 17 | 18 | 19 | ../src/inc/ 20 | 21 | ../src/inc/base/others 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/test/phpunit/ActivatorTest.php: -------------------------------------------------------------------------------- 1 | activator = Mockery::mock(Activator::class); 17 | } 18 | 19 | public function testActivate() { 20 | $this->activator->shouldReceive('activate')->passthru(); 21 | 22 | $this->activator->activate(); 23 | 24 | $this->addToAssertionCount(1); 25 | } 26 | 27 | public function testDeactivate() { 28 | $this->activator->shouldReceive('deactivate')->passthru(); 29 | 30 | $this->activator->deactivate(); 31 | 32 | $this->addToAssertionCount(1); 33 | } 34 | 35 | public function testDbDelta() { 36 | $this->activator->shouldReceive('dbDelta')->passthru(); 37 | 38 | $this->activator->dbDelta(false); 39 | 40 | $this->addToAssertionCount(1); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/test/phpunit/CoreTest.php: -------------------------------------------------------------------------------- 1 | core = Mockery::mock(Core::class); 28 | } 29 | 30 | public function testConstruct() { 31 | $self = $this->expectCallbacksReached(['parentConstructor']); 32 | 33 | redefine(BaseCore::class . '::__construct', function () use ($self) { 34 | $self->addCallbackReached('parentConstructor'); 35 | }); 36 | 37 | WP_Mock::expectActionAdded('widgets_init', [Mockery::self(), 'widgets_init']); 38 | 39 | $ctor = (new ReflectionClass(Core::class))->getConstructor(); 40 | $ctor->setAccessible(true); 41 | $ctor->invoke($this->core); 42 | 43 | $this->assertHooksAdded(); 44 | $this->assertCallbacksReached(); 45 | } 46 | 47 | public function testInit() { 48 | $this->core->shouldReceive('init')->passthru(); 49 | $this->core->shouldReceive('getAssets')->andReturnNull(); 50 | 51 | redefine(Page::class . '::instance', always(null)); 52 | redefine(HelloWorld::class . '::instance', always(null)); 53 | 54 | WP_Mock::expectActionAdded('rest_api_init', [null, 'rest_api_init']); 55 | WP_Mock::expectActionAdded('admin_enqueue_scripts', [null, 'admin_enqueue_scripts']); 56 | WP_Mock::expectActionAdded('wp_enqueue_scripts', [null, 'wp_enqueue_scripts']); 57 | WP_Mock::expectActionAdded('admin_menu', [null, 'admin_menu']); 58 | 59 | $this->core->init(); 60 | 61 | $this->assertHooksAdded(); 62 | } 63 | 64 | public function testGetInstance() { 65 | $self = $this->expectCallbacksReached(['constructor']); 66 | 67 | redefine(Core::class . '::__construct', function () use ($self) { 68 | $self->addCallbackReached('constructor'); 69 | }); 70 | 71 | $actual = Core::getInstance(); 72 | 73 | $this->assertEquals(Core::class, get_class($actual)); 74 | $this->assertCallbacksReached(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/test/phpunit/LocalizationTest.php: -------------------------------------------------------------------------------- 1 | localization = Mockery::mock(Localization::class); 21 | } 22 | 23 | public function testOverride() { 24 | $locale = 'de_DE'; 25 | $should = 'de_DE'; 26 | 27 | $this->localization 28 | ->shouldAllowMockingProtectedMethods() 29 | ->shouldReceive('override') 30 | ->passthru(); 31 | 32 | $method = new ReflectionMethod(Localization::class, 'override'); 33 | $method->setAccessible(true); 34 | $actual = $method->invoke($this->localization, $locale); 35 | 36 | $this->assertEquals($should, $actual); 37 | } 38 | 39 | public function testGetPackageInfoBackend() { 40 | $should = [null, PHPUNIT_TD]; 41 | 42 | WP_Mock::userFunction('path_join', [ 43 | 'args' => [PHPUNIT_PATH, 'languages'], 44 | 'return' => null 45 | ]); 46 | 47 | $method = new ReflectionMethod(Localization::class, 'getPackageInfo'); 48 | $method->setAccessible(true); 49 | $actual = $method->invoke($this->localization, UtilsLocalization::$PACKAGE_INFO_BACKEND); 50 | 51 | $this->assertEquals($should, $actual); 52 | } 53 | 54 | public function testGetPackageInfoFrontend() { 55 | $should = [null, PHPUNIT_TD]; 56 | 57 | WP_Mock::userFunction('path_join', [ 58 | 'args' => [PHPUNIT_PATH, Assets::$PUBLIC_JSON_I18N], 59 | 'return' => null 60 | ]); 61 | 62 | $method = new ReflectionMethod(Localization::class, 'getPackageInfo'); 63 | $method->setAccessible(true); 64 | $actual = $method->invoke($this->localization, UtilsLocalization::$PACKAGE_INFO_FRONTEND); 65 | 66 | $this->assertEquals($should, $actual); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/test/phpunit/base/CoreTest.php: -------------------------------------------------------------------------------- 1 | core = Mockery::mock(Core::class); 29 | } 30 | 31 | public function testConstruct() { 32 | $this->expectCallbacksReached(['td', 'version']); 33 | 34 | $this->core 35 | ->shouldReceive('getPluginData') 36 | ->with('TextDomain') 37 | ->andReturn(PHPUNIT_TD); 38 | $this->core 39 | ->shouldReceive('getPluginData') 40 | ->with('Version') 41 | ->andReturn(PHPUNIT_VERSION); 42 | $this->core->shouldAllowMockingProtectedMethods()->shouldReceive('construct'); 43 | 44 | redefine('define', function ($name) { 45 | $this->addCallbackReached('td', strpos($name, '_TD') !== false); 46 | $this->addCallbackReached('version', strpos($name, '_VERSION') !== false); 47 | }); 48 | 49 | $ctor = (new ReflectionClass(Core::class))->getConstructor(); 50 | $ctor->setAccessible(true); 51 | $ctor->invoke($this->core); 52 | 53 | $this->assertCallbacksReached(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../common/tsconfig.json", 3 | "include": ["src/public/ts/**/*", "scripts/**/*.ts", "test/jest/**/*"], 4 | "compilerOptions": { 5 | "outDir": "types/" 6 | }, 7 | "typedocOptions": { 8 | "mode": "modules", 9 | "out": "docs/js" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/wordpress.org/README.wporg.txt: -------------------------------------------------------------------------------- 1 | === WP ReactJS Starter === 2 | Contributors: mguenter 3 | Tags: 4 | Donate link: 5 | Stable tag: trunk 6 | Requires at least: 5.2.0 7 | Requires PHP: 7.0.0 8 | Tested up to: 5.2 9 | License: GPLv2 10 | License: 11 | License URI: 12 | 13 | This WordPress plugin demonstrates how to setup a plugin that uses React and ES6 in a WordPress plugin. 14 | 15 | == Description == 16 | 17 | A complete plugin generated by [create-wp-react-app](https://github.com/devowlio/create-wp-react-app) and the boilerplate [wp-reactjs-starter](https://github.com/devowlio/wp-react-starter). It comes with a complete local development environment and the newest technologies to build your WordPress plugin: ES6, TypeScript, Docker, GitLab CI, ... Note: This plugin has no functionality, it simply demonstrates how the plugin gets deployed to wordpress.org plugin directory when using the starter plugin. 18 | 19 | == Installation == 20 | 21 | 1. Goto your wordpress backend 22 | 2. Navigate to Plugins > Add new 23 | 3. Search for "WP ReactJS Starter" 24 | 4. "Install" 25 | 26 | OR 27 | 28 | 1. Download the plugin from this site (wordpress.org) 29 | 2. Copy the extracted folder into your `wp-content/plugins` folder 30 | 3. Activate the "WP ReactJS Starter" plugin via the plugins admin page 31 | 32 | == Frequently Asked Questions == 33 | 34 | 35 | == Screenshots == 36 | 37 | 38 | == Changelog == 39 | [include:CHANGELOG.md] 40 | 41 | == Upgrade Notice == 42 | 43 | -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/wordpress.org/assets/banner-1544x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devowlio/wp-react-starter/96f8fd6afcb68e9b921e66c9984b4451abb24937/plugins/wp-reactjs-starter/wordpress.org/assets/banner-1544x500.png -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/wordpress.org/assets/banner-772x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devowlio/wp-react-starter/96f8fd6afcb68e9b921e66c9984b4451abb24937/plugins/wp-reactjs-starter/wordpress.org/assets/banner-772x250.png -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/wordpress.org/assets/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devowlio/wp-react-starter/96f8fd6afcb68e9b921e66c9984b4451abb24937/plugins/wp-reactjs-starter/wordpress.org/assets/icon-128x128.png -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/wordpress.org/assets/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devowlio/wp-react-starter/96f8fd6afcb68e9b921e66c9984b4451abb24937/plugins/wp-reactjs-starter/wordpress.org/assets/icon-256x256.png -------------------------------------------------------------------------------- /plugins/wp-reactjs-starter/wordpress.org/assets/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devowlio/wp-react-starter/96f8fd6afcb68e9b921e66c9984b4451abb24937/plugins/wp-reactjs-starter/wordpress.org/assets/screenshot-1.png --------------------------------------------------------------------------------