├── .babelrc.js ├── .browserslistrc ├── .coveralls.yml ├── .dev-lib ├── .editorconfig ├── .env.dist ├── .eslintignore ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── help_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmrc ├── .nvmrc ├── .rtlcssrc ├── .stylelintignore ├── .stylelintrc.json ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── assets ├── css │ └── src │ │ ├── block-editor.css │ │ ├── classic-editor.css │ │ ├── customize-controls.css │ │ ├── customize-preview.css │ │ └── front-end.css └── src │ ├── block-editor │ ├── blocks │ │ └── hello-world │ │ │ ├── edit.css │ │ │ ├── index.js │ │ │ └── save.css │ ├── helpers │ │ └── index.js │ └── index.js │ ├── classic-editor │ └── index.js │ ├── customizer │ ├── customize-controls.js │ └── customize-preview.js │ └── front-end │ └── index.js ├── bin ├── includes.sh ├── local-dev │ └── wordpress │ │ ├── Dockerfile │ │ └── config │ │ ├── mysql │ │ └── wptests.sql │ │ └── php │ │ ├── php.ini │ │ └── xdebug.ini ├── logs.sh ├── phpunit.sh ├── start.sh ├── stop.sh ├── tag-built.sh └── xdebug.sh ├── code_of_conduct.md ├── composer.json ├── composer.lock ├── contributing.md ├── contributing ├── engineering.md └── project-management.md ├── docker-compose.yml ├── foo-bar.php ├── init-plugin.sh ├── instance.php ├── jest.config.js ├── package-lock.json ├── package.json ├── php ├── baz-bar │ └── class-sample.php ├── class-exception.php ├── class-plugin-base.php └── class-plugin.php ├── phpcs.xml ├── phpunit.xml ├── postcss.config.js ├── readme.md ├── readme.txt ├── renovate.json ├── tests ├── bootstrap.php ├── e2e │ ├── config │ │ └── bootstrap.js │ ├── jest.config.js │ ├── specs │ │ └── block-editor │ │ │ └── blocks │ │ │ └── hello-world │ │ │ └── index.spec.js │ └── utils │ │ ├── index.js │ │ ├── insert-block.js │ │ ├── open-global-block-inserter.js │ │ └── search-for-block.js ├── js │ ├── block-editor │ │ ├── blocks │ │ │ └── hello-world │ │ │ │ └── index.spec.js │ │ └── helpers │ │ │ └── index.spec.js │ ├── classic-editor │ │ └── index.spec.js │ └── customizer │ │ ├── customize-controls.spec.js │ │ └── customize-preview.spec.js ├── merge-coverage.js ├── phpunit │ ├── class-test-foo-bar.php │ └── php │ │ ├── baz-bar │ │ └── class-test-sample.php │ │ ├── class-test-plugin-base.php │ │ └── class-test-plugin.php └── wp-tests-config.php ├── webpack.config.js └── wp-assets ├── banner-1544x500.png ├── banner-772x250.png ├── icon-128x128.png ├── icon-256x256.png └── screenshot-1.png /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ '@wordpress/default' ], 3 | plugins: [ 4 | '@wordpress/babel-plugin-import-jsx-pragma', 5 | '@babel/transform-react-jsx', 6 | '@babel/plugin-proposal-optional-chaining', 7 | ], 8 | env: { 9 | test: { 10 | plugins: [ 'transform-require-context' ], 11 | }, 12 | e2e: { 13 | plugins: [ 'istanbul' ], 14 | }, 15 | development: { 16 | plugins: [ 'istanbul' ], 17 | }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | extends @wordpress/browserslist-config 2 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | coverage_clover: build/logs/clover.xml 3 | json_path: build/logs/coveralls-upload.json 4 | -------------------------------------------------------------------------------- /.dev-lib: -------------------------------------------------------------------------------- 1 | DEFAULT_BASE_BRANCH=develop 2 | ASSETS_DIR=wp-assets 3 | README_MD_TITLE="Foo Bar Plugin for WordPress" 4 | CHECK_SCOPE=all 5 | DOCKER_PHPUNIT_BIN=bin/phpunit.sh 6 | TRAVIS_CI_COM_URL=true 7 | BADGE_FURY_URL=https://badge.fury.io/gh/xwp%2Fwp-foo-bar 8 | 9 | source ./bin/includes.sh 10 | 11 | function can_run_e2e() { 12 | if command_exists "curl" && is_wp_available "$1"; then 13 | return 0 14 | fi 15 | 16 | return 1 17 | } 18 | 19 | function run_tests { 20 | if [ "$TRAVIS" != true ]; then 21 | echo "" 22 | echo "## Linting" 23 | npm run lint 24 | 25 | echo "" 26 | fi 27 | } 28 | 29 | function run_after_tests { 30 | if [ "$TRAVIS" != true ]; then 31 | echo "" 32 | echo "## JavaScript tests" 33 | npm run test:js 34 | 35 | if can_run_e2e "localhost:8088"; then 36 | echo "" 37 | echo "## E2E tests" 38 | npm run test:e2e 39 | fi 40 | 41 | echo "" 42 | fi 43 | } 44 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # WordPress Coding Standards 2 | # https://make.wordpress.org/core/handbook/coding-standards/ 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | indent_style = tab 12 | 13 | [{.babelrc,.eslintrc,.rtlcssrc,*.json,*.yml}] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [{*.md}] 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /.env.dist: -------------------------------------------------------------------------------- 1 | # MySQL 2 | MYSQL_ROOT_PASSWORD=root 3 | MYSQL_DATABASE=wordpress 4 | MYSQL_USER=wordpress 5 | MYSQL_PASSWORD=wordpress 6 | 7 | # WordPress 8 | WP_VERSION=5.8 9 | WP_DB_USER=wordpress 10 | WP_DB_PASSWORD=wordpress 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.min.js 2 | **/bin/** 3 | **/node_modules/** 4 | **/vendor/** 5 | **/assets/js/*.js 6 | build/* 7 | built/* 8 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@wordpress/eslint-plugin/recommended", 4 | "prettier" 5 | ], 6 | "rules": { 7 | "@wordpress/i18n-text-domain": [ 8 | "error", 9 | { 10 | "allowedTextDomain": [ "foo-bar" ] 11 | } 12 | ], 13 | "prettier/prettier": [ 14 | "error", 15 | { 16 | "singleQuote": true, 17 | "parenSpacing": true, 18 | "trailingComma": "es5", 19 | "jsxBracketSameLine": false, 20 | "arrowParens": "avoid" 21 | } 22 | ] 23 | }, 24 | "overrides": [ 25 | { 26 | "files":[ 27 | "**/__tests__/**/*.js", 28 | "**/test/*.js", 29 | "**/?(*.)test.js", 30 | "tests/js/**/*.js" 31 | ], 32 | "extends": [ 33 | "plugin:jest/all" 34 | ], 35 | "rules": { 36 | "jest/lowercase-name": [ 37 | "error", 38 | { 39 | "ignore": [ "describe" ] 40 | } 41 | ], 42 | "jest/no-hooks": "off", 43 | "jest/prefer-expect-assertions": "off", 44 | "jest/prefer-inline-snapshots": "off" 45 | } 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | ## Bug Description 8 | 9 | 10 | 11 | ## Expected Behaviour 12 | 13 | 14 | 15 | ## Steps to reproduce 16 | 17 | 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | ## Screenshots 24 | 25 | 26 | 27 | ## Additional context 28 | 29 | 30 | - WordPress version: 31 | - Plugin version: 32 | - Gutenberg plugin version (if applicable): 33 | - PHP version: 34 | - OS: 35 | - Browser: [e.g. chrome, safari] 36 | - Device: [e.g. iPhone6] 37 | 38 | 39 | 40 | --------------- 41 | 42 | _Do not alter or remove anything below. The following sections will be managed by moderators only._ 43 | 44 | ## Acceptance criteria 45 | 46 | * 47 | 48 | ## Implementation brief 49 | 50 | * 51 | 52 | ## QA testing instructions 53 | 54 | * 55 | 56 | ## Demo 57 | 58 | * 59 | 60 | ## Changelog entry 61 | 62 | * 63 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | ## Feature description 8 | 9 | 10 | 11 | --------------- 12 | 13 | _Do not alter or remove anything below. The following sections will be managed by moderators only._ 14 | 15 | ## Acceptance criteria 16 | 17 | * 18 | 19 | ## Implementation brief 20 | 21 | * 22 | 23 | ## QA testing instructions 24 | 25 | * 26 | 27 | ## Demo 28 | 29 | * 30 | 31 | ## Changelog entry 32 | 33 | * 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/help_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Help Request 3 | about: Please post help requests or ‘how to’ questions in support forum 4 | 5 | --- 6 | 7 | For general, technical and product help requests, please post it on the [Foo Bar Plugin support forum](https://wordpress.org/support/plugin/foo-bar/). Support will not be provided on GitHub. 8 | 9 | Thank you! 10 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | 4 | Fixes # 5 | 6 | ## Checklist 7 | 8 | - [ ] My pull request is addressing an [open issue](https://github.com/xwp/wp-foo-bar/issues) (please create one otherwise). 9 | - [ ] My code is tested and passes existing [tests](https://github.com/xwp/wp-foo-bar/contributing.md#scripts). 10 | - [ ] My code follows the [Contributing Guidelines](https://github.com/xwp/wp-foo-bar/contributing.md) (updates are often made to the guidelines, check it out periodically). 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env 3 | .idea 4 | foo-bar.zip 5 | assets/css/* 6 | !assets/css/src/ 7 | assets/js/*.css 8 | assets/js/*.js 9 | assets/js/*.asset.php 10 | assets/js/*.map 11 | bin/local-dev/mysql/ 12 | bin/local-dev/wordpress/html/ 13 | build/ 14 | node_modules/ 15 | tests/coverage 16 | vendor/ 17 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict = true 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /.rtlcssrc: -------------------------------------------------------------------------------- 1 | { 2 | "options": { 3 | "autoRename": false, 4 | "autoRenameStrict": false, 5 | "blacklist":{}, 6 | "clean": true, 7 | "greedy": false, 8 | "processUrls": false, 9 | "stringMap":[] 10 | }, 11 | "plugins": [ ], 12 | "map": false 13 | } 14 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | /assets/css/*.css 2 | /assets/js/*.css 3 | /bin 4 | /build 5 | /node_modules 6 | /tests 7 | /vendor 8 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-wordpress", 3 | "rules": { 4 | "font-weight-notation": null, 5 | "function-url-quotes": null, 6 | "max-line-length": null, 7 | "no-descending-specificity": null, 8 | "no-duplicate-selectors": null, 9 | "number-leading-zero": null, 10 | "selector-type-no-unknown": [ true, { "ignore": ["custom-elements"] } ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | cache: 2 | directories: 3 | - $HOME/.composer/cache 4 | - $HOME/.jest-cache 5 | - $HOME/.npm 6 | - $HOME/.nvm/.cache 7 | - $HOME/phpunit-bin 8 | 9 | notifications: 10 | email: 11 | on_success: never 12 | on_failure: change 13 | webhooks: https://coveralls.io/webhook 14 | 15 | language: php 16 | 17 | dist: xenial 18 | 19 | services: 20 | - mysql 21 | - docker 22 | 23 | addons: 24 | apt: 25 | packages: 26 | - libxml2-utils 27 | 28 | branches: 29 | only: 30 | - master 31 | - develop 32 | - /^\d+\.\d+$/ 33 | 34 | env: 35 | global: 36 | - COVERALLS_PARALLEL=true 37 | # Ensure Xdebug v3 coverage reporting is available. 38 | - XDEBUG_MODE=coverage 39 | 40 | before_install: 41 | - nvm install 42 | - nvm use 43 | 44 | install: 45 | - npm install 46 | - export DEV_LIB_PATH=vendor/xwp/wp-dev-lib/scripts 47 | - export DIFF_HEAD=HEAD 48 | - source "$DEV_LIB_PATH/travis.install.sh" 49 | 50 | before_script: 51 | - phpenv config-rm xdebug.ini || echo "xdebug.ini does not exist." 52 | 53 | script: 54 | - source "$DEV_LIB_PATH/travis.script.sh" 55 | 56 | after_script: 57 | - source "$DEV_LIB_PATH/travis.after_script.sh" 58 | 59 | jobs: 60 | fast_finish: true 61 | include: 62 | - stage: lint 63 | name: Lint (PHP, JavaScript, and configuration files) 64 | php: "7.4" 65 | env: WP_VERSION=latest DEV_LIB_ONLY=xmllint,phpsyntax,composer 66 | script: 67 | - source "$DEV_LIB_PATH/travis.script.sh" 68 | - npm run lint 69 | 70 | - stage: test 71 | name: E2E tests with Docker (7.4, WordPress latest, with code coverage) 72 | php: "7.4" 73 | env: NODE_ENV=e2e 74 | install: 75 | - sudo service mysql stop 76 | - npm install 77 | - docker-compose pull 78 | before_script: 79 | - echo "Running E2E tests with code coverage ..." 80 | script: 81 | - npm run env:start 82 | - npm run wp -- bash -c "while ! nc -z mysql 3306; do sleep 1; done" 83 | - npm run wp -- wp core install --title=WordPress --admin_user=admin --admin_password=password --admin_email=admin@example.com --skip-email --url=http://localhost:8088 --quiet 84 | - npm run wp -- wp plugin activate foo-bar 85 | - npm run build:js 86 | - npm run test:e2e:coveralls 87 | after_script: 88 | - echo "E2E tests complete" 89 | 90 | - name: JS unit tests (7.4, WordPress latest, with code coverage) 91 | php: "7.4" 92 | env: WP_VERSION=latest 93 | install: 94 | - sudo service mysql stop 95 | - npm install 96 | before_script: 97 | - echo "Running JS unit tests with code coverage ..." 98 | script: 99 | - npm run build:js 100 | - npm run test:js:coveralls 101 | after_script: 102 | - echo "JS unit tests complete" 103 | 104 | - name: PHP unit tests (7.4, WordPress latest, with code coverage) 105 | php: "7.4" 106 | env: WP_VERSION=latest DEV_LIB_ONLY=phpunit,coverage,composer 107 | before_script: 108 | - echo "Running PHP unit tests with code coverage ..." 109 | 110 | - name: PHP unit tests (7.4, WordPress trunk) 111 | php: "7.4" 112 | env: WP_VERSION=trunk DEV_LIB_ONLY=phpunit,composer 113 | 114 | - name: PHP unit tests (5.6, WordPress latest) 115 | php: "5.6" 116 | env: WP_VERSION=latest DEV_LIB_ONLY=phpunit,composer 117 | before_script: 118 | - composer config --unset platform.php 119 | - composer require --dev phpunit/phpunit:^5 phpunit/phpcov php-coveralls/php-coveralls --update-with-dependencies 120 | 121 | - name: PHP unit tests (5.6, WordPress 5.0) 122 | php: "5.6" 123 | env: WP_VERSION=5.0 DEV_LIB_ONLY=phpunit,composer 124 | before_script: 125 | - composer config --unset platform.php 126 | - composer require --dev phpunit/phpunit:^5 phpunit/phpcov php-coveralls/php-coveralls --update-with-dependencies 127 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | module.exports = function ( grunt ) { 4 | 'use strict'; 5 | 6 | // prettier-ignore 7 | grunt.initConfig( { 8 | // Build a deploy-able plugin. 9 | copy: { 10 | build: { 11 | src: [ 12 | '**', 13 | '!.*', 14 | '!.*/**', 15 | '!.DS_Store', 16 | '!assets/css/src/**', 17 | '!assets/js/.gitignore', 18 | '!assets/src/**', 19 | '!bin/**', 20 | '!build/**', 21 | '!built/**', 22 | '!code_of_conduct.md', 23 | '!contributing/**', 24 | '!composer.json', 25 | '!composer.lock', 26 | '!contributing.md', 27 | '!docker-compose.yml', 28 | '!foo-bar.zip', 29 | '!Gruntfile.js', 30 | '!init-plugin.sh', 31 | '!jest.config.js', 32 | '!node_modules/**', 33 | '!npm-debug.log', 34 | '!package.json', 35 | '!package-lock.json', 36 | '!postcss.config.js', 37 | '!phpcs.xml', 38 | '!phpunit.xml', 39 | '!readme.md', 40 | '!renovate.json', 41 | '!tests/**', 42 | '!vendor/**', 43 | '!webpack.config.js', 44 | '!wp-assets/**', 45 | ], 46 | dest: 'build', 47 | expand: true, 48 | dot: true, 49 | }, 50 | }, 51 | 52 | // Clean up the build. 53 | clean: { 54 | compiled: { 55 | src: [ 56 | 'assets/js/*.js', 57 | '!assets/js/admin.js', 58 | 'assets/js/*.asset.php', 59 | ], 60 | }, 61 | build: { 62 | src: [ 'build' ], 63 | }, 64 | }, 65 | 66 | // Shell actions. 67 | shell: { 68 | options: { 69 | stdout: true, 70 | stderr: true, 71 | }, 72 | readme: { 73 | command: './vendor/xwp/wp-dev-lib/scripts/generate-markdown-readme', // Generate the readme.md. 74 | }, 75 | create_build_zip: { 76 | command: 'if [ ! -e build ]; then echo "Run grunt build first."; exit 1; fi; if [ -e foo-bar.zip ]; then rm foo-bar.zip; fi; cd build; zip -r ../foo-bar.zip .; cd ..; echo; echo "ZIP of build: $(pwd)/foo-bar.zip"', 77 | }, 78 | }, 79 | 80 | // Deploys a git Repo to the WordPress SVN repo. 81 | wp_deploy: { 82 | deploy: { 83 | options: { 84 | plugin_slug: 'foo-bar', 85 | build_dir: 'build', 86 | assets_dir: 'wp-assets', 87 | }, 88 | }, 89 | }, 90 | } ); 91 | 92 | // Load tasks. 93 | grunt.loadNpmTasks( 'grunt-contrib-clean' ); 94 | grunt.loadNpmTasks( 'grunt-contrib-copy' ); 95 | grunt.loadNpmTasks( 'grunt-shell' ); 96 | grunt.loadNpmTasks( 'grunt-wp-deploy' ); 97 | 98 | // Register tasks. 99 | grunt.registerTask( 'default', [ 'build' ] ); 100 | 101 | grunt.registerTask( 'readme', [ 'shell:readme' ] ); 102 | 103 | grunt.registerTask( 'build', [ 'readme', 'copy' ] ); 104 | 105 | grunt.registerTask( 'create-build-zip', [ 'shell:create_build_zip' ] ); 106 | 107 | grunt.registerTask( 'deploy', [ 'build', 'wp_deploy', 'clean' ] ); 108 | }; 109 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /assets/css/src/block-editor.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Put Block Editor CSS here. 3 | */ 4 | -------------------------------------------------------------------------------- /assets/css/src/classic-editor.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Put Classic Editor CSS here. 3 | */ 4 | -------------------------------------------------------------------------------- /assets/css/src/customize-controls.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Put Customize Controls CSS here. 3 | */ 4 | -------------------------------------------------------------------------------- /assets/css/src/customize-preview.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Put Customize Preview CSS here. 3 | */ 4 | -------------------------------------------------------------------------------- /assets/css/src/front-end.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Put Front End CSS here. 3 | */ 4 | -------------------------------------------------------------------------------- /assets/src/block-editor/blocks/hello-world/edit.css: -------------------------------------------------------------------------------- 1 | /* Add the CSS code to this file. On running npm run dev, it will compile with build:js into assets/css/. */ 2 | 3 | .editor-styles-wrapper .wp-block[data-type="foo-bar/hello-world"] h2 { 4 | color: #8b0000; 5 | } 6 | -------------------------------------------------------------------------------- /assets/src/block-editor/blocks/hello-world/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | import { __ } from '@wordpress/i18n'; 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | import './edit.css'; 10 | 11 | export const name = 'foo-bar/hello-world'; 12 | export const Edit = () =>

{ __( 'Hello Editor', 'foo-bar' ) }

; 13 | export const Save = () =>

{ __( 'Hello Website', 'foo-bar' ) }

; 14 | 15 | export const settings = { 16 | title: __( 'Hello World', 'foo-bar' ), 17 | icon: 'smiley', 18 | category: 'common', 19 | edit: Edit, 20 | save: Save, 21 | }; 22 | -------------------------------------------------------------------------------- /assets/src/block-editor/blocks/hello-world/save.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Hello World block 3 | */ 4 | h2.wp-block-foo-bar-hello-world { 5 | color: #8b0000; 6 | } 7 | -------------------------------------------------------------------------------- /assets/src/block-editor/helpers/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | import { registerBlockType } from '@wordpress/blocks'; 5 | 6 | /** 7 | * Registers the blocks with the proper settings. 8 | * 9 | * @param {Object} blocks The blocks to register. 10 | */ 11 | export const registerBlocks = blocks => { 12 | blocks.keys().forEach( modulePath => { 13 | const { name, settings } = blocks( modulePath ); 14 | 15 | registerBlockType( name, settings ); 16 | } ); 17 | }; 18 | -------------------------------------------------------------------------------- /assets/src/block-editor/index.js: -------------------------------------------------------------------------------- 1 | /* istanbul ignore file */ 2 | 3 | /** 4 | * Internal dependencies 5 | */ 6 | import { registerBlocks } from './helpers'; 7 | 8 | /** 9 | * Register the blocks. 10 | */ 11 | registerBlocks( require.context( './blocks', true, /(?/dev/null 2>&1 33 | } 34 | 35 | ## 36 | # Add error message formatting to a string, and echo it. 37 | # 38 | # @param {string} message The string to add formatting to. 39 | ## 40 | error_message() { 41 | echo -en "\033[31mERROR\033[0m: $1" 42 | } 43 | 44 | ## 45 | # Add warning message formatting to a string, and echo it. 46 | # 47 | # @param {string} message The string to add formatting to. 48 | ## 49 | warning_message() { 50 | echo -en "\033[33mWARNING\033[0m: $1" 51 | } 52 | 53 | ## 54 | # Add formatting to an action string. 55 | # 56 | # @param {string} message The string to add formatting to. 57 | ## 58 | action_format() { 59 | echo -en "\033[32m$1\033[0m" 60 | } 61 | -------------------------------------------------------------------------------- /bin/local-dev/wordpress/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG WP_VERSION 2 | 3 | FROM wordpress:${WP_VERSION}-php7.4-apache 4 | 5 | ENV NODE_VERSION 10.18.1 6 | ENV YARN_VERSION 1.21.1 7 | ENV XDEBUG_VERSION 2.9.8 8 | 9 | # Setup user. 10 | RUN groupadd --gid 1000 webserver \ 11 | && useradd --uid 1000 --gid webserver --shell /bin/bash --create-home webserver 12 | 13 | # Install Node. 14 | RUN buildDeps='xz-utils' \ 15 | && ARCH= && dpkgArch="$(dpkg --print-architecture)" \ 16 | && case "${dpkgArch##*-}" in \ 17 | amd64) ARCH='x64';; \ 18 | ppc64el) ARCH='ppc64le';; \ 19 | s390x) ARCH='s390x';; \ 20 | arm64) ARCH='arm64';; \ 21 | armhf) ARCH='armv7l';; \ 22 | i386) ARCH='x86';; \ 23 | *) echo "unsupported architecture"; exit 1 ;; \ 24 | esac \ 25 | && set -ex \ 26 | && apt-get update && apt-get install -y ca-certificates curl wget gnupg dirmngr $buildDeps --no-install-recommends \ 27 | && rm -rf /var/lib/apt/lists/* \ 28 | && for key in \ 29 | 94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \ 30 | FD3A5288F042B6850C66B31F09FE44734EB7990E \ 31 | 71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \ 32 | DD8F2338BAE7501E3DD5AC78C273792F7D83545D \ 33 | C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \ 34 | B9AE9905FFD7803F25714661B63B535A4C206CA9 \ 35 | 77984A986EBC2AA786BC0F66B01FBB92821C587A \ 36 | 8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600 \ 37 | 4ED778F539E3634C779C87C6D7062848A1AB005C \ 38 | A48C2BEE680E841632CD4E44F07496B3EB3C1762 \ 39 | B9E2F5981AA6E0CD28160D9FF13993A75599653C \ 40 | ; do \ 41 | gpg --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys "$key" || \ 42 | gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" ; \ 43 | done \ 44 | && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH.tar.xz" \ 45 | && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \ 46 | && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \ 47 | && grep " node-v$NODE_VERSION-linux-$ARCH.tar.xz\$" SHASUMS256.txt | sha256sum -c - \ 48 | && tar -xJf "node-v$NODE_VERSION-linux-$ARCH.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \ 49 | && rm "node-v$NODE_VERSION-linux-$ARCH.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \ 50 | && apt-get purge -y --auto-remove $buildDeps \ 51 | && ln -s /usr/local/bin/node /usr/local/bin/nodejs 52 | 53 | # Install Yarn. 54 | RUN set -ex \ 55 | && for key in \ 56 | 6A010C5166006599AA17F08146C2130DFD2497F5 \ 57 | ; do \ 58 | gpg --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys "$key" || \ 59 | gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" ; \ 60 | done \ 61 | && curl -fsSLO --compressed "https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz" \ 62 | && curl -fsSLO --compressed "https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz.asc" \ 63 | && gpg --batch --verify yarn-v$YARN_VERSION.tar.gz.asc yarn-v$YARN_VERSION.tar.gz \ 64 | && mkdir -p /opt \ 65 | && tar -xzf yarn-v$YARN_VERSION.tar.gz -C /opt/ \ 66 | && ln -s /opt/yarn-v$YARN_VERSION/bin/yarn /usr/local/bin/yarn \ 67 | && ln -s /opt/yarn-v$YARN_VERSION/bin/yarnpkg /usr/local/bin/yarnpkg \ 68 | && rm yarn-v$YARN_VERSION.tar.gz.asc yarn-v$YARN_VERSION.tar.gz 69 | 70 | # Development tooling dependencies 71 | RUN apt-get update \ 72 | && apt-get install -y --no-install-recommends \ 73 | bash \ 74 | less \ 75 | default-mysql-client \ 76 | git \ 77 | subversion \ 78 | zip \ 79 | unzip \ 80 | curl \ 81 | msmtp \ 82 | && npm install --global npm@latest \ 83 | && rm -rf /var/lib/apt/lists/* 84 | 85 | RUN curl -s https://getcomposer.org/installer | php \ 86 | && mv composer.phar /usr/local/bin/composer 87 | 88 | # Include our custom config for PHP and Xdebug. 89 | COPY config/php/* /usr/local/etc/php/conf.d/ 90 | 91 | # Setup xdebug. 92 | RUN pecl install xdebug-${XDEBUG_VERSION}; \ 93 | docker-php-ext-enable xdebug; 94 | -------------------------------------------------------------------------------- /bin/local-dev/wordpress/config/mysql/wptests.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS wptests; 2 | GRANT ALL ON wptests.* TO 'wptests'@'%' IDENTIFIED BY 'wptests'; 3 | FLUSH PRIVILEGES; 4 | -------------------------------------------------------------------------------- /bin/local-dev/wordpress/config/php/php.ini: -------------------------------------------------------------------------------- 1 | date.timezone = "America/Los_Angeles" 2 | session.auto_start = Off 3 | file_uploads = On 4 | memory_limit = -1 5 | upload_max_filesize = 1G 6 | post_max_size = 1G 7 | 8 | # Forward mail to the MailHog container. 9 | sendmail_path = "/usr/bin/msmtp --host=mailhog --port=1025 --read-recipients --read-envelope-from --auto-from=on" 10 | -------------------------------------------------------------------------------- /bin/local-dev/wordpress/config/php/xdebug.ini: -------------------------------------------------------------------------------- 1 | # Enable remote Xdebug. 2 | xdebug.remote_enable = 1 3 | xdebug.remote_autostart = 1 4 | xdebug.remote_connect_back = 1 5 | xdebug.scream = 1 6 | -------------------------------------------------------------------------------- /bin/logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ./bin/includes.sh 4 | 5 | # Grab full name of wordpress container 6 | WORDPRESS_CONTAINER=$(docker ps | grep wordpress | awk '{print $1}') 7 | 8 | if [[ '' == $WORDPRESS_CONTAINER ]]; then 9 | echo -e "$(error_message "The WordPress Docker container must be running!")" 10 | echo -e "Execute the following command: $(action_format "npm run env:start")" 11 | echo "" 12 | exit 0 13 | fi 14 | 15 | # From: http://patorjk.com/software/taag/#p=display&c=echo&f=Standard&t=Foo%20Bar 16 | echo " _____ ____ "; 17 | echo " | ___|__ ___ | __ ) __ _ _ __ "; 18 | echo " | |_ / _ \ / _ \ | _ \ / _\` | '__|"; 19 | echo " | _| (_) | (_) | | |_) | (_| | | "; 20 | echo " |_| \___/ \___/ |____/ \__,_|_| "; 21 | echo " "; 22 | 23 | echo -e "Here comes the logs in real-time ... $(action_format "done")" 24 | echo "" 25 | 26 | docker-compose logs -f 27 | -------------------------------------------------------------------------------- /bin/phpunit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose run --rm -u 1000 --workdir=/var/www/html/wp-content/plugins/foo-bar wordpress -- composer test 4 | -------------------------------------------------------------------------------- /bin/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source .env 4 | source ./bin/includes.sh 5 | 6 | printf "Starting up containers ..." 7 | 8 | # Start the containers. 9 | docker-compose up -d 2>/dev/null 10 | 11 | if ! command_exists "curl"; then 12 | printf " $(action_format "unknown")" 13 | echo "" 14 | echo -e "$(error_message "The $(action_format "curl") command is not installed on your host machine.")" 15 | echo -e "$(warning_message "Checking that WordPress has been installed has failed.")" 16 | echo -e "$(warning_message "It could take a minute for the Database to become available.")" 17 | else 18 | 19 | # Check for WordPress. 20 | until is_wp_available "localhost:8088"; do 21 | printf "." 22 | sleep 5 23 | done 24 | 25 | printf " $(action_format "done")" 26 | echo "" 27 | fi 28 | 29 | echo "" 30 | echo "Welcome to:" 31 | 32 | # From: http://patorjk.com/software/taag/#p=display&c=echo&f=Standard&t=Foo%20Bar 33 | echo " _____ ____ "; 34 | echo " | ___|__ ___ | __ ) __ _ _ __ "; 35 | echo " | |_ / _ \ / _ \ | _ \ / _\` | '__|"; 36 | echo " | _| (_) | (_) | | |_) | (_| | | "; 37 | echo " |_| \___/ \___/ |____/ \__,_|_| "; 38 | echo " "; 39 | 40 | # Give the user more context to what they should do next: Build the plugin and start testing! 41 | echo -e "Run $(action_format "npm run dev") to build the latest version of the Foo Bar plugin," 42 | echo -e "then open $(action_format "http://localhost:8088/") to get started!" 43 | echo "" 44 | -------------------------------------------------------------------------------- /bin/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ./bin/includes.sh 4 | 5 | printf "Shutting down containers ... " 6 | 7 | docker-compose down 2>/dev/null 8 | 9 | printf "$(action_format "done")" 10 | echo "" 11 | 12 | # From: http://patorjk.com/software/taag/#p=display&c=echo&f=Standard&t=Foo%20Bar 13 | echo " _____ ____ "; 14 | echo " | ___|__ ___ | __ ) __ _ _ __ "; 15 | echo " | |_ / _ \ / _ \ | _ \ / _\` | '__|"; 16 | echo " | _| (_) | (_) | | |_) | (_| | | "; 17 | echo " |_| \___/ \___/ |____/ \__,_|_| "; 18 | echo " "; 19 | 20 | echo "See you again soon, same bat time, same bat channel?" 21 | echo "" 22 | -------------------------------------------------------------------------------- /bin/tag-built.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | tag=$(cat build/foo-bar.php | grep 'Version:' | sed 's/.*: //' | sed 's/-[0-9]\{8\}T[0-9]\{6\}Z-[a-f0-9]*$//') 6 | if [[ -z "$tag" ]]; then 7 | echo "Error: Unable to determine tag from build/foo-bar.php." 8 | exit 1 9 | fi 10 | if ! git rev-parse "$tag" >/dev/null 2>&1; then 11 | echo "Error: Tag does not exist: $tag" 12 | exit 2 13 | fi 14 | 15 | built_tag="$tag-built" 16 | if git rev-parse "$built_tag" >/dev/null 2>&1; then 17 | echo "Error: Built tag already exists: $built_tag" 18 | exit 2 19 | fi 20 | 21 | if ! git diff-files --quiet || ! git diff-index --quiet --cached HEAD --; then 22 | echo "Error: Repo is in dirty state" 23 | exit 3 24 | fi 25 | 26 | if [[ -e built ]]; then 27 | rm -rf built 28 | fi 29 | mkdir built 30 | git clone . built/ 31 | cd built 32 | git checkout $tag 33 | git rm -r $(git ls-files) 34 | rsync -avz ../build/ ./ 35 | git add -A . 36 | git commit -m "Build $tag" --no-verify 37 | git tag "$built_tag" -m "Build $tag" 38 | git push origin "$built_tag" 39 | cd .. 40 | git push origin "$built_tag" 41 | rm -rf built 42 | 43 | echo "Pushed tag $built_tag." 44 | echo "See https://github.com/xwp/wp-foo-bar/releases/tag/$built_tag" 45 | echo "For https://github.com/xwp/wp-foo-bar/releases/tag/$tag" 46 | -------------------------------------------------------------------------------- /bin/xdebug.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # NOTE: At the moment, this has only been confirmed to work with PHP 7 4 | 5 | source ./bin/includes.sh 6 | 7 | # Grab full name of wordpress container 8 | WORDPRESS_CONTAINER=$(docker ps | grep wordpress | awk '{print $1}') 9 | 10 | if [[ '' == $WORDPRESS_CONTAINER ]]; then 11 | echo -e "$(error_message "The WordPress Docker container must be running!")" 12 | echo -e "Execute the following command: $(action_format "npm run env:start")" 13 | echo "" 14 | exit 0 15 | fi 16 | 17 | # Grab OS type 18 | if [[ "$(uname)" == "Darwin" ]]; then 19 | OS_TYPE="OSX" 20 | else 21 | OS_TYPE=$(expr substr $(uname -s) 1 5) 22 | fi 23 | 24 | xdebug_status () { 25 | printf "Getting Xdebug status ... " 26 | 27 | # If running on Windows, need to prepend with winpty :( 28 | if [[ ${OS_TYPE} == "MINGW" ]]; then 29 | STATUS=`winpty docker exec -it ${WORDPRESS_CONTAINER} bash -c 'php -v'` 30 | else 31 | STATUS=`docker exec -it ${WORDPRESS_CONTAINER} bash -c 'php -v'` 32 | fi 33 | 34 | printf "$(action_format "done")" 35 | echo "" 36 | echo "$(action_format "$STATUS")" 37 | } 38 | 39 | xdebug_on () { 40 | printf "Turning Xdebug ON ... "; 41 | 42 | # And uncomment line with xdebug extension, thus enabling it 43 | ON_CMD="sed -i 's/^;zend_extension=/zend_extension=/g' \ 44 | /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini" 45 | 46 | # If running on Windows, need to prepend with winpty :( 47 | if [[ ${OS_TYPE} == "MINGW" ]]; then 48 | winpty docker exec -it ${WORDPRESS_CONTAINER} bash -c "${ON_CMD}" 49 | else 50 | docker exec -it ${WORDPRESS_CONTAINER} bash -c "${ON_CMD}" 51 | fi 52 | 53 | printf "$(action_format "done")"; 54 | echo "" 55 | 56 | container_restart 57 | } 58 | 59 | xdebug_off () { 60 | printf "Turning Xdebug OFF ... "; 61 | 62 | # Comment out xdebug extension line 63 | OFF_CMD="sed -i 's/^zend_extension=/;zend_extension=/g' /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini" 64 | 65 | # If running on Windows, need to prepend with winpty :( 66 | if [[ ${OS_TYPE} == "MINGW" ]]; then 67 | # This is the equivalent of: 68 | # winpty docker exec -it kurly_finance_php_wordpress_1 bash -c 'bla bla bla' 69 | # Thanks to @michaelarnauts at https://github.com/docker/compose/issues/593 70 | winpty docker exec -it ${WORDPRESS_CONTAINER} bash -c "${OFF_CMD}" 71 | else 72 | docker exec -it ${WORDPRESS_CONTAINER} bash -c "${OFF_CMD}" 73 | fi 74 | 75 | printf "$(action_format "done")"; 76 | echo "" 77 | 78 | container_restart 79 | } 80 | 81 | container_restart () { 82 | # docker-compose restart wordpress 83 | printf "Restarting container ... " 84 | 85 | docker restart ${WORDPRESS_CONTAINER} >/dev/null 86 | 87 | printf "$(action_format "done")"; 88 | echo "" 89 | 90 | xdebug_status 91 | } 92 | 93 | case $@ in 94 | off|OFF) 95 | xdebug_off 96 | ;; 97 | on|ON) 98 | xdebug_on 99 | ;; 100 | status|STATUS) 101 | xdebug_status 102 | ;; 103 | *) 104 | echo "Usage:" 105 | echo " $(action_format "bin/xdebug") ($(action_format "off")|$(action_format "on")|$(action_format "status"))" 106 | esac 107 | 108 | exit 0 109 | -------------------------------------------------------------------------------- /code_of_conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at technology@xwp.co. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xwp/wp-foo-bar", 3 | "type": "wordpress-plugin", 4 | "description": "Plugin Scaffolding for WordPress.", 5 | "homepage": "https://github.com/xwp/wp-foo-bar", 6 | "license": "GPL-2.0-or-later", 7 | "minimum-stability": "dev", 8 | "prefer-stable": true, 9 | "config": { 10 | "sort-packages": true, 11 | "platform": { 12 | "php": "7.4.2", 13 | "ext-filter": "7.1" 14 | } 15 | }, 16 | "require": { 17 | "php": ">=5.6.20" 18 | }, 19 | "require-dev": { 20 | "automattic/vipwpcs": "@stable", 21 | "dealerdirect/phpcodesniffer-composer-installer": "@stable", 22 | "php-coveralls/php-coveralls": "^2.2.0", 23 | "phpcompatibility/phpcompatibility-wp": "@stable", 24 | "phpunit/phpcov": "^5.0", 25 | "phpunit/phpunit": "^7", 26 | "slowprog/composer-copy-file": "@stable", 27 | "wp-coding-standards/wpcs": "@stable", 28 | "xwp/wordpress-tests-installer": "@stable", 29 | "xwp/wp-dev-lib": "@stable", 30 | "yoast/phpunit-polyfills": "^1.0" 31 | }, 32 | "scripts": { 33 | "build": [ 34 | "composer install --no-dev --prefer-dist --optimize-autoloader --no-scripts" 35 | ], 36 | "format": [ 37 | "phpcbf ." 38 | ], 39 | "lint": [ 40 | "@composer validate --strict", 41 | "phpcs ." 42 | ], 43 | "post-install-cmd": [ 44 | "@setup" 45 | ], 46 | "post-update-cmd": [ 47 | "@setup" 48 | ], 49 | "readme": [ 50 | "vendor/xwp/wp-dev-lib/scripts/generate-markdown-readme" 51 | ], 52 | "setup": [ 53 | "SlowProg\\CopyFile\\ScriptHandler::copy", 54 | "if [ ! -f .git/hooks/pre-commit ]; then vendor/xwp/wp-dev-lib/scripts/install-pre-commit-hook.sh; fi", 55 | "if [ ! -f .env ]; then cp .env.dist .env; fi" 56 | ], 57 | "test": [ 58 | "phpunit" 59 | ], 60 | "test-coverage": [ 61 | "phpunit --coverage-html tests/coverage/phpunit" 62 | ] 63 | }, 64 | "extra": { 65 | "installer-name": "foo-bar", 66 | "copy-file": { 67 | "tests/wp-tests-config.php": "vendor/xwp/wordpress-tests/phpunit/wp-tests-config.php" 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Foo Bar contributing guide 2 | 3 | We'd love to accept your patches and contributions to this project and hope you'll become an ongoing participant in our open source community but we also welcome one-off contributions for the issues you're particularly passionate about. 4 | 5 | **How would you like to help?** 6 | 7 | * [Report a bug](#report-a-bug) 8 | * [Make a suggestion](#make-a-suggestion) 9 | * [Contribute code](#product-and-code-contributions) 10 | 11 | ## Report a bug 12 | 13 | [File an issue](https://github.com/xwp/wp-foo-bar/issues/new?template=bug_report.md) if you find a bug in the Foo Bar Plugin. Providing details for each section predefined in the issue template is much appreciated! 14 | 15 | Project maintainers are regularly monitoring issues and aim to fix open bugs quickly. 16 | 17 | ## Make a suggestion 18 | 19 | [File a "feature request" issue](https://github.com/xwp/wp-foo-bar/issues/new?template=feature_request.md) if you have a suggestion for a way to improve the Foo Bar Plugin, or a request for a new feature. 20 | 21 | ## Product and code contributions 22 | 23 | We'd love to have your help contributing code and features to the Foo Bar Plugin! Head to the [Project Management guidelines](contributing/project-management.md) as well as [Engineering guidelines](contributing/engineering.md) for details on the process you can use to contribute. 24 | 25 | ## Contributors list policy 26 | 27 | The list of contributors who are featured on the WordPress.org plugin directory are subject to change over time. The organizations and individuals who contribute significantly and consistently (e.g. 3-month period) to the project are eligible to be listed. Those listed should generally be considered as those who take responsibility for the project (i.e. owners). Note that contributions include more than just code, though contributors who commit are [most visible](https://github.com/xwp/wp-foo-bar/graphs/contributors). The sort order of the contributors list should generally follow the sort order of the GitHub contributors page, though again, this order does not consider work in issues and the support forum, so it cannot be relied on solely. 28 | 29 | ## Code of conduct 30 | 31 | In addition to the Community Guidelines, this project follows an explicit [Code of Conduct](code_of_conduct.md). 32 | -------------------------------------------------------------------------------- /contributing/engineering.md: -------------------------------------------------------------------------------- 1 | # Engineering guidelines 2 | 3 | ## Getting started 4 | 5 | ### Requirements 6 | 7 | To contribute to this plugin, you need the following tools installed on your computer: 8 | 9 | * PHP 5.6.20+ is required and WordPress 5.0+ or the [Gutenberg Plugin](https://wordpress.org/plugins/gutenberg/) 10 | * [Composer](https://getcomposer.org/) - to install PHP dependencies. 11 | * [Node.js](https://nodejs.org/en/) - to install JavaScript dependencies. 12 | * [WordPress](https://wordpress.org/download/) - to run the actual plugin. 13 | * [Docker](https://docs.docker.com/install/) - for a local development environment. 14 | 15 | We use `npm` as the canonical task runner for the project. Some of the PHP related scripts are defined in `composer.json` but are not meant to be executed directly. You should be running a Node version matching the [current active LTS release](https://github.com/nodejs/Release#release-schedule) or newer for this plugin to work correctly. You can check your Node.js version by typing `node -v` in the Terminal prompt. 16 | 17 | If you have an incompatible version of Node in your development environment, you can use [nvm](https://github.com/creationix/nvm) to change node versions on the command line: 18 | 19 | ```bash 20 | nvm install 21 | ``` 22 | 23 | We suggest using a software package manager for installing the development dependencies such as [Homebrew](https://brew.sh) on MacOS: 24 | 25 | ```bash 26 | brew install php composer node docker docker-compose 27 | ``` 28 | 29 | or [Chocolatey](https://chocolatey.org) for Windows: 30 | 31 | ```bash 32 | choco install php composer node nodejs docker-compose 33 | ``` 34 | 35 | ## Local environment 36 | 37 | Since you need a WordPress environment to run the plugin, the quickest way to get up and running is to use the provided Docker setup. Install [Docker](https://docs.docker.com/install/) by following the instructions on their website. WordPress will be available on [localhost:8088](http://localhost:8088/). Ensure that no other Docker containers or services are using `mysql` port `3306` on your machine to avoid collisions. 38 | 39 | Clone this project somewhere on your computer: 40 | 41 | ```bash 42 | git clone git@github.com:xwp/wp-foo-bar.git foo-bar 43 | cd foo-bar 44 | ``` 45 | 46 | Alternatively, you can use your own local WordPress environment and clone this repository right into your `wp-content/plugins` directory. However, the phpunit tests need Docker to be setup unless the environment can run them outside of the container. 47 | 48 | Support for the following environments have been verified to work 49 | 50 | * [WordPressDev Environment](https://github.com/GoogleChromeLabs/wordpressdev) created by Google 51 | 52 | ```bash 53 | cd wp-content/plugins 54 | git clone git@github.com:xwp/wp-foo-bar.git foo-bar 55 | cd foo-bar 56 | ``` 57 | 58 | Setup the development tools using [Node.js](https://nodejs.org) and [Composer](https://getcomposer.org): 59 | 60 | ```bash 61 | npm install 62 | ``` 63 | 64 | _This will automatically install the `pre-commit` hook from the `wp-dev-lib` Composer package and setup the unit tests._ 65 | 66 | Start the the Docker environment: 67 | 68 | ```bash 69 | npm run env:start 70 | ``` 71 | 72 | _**Important**: You must execute this command before the `pre-commit` hook will work properly. This is because the unit tests depend on the MySQL database being initialized. The first time you run this command the Docker image needs to be built and could take several minutes to complete, so have patience young Padawan._ 73 | 74 | If everything was successful, you'll see this on your screen: 75 | 76 | ```bash 77 | Starting up containers ... done 78 | 79 | Welcome to: 80 | _____ ____ 81 | | ___|__ ___ | __ ) __ _ _ __ 82 | | |_ / _ \ / _ \ | _ \ / _` | '__| 83 | | _| (_) | (_) | | |_) | (_| | | 84 | |_| \___/ \___/ |____/ \__,_|_| 85 | 86 | Run npm run dev to build the latest version of the Foo Bar plugin, 87 | then open http://localhost:8088/ to get started! 88 | ``` 89 | 90 | Stop the Docker environment: 91 | 92 | ```bash 93 | npm run env:stop 94 | ``` 95 | 96 | _Be sure to do this when you are done developing so you free up `mysql` port `3306` and do not leave your containers running._ 97 | 98 | See the Docker environment logs: 99 | 100 | ```bash 101 | npm run env:logs 102 | ``` 103 | 104 | Build of the JavaScript files: 105 | 106 | ```bash 107 | npm run build:js 108 | ``` 109 | 110 | Lastly, to get the plugin running in your WordPress install, activate the plugin via the WordPress dashboard, or the following `wp-cli` command: 111 | 112 | ```bash 113 | wp plugin activate foo-bar 114 | ``` 115 | 116 | If running this from the included Docker environment: 117 | 118 | ```bash 119 | npm run wp -- wp plugin activate foo-bar 120 | ``` 121 | 122 | _This command assumes you went through the WordPress install process already_ 123 | 124 | Visit [localhost:8025](http://localhost:8025) to check all emails sent by WordPress. 125 | 126 | ## Developing the plugin 127 | 128 | Whether you use the pre-existing local environment or a custom one, any PHP code changes will be directly visible during development. 129 | 130 | However, for JavaScript this involves a build process. To watch for any JavaScript file changes and re-build it when needed, you can run the following command: 131 | 132 | ```bash 133 | npm run dev 134 | ``` 135 | 136 | _This way you will get a development build of the JavaScript, which makes debugging easier._ 137 | 138 | To get a production build, run: 139 | 140 | ```bash 141 | npm run build:js 142 | ``` 143 | 144 | ## Continuous Integration 145 | 146 | We use [Travis CI](https://travis-ci.com) to lint all code, run tests and report test coverage to [Coveralls](https://coveralls.io) as defined in [`.travis.yml`](.travis.yml). Travis CI will run the unit tests and perform sniffs against the WordPress Coding Standards whenever you push changes to your PR. Tests are required to pass successfully for a merge to be considered. 147 | 148 | ### Branches 149 | 150 | The branching strategy follows the [GitFlow schema](https://datasift.github.io/gitflow/IntroducingGitFlow.html); make sure to familiarize yourself with it. 151 | 152 | All branches are named with with the following pattern: `{type}`/`{issue_id}`-`{short_description}` 153 | 154 | * `{type}` = issue Type label 155 | * `{issue_id}` = issue ID 156 | * `{short_description}` = short description of the PR 157 | 158 | To include your changes in the next patch release (e.g. `1.0.x`), please base your branch off of the current release branch (e.g. `1.0.0`) and open your pull request back to that branch. If you open your pull request with the `develop` branch then it will be by default included in the next minor version (e.g. `1.x.x`). 159 | 160 | ### Code reviews 161 | 162 | All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests. 163 | 164 | ### Coding standards 165 | 166 | All contributions to this project will be checked against [WordPress-Coding-Standards](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards) with [PHPCS](https://github.com/squizlabs/PHP_CodeSniffer), [stylelint](https://www.npmjs.com/package/stylelint-config-wordpress) for CSS, and for JavaScript linting is done with [ESLint](https://eslint.org/). 167 | 168 | To verify your code meets the requirements, you can run `npm run lint`. 169 | 170 | - `npm run lint:css` to lint the CSS files with [stylelint](https://www.npmjs.com/package/stylelint-config-wordpress). 171 | 172 | - `npm run lint:js` to lint only JavaScript files with [eslint](https://eslint.org/). 173 | 174 | - `npm run lint:php` to lint only PHP files with [phpcs](https://github.com/squizlabs/PHP_CodeSniffer). 175 | 176 | To format your CSS, PHP, and JS code, you can run `npm run format`. 177 | 178 | - `npm run format:css` to format the CSS files with [stylelint](https://www.npmjs.com/package/stylelint-config-wordpress). 179 | 180 | - `npm run format:js` to format the JS files with [prettier](https://www.npmjs.com/package/prettier). 181 | 182 | - `npm run format:php` to format the PHP files with [phpcbf](https://github.com/squizlabs/PHP_CodeSniffer). 183 | 184 | ### Tests 185 | 186 | #### PHP Unit Tests 187 | 188 | The plugin uses the [PHPUnit](https://phpunit.de/) testing framework to write unit and integration tests for the PHP part. 189 | 190 | **Important**: The commands that execute phpunit tests or generate coverage reports (i.e. contain `test:php` in the name) should be executed inside the Docker container. 191 | 192 | To run the full test suite, you can use the following command: 193 | 194 | ```bash 195 | npm run docker -- npm run test 196 | ``` 197 | 198 | _This assumes you are using the built-in development environment and requires running the command from the Docker container_ 199 | 200 | ```bash 201 | npm run test:php 202 | ``` 203 | 204 | _This assumes you are using an alternative development environment that has all the required tools setup_ 205 | 206 | ##### Xdebug 207 | 208 | By default the container will be initialized with Xdebug on. Here are a few helper functions to check the status and turn on/off Xdebug. 209 | 210 | Get the status of Xdebug in the running WordPress container: 211 | 212 | ```bash 213 | npm run xdebug:status 214 | ``` 215 | 216 | Turn Xdebug on in the running WordPress container: 217 | 218 | ```bash 219 | npm run xdebug:on 220 | ``` 221 | 222 | Turn Xdebug off the running WordPress container: 223 | 224 | ```bash 225 | npm run xdebug:off 226 | ``` 227 | 228 | #### JavaScript Unit Tests 229 | 230 | [Jest](https://jestjs.io/) is used as the JavaScript unit testing framework. 231 | 232 | To run the full test suite, you can use the following command: 233 | 234 | ```bash 235 | npm run test:js 236 | ``` 237 | 238 | #### JavaScript End-to-End Tests 239 | 240 | [Jest](https://jestjs.io/) in combination with [Puppeteer](https://pptr.dev/) is used as the JavaScript end-to-end testing framework. 241 | 242 | To run the full end-to-end test suite, you can use the following command: 243 | 244 | ```bash 245 | npm run test:e2e 246 | ``` 247 | 248 | **Note**: If your installation is running on a different domain than `localhost:8088` and you are using a different username and/or password, you might need to run the following command instead: 249 | 250 | ```bash 251 | npm run test:e2e -- --wordpress-base-url=http://your-domain-name:your-port --wordpress-username=your-admin-username --wordpress-password=your-admin-password 252 | ``` 253 | 254 | ## Creating a plugin build 255 | 256 | To create a build of the plugin for installing in WordPress as a ZIP package, run: 257 | 258 | ```bash 259 | npm run build 260 | ``` 261 | 262 | This will create an `foo-bar.zip` in the plugin directory which you can install. The contents of this ZIP are also located in the `build` directory which you can `rsync` somewhere as well if needed. 263 | 264 | ## Creating a pre-release 265 | 266 | 1. Create changelog draft on [Wiki page](https://github.com/xwp/wp-foo-bar/wiki/Release-Changelog-Draft). 267 | 1. Check out the branch intended for release (`develop` for major, `x.y` for minor) and pull latest commits. 268 | 1. Bump plugin versions in `foo-bar.php`. 269 | 1. Do `npm install`. 270 | 1. Do `npm run build` and install the `foo-bar.zip` onto a normal WordPress install running a stable release build; do smoke test to ensure it works. 271 | 1. [Draft new release](https://github.com/xwp/wp-foo-bar/releases/new) on GitHub targeting the required branch (`develop` for major, `x.y` for minor). 272 | 1. Use the new plugin version as the tag (e.g. `1.2-beta3` or `1.2.1-RC1`) 273 | 1. Use new version as the title, followed by some highlight tagline of the release. 274 | 1. Attach the `foo-bar.zip` build to the release. 275 | 1. Add a changelog entry to the release, link to the compare view (comparing the previous release), and a link to the milestone. 276 | 1. Make sure “Pre-release” is checked. 277 | 1. Publish GitHub release. 278 | 1. Create built release tag (from the just-created `build` directory): 279 | 1. do `git fetch --tags && ./bin/tag-built.sh` 280 | 1. Add link from release notes. 281 | 1. Bump version in release branch, e.g. `…-alpha` to `…-beta1` and `…-beta2` to `…-RC1` 282 | 1. Publish release blog post (if applicable), including link to GitHub release. 283 | 284 | ## Creating a stable release 285 | 286 | Contributors who want to make a new release, follow these steps: 287 | 288 | 1. Create changelog draft on [Wiki page](https://github.com/xwp/wp-foo-bar/wiki/Release-Changelog-Draft). 289 | 1. Gather props list of the entire release, including contributors of code, design, testing, project management, etc. 290 | 1. Update readme including the description, contributors, and screenshots (as needed). 291 | 1. For major release, draft blog post about the new release. 292 | 1. For minor releases, make sure all merged commits in `develop` have been also merged onto release branch. 293 | 1. Check out the branch intended for release (`develop` for major, `x.y` for minor) and pull latest commits. 294 | 1. Do `npm install`. 295 | 1. Bump plugin versions in `foo-bar.php`. Ensure patch version number is supplied for major releases, so `1.2-RC1` should bump to `1.2.0`. 296 | 1. Ensure "Tested Up To" is updated to current WordPress version. 297 | 1. Do `npm run build` and install the `foo-bar.zip` onto a normal WordPress install running a stable release build; do smoke test to ensure it works. 298 | 1. Optionally do sanity check by comparing the `build` directory with the previously-deployed plugin on WordPress.org for example: `svn export https://plugins.svn.wordpress.org/foo-bar/trunk /tmp/foo-bar-trunk; diff /tmp/foo-bar-trunk/ ./build/` (instead of straight `diff`, it's best to use a GUI like `idea diff`, `phpstorm diff`, or `opendiff`). 299 | 1. [Draft new release](https://github.com/xwp/wp-foo-bar/releases/new) on GitHub targeting the required branch (`develop` for major, `x.y` for minor): 300 | 1. Use the new plugin version as the tag (e.g. `1.2.0` or `1.2.1`) 301 | 1. Attach the `foo-bar.zip` build to the release. 302 | 1. Add a changelog entry to the release, link to the compare view (comparing the previous release), and a link to the milestone. 303 | 1. Publish GitHub release. 304 | 1. Run `npm run deploy` to commit the plugin to WordPress.org. 305 | 1. Confirm the release is available on WordPress.org; try installing it on a WordPress install and confirm it works. 306 | 1. Create built release tag (from the just-created `build` directory): 307 | 1. do `git fetch --tags && ./bin/tag-built.sh` 308 | 1. Add link from release notes. 309 | 1. For new major releases, create a release branch from the tag. Patch versions are made from the release branch. 310 | 1. For minor releases, bump `Stable tag` in the `readme.txt`/`readme.md` in `develop`. Cherry-pick other changes as necessary. 311 | 1. Merge release tag into `master`. 312 | 1. Close the GitHub milestone (and project). 313 | 1. Bump version in release branch. After major release (e.g. `1.2.0`), bump to `1.3.0-alpha` on `develop`; after minor release (e.g. `1.2.1`) bump version to `1.2.2-alpha` on the release branch. 314 | 1. Publish release blog post (if applicable), including link to GitHub release. 315 | 316 | ## Changelog 317 | 318 | Release changelogs are created by an automation script that accumulates changelog messages from issues associated with a given milestone. 319 | 320 | ### Changelog messages 321 | 322 | * Changelog messages are added in the PR-related issue, within its reserved section, which is pre-populated from the issue template. 323 | * Changelog messages start with a verb in its imperative form (e.g. “Fix bug xyz”), preferably one of the following words: 324 | * Add (for features) 325 | * Introduce (for features) 326 | * Enhance (for enhancements) 327 | * Improve (for enhancements) 328 | * Change (for misc changes) 329 | * Update (for misc changes) 330 | * Modify (for misc changes) 331 | * Remove (for removal) 332 | * Fix (for bug fixes) 333 | * N/A (skip changelog message) 334 | 335 | ### Changelog format 336 | 337 | * The changelog messages are categorized as follows: 338 | * Added 339 | * Enhanced 340 | * Changed 341 | * Fixed 342 | * Changelog messages are automatically assigned to one of the defined categories based on the first word the message starts with. Default: “Changed”. 343 | * Changelogs with the message “N/A” are skipped. 344 | 345 | Maintainers must ensure that changelog messages are clear and follow the formatting guidelines. 346 | -------------------------------------------------------------------------------- /contributing/project-management.md: -------------------------------------------------------------------------------- 1 | # Project management guidelines 2 | 3 | ## Project boards 4 | 5 | In addition to [Milestones][milestones], which are used to manage releases, project boards are used to manage issue statuses. 6 | 7 | ### Definition project board 8 | 9 | The [Definition][definition] project board covers the pipeline which issues go through in preparation for execution. The board contains the following columns: 10 | 11 | * Revisit Later 12 | * Prioritization 13 | * Acceptance Criteria 14 | * Implementation Brief 15 | * Review 16 | * Estimate 17 | 18 | #### Setup 19 | 20 | * Click the `Project` tab from the GitHub repository. 21 | * Click the `Create a Project` button. 22 | * Set `Definition` as the Project board name and select the `None` template. 23 | 24 | Create these columns with automation: 25 | * Revisit Later (None) 26 | * Prioritization (To do) 27 | * Move issues here when... Newly added 28 | * Move issues here when... Reopened 29 | * Acceptance Criteria (In progress) 30 | * Implementation Brief (In progress) 31 | * Review (In progress) 32 | * Estimate (In progress) 33 | 34 | ### Execution project board 35 | 36 | The [Execution][execution] project board covers the execution pipeline which issues go through for implementation. The board contains the following columns: 37 | 38 | * Blocked 39 | * Backlog 40 | * To Do 41 | * In Progress 42 | * Code Review 43 | * QA 44 | * Demo 45 | * Approval 46 | * Done 47 | 48 | #### Setup 49 | 50 | * Click the `Project` tab from the GitHub repository. 51 | * Click the `New project`. 52 | * Set `Execution` as the Project board name and select the `None` template. 53 | 54 | Create these columns with automation: 55 | * Blocked (None) 56 | * Backlog (To do) 57 | * Move issues here when... Reopened 58 | * Move pull requests here when... Reopened 59 | * To Do (To do) 60 | * In Progress (In progress) 61 | * Move pull requests here when... Newly added 62 | * Code Review (In progress) 63 | * Move pull requests here when... Pending approval by reviewer 64 | * QA (In progress) 65 | * Move pull requests here when... Approved by reviewer 66 | * Demo (In progress) 67 | * Approval (In progress) 68 | * Done (Done) 69 | * Move issues here when... Closed 70 | * Move pull requests here when... Closed with unmerged commits 71 | 72 | 73 | The labels below are utilized to categorize issues: 74 | 75 | * Type: `{type}` = the issue type (ex. `Type: Bug`, `Type: Enhancement`, `Type: Feature`, `Type: Taks`) 76 | * P`{priority}` = the priority of the task (ex. `P0`, `P1`, `P2`, `P3`, `P4`, `P5`) 77 | * S`{size}` = the issue size (ex. `S1`, `S2`, `S3`, `S5`, `S8`, `S13`, `S21`) 78 | * Sprint: `{sprint_number`} = the sprint associated to the issue (ex. `Sprint: 1`, `Sprint: 2`, `Sprint: 3`) 79 | 80 | ## Life of an issue 81 | 82 | **IMPORTANT**: We use GitHub issues to track all task statuses, therefore PRs should **only** be associated with an issue, **not** assigned a label, project, and/or milestone. 83 | 84 | ### Triage 85 | 86 | The [GitHub issues][issues] view serves as the “Awaiting Triage” backlog. 87 | 88 | 1. An issue is created. 89 | 1. The issue “Type” label is assigned. 90 | 91 | ### Issue cycle by type 92 | 93 | #### Type: `Bug`, `Enhancement`, `Feature` 94 | 95 | ##### Definition 96 | 97 | | Step | Task | Role | 98 | | :--- | :--- | :--- | 99 | | 1. | An issue requiring work is added to the [Definition][definition] project board (automatically added to Prioritization column). | `Project Manager` 100 | | 2. | The issue is assigned a “Priority” and moved to the “Acceptance Criteria” column. | `Product Owner` 101 | | 3. | “Acceptance Criteria” are added to the issue description, and the issue is moved to the “Implementation Brief” column. | `Project Manager` `Product Owner` `Lead Engineer` 102 | | 4. | “Implementation Brief” is added to the issue description, and the issue is moved to the “Review” column. | `Engineer` 103 | | 5. | The “Implementation Brief” is reviewed and the issue is moved to the “Estimate” column upon approval. The issue is moved back to the “Implementation Brief” column if changes in the “Implementation Brief” description are requested. | `Lead Engineer` 104 | | 6. | The issue is estimated using T-Shirt sizing and moved to the [Execution][execution] project board (automatically added to the Backlog). | `Project Manager` `Engineer` 105 | 106 | ##### Execution 107 | | Step | Task | Role | 108 | | :--- | :--- | :--- | 109 | | 1. | An issue requiring work is added to the [Execution][execution] project board (automatically added to the Backlog) after going through the [Definition][definition] project board pipeline. | `Project Manager` 110 | | 2. | The issue is assigned a “[Milestone][milestones]” and “Sprint” label. | `Project Manager` 111 | | 3. | The issue is moved to the “To Do” column if it is assigned to the current sprint. | `Project Manager` 112 | | 4. | The issue is assigned (or may be self-assigned) to an engineer. | `Project Manager` `Engineer` 113 | | 5. | The issue is moved to the “In Progress” column when development starts. A PR is created, following the [Branching Strategy](engineering.md#branches). The PR must contain details for each section predefined in the PR template, with a reference to the associated issue. **IMPORTANT:** do not add [GitHub keywords](https://help.github.com/en/articles/closing-issues-using-keywords) which would automatically close an issue once the PR is merged. | `Engineer` 114 | | 6. | The “[Changelog Message](engineering.md#changelog)” is added to the relevant section of the issue description once development is completed. | `Engineer` 115 | | 7. | The “QA Testing Instructions“ are added to the relevant section of the issue description and the issue is moved to the “Code Review” column. | `Engineer` 116 | | 8. | The code review is done in the referred PR and the issue is moved to the “QA” column once the review is completed and the PR is approved, merged and deployed to the QA environment. The reviewer must ensure that the “Acceptance Criteria” match the implementation and that the “QA Testing Instructions“ has been added to the issue before moving it to QA. | `Engineer` 117 | | 9. | The issue is moved to the “Demo” column once QA is passed or moved back to the “To Do” column if changes are required, in which case the cycle from the “To Do” column onwards is repeated. | `QA Specialist` 118 | | 9. | A video or screenshots demoing the implementation are added to the relevant section of the issue description. | `Engineer` 119 | | 10. | The issue is moved to the “Approval” column once the demo is added. | `Engineer` 120 | | 11. | The issue goes through a final review and moved to the “Done” once approved or moved back to the “To Do” column if changes are required, in which case the cycle from the “To Do” column onwards is repeated. | `Project Manager` 121 | | 12. | The issue is closed. | `Product Manager` 122 | 123 | #### Type: `Task` 124 | 125 | ##### Definition 126 | 127 | | Step | Task | Role | 128 | | :--- | :--- | :--- | 129 | | 1. | An issue requiring work is added to the [Definition][definition] project board (automatically added to Prioritization column). | `Project Manager` 130 | | 2. | The issue is assigned a “Priority” and moved to the “Acceptance Criteria” column. | `Project Manager` 131 | | 3. | “Acceptance Criteria” are added to the issue description, and the issue is moved to the “Estimate" column. | `Project Manager` `Product Owner` `Lead Engineer` 132 | | 4. | The issue is estimated using T-Shirt sizing and moved to the [Execution][execution] project board (automatically added to the Backlog). | `Task specific` 133 | 134 | ##### Execution 135 | | Step | Task | Role | 136 | | :--- | :--- | :--- | 137 | | 1. | An issue requiring work is added to the [Execution][execution] project board (automatically added to the Backlog) after going through the [Definition][definition] project board pipeline. | `Project Manager` 138 | | 2. | The issue is assigned a “Sprint” label. | `Project Manager` 139 | | 3. | The issue is moved to the “To Do” column if it is assigned to the current sprint. | `Project Manager` 140 | | 4. | The issue is assigned (or may be self-assigned) | `Project Manager` `Assignee` 141 | | 5. | The issue is moved to the “In progress” column when work starts. | `Assignee` 142 | | 8. | The issue is moved to the “Approval” column the work is done. | `Assignee` 143 | | 9. | The issue goes through a final review and moved to the “Done” once approved or moved back to the “To Do” column if changes are required, in which case the cycle from the “To Do” column onwards is repeated. | `Project Manager` 144 | | 10. | The issue is closed. | `Project Manager` 145 | 146 | [milestones]: https://github.com/xwp/wp-foo-bar/milestones 147 | [issues]: https://github.com/xwp/wp-foo-bar/issues 148 | [definition]: https://github.com/xwp/wp-foo-bar/projects/1 149 | [execution]: https://github.com/xwp/wp-foo-bar/projects/2 150 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | mysql: 6 | image: mysql:5 7 | volumes: 8 | - ./bin/local-dev/mysql:/var/lib/mysql 9 | - ./bin/local-dev/wordpress/config/mysql/wptests.sql:/docker-entrypoint-initdb.d/wptests.sql 10 | restart: always 11 | ports: 12 | - 3306:3306 13 | environment: 14 | MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} 15 | MYSQL_DATABASE: ${MYSQL_DATABASE} 16 | MYSQL_USER: ${MYSQL_USER} 17 | MYSQL_PASSWORD: ${MYSQL_PASSWORD} 18 | 19 | wordpress: 20 | image: ghcr.io/xwp/wp-foo-bar-wordpress:${WP_VERSION} 21 | build: 22 | context: ./bin/local-dev/wordpress 23 | args: 24 | WP_VERSION: ${WP_VERSION} 25 | depends_on: 26 | - mysql 27 | ports: 28 | - 8088:80 29 | volumes: 30 | - ./bin/local-dev/wordpress/html:/var/www/html 31 | - .:/var/www/html/wp-content/plugins/foo-bar 32 | restart: always 33 | environment: 34 | WORDPRESS_DEBUG: 1 35 | WORDPRESS_DB_USER: ${WP_DB_USER} 36 | WORDPRESS_DB_PASSWORD: ${WP_DB_PASSWORD} 37 | 38 | cli: 39 | image: wordpress:cli 40 | user: xfs 41 | volumes: 42 | - ./bin/local-dev/wordpress/html:/var/www/html 43 | - .:/var/www/html/wp-content/plugins/foo-bar 44 | depends_on: 45 | - mysql 46 | - wordpress 47 | command: tail -f /dev/null 48 | environment: 49 | WORDPRESS_DEBUG: 1 50 | WORDPRESS_DB_USER: ${WP_DB_USER} 51 | WORDPRESS_DB_PASSWORD: ${WP_DB_PASSWORD} 52 | 53 | mailhog: 54 | image: mailhog/mailhog 55 | ports: 56 | - 1025:1025 57 | - 8025:8025 58 | -------------------------------------------------------------------------------- /foo-bar.php: -------------------------------------------------------------------------------- 1 | =' ) ) { 34 | require_once __DIR__ . '/instance.php'; 35 | } else { 36 | if ( defined( 'WP_CLI' ) ) { 37 | WP_CLI::warning( _foo_bar_php_version_text() ); 38 | } else { 39 | add_action( 'admin_notices', '_foo_bar_php_version_error' ); 40 | } 41 | } 42 | 43 | /** 44 | * Admin notice for incompatible versions of PHP. 45 | */ 46 | function _foo_bar_php_version_error() { 47 | printf( '

%s

', esc_html( _foo_bar_php_version_text() ) ); 48 | } 49 | 50 | /** 51 | * String describing the minimum PHP version. 52 | * 53 | * @return string 54 | */ 55 | function _foo_bar_php_version_text() { 56 | return esc_html__( 'Foo Bar plugin error: Your version of PHP is too old to run this plugin. You must be running PHP 5.6.20 or higher.', 'foo-bar' ); 57 | } 58 | -------------------------------------------------------------------------------- /init-plugin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: ./wp-foo-bar/init-plugin.sh 3 | 4 | # Check for valid plugin name. 5 | function valid_name () { 6 | valid="^[A-Z][A-Za-z0-9]*( [A-Z][A-Za-z0-9]*)*$" 7 | 8 | if [[ ! "$1" =~ $valid ]]; then 9 | return 1 10 | fi 11 | 12 | return 0 13 | } 14 | 15 | echo 16 | echo "Hello, "$USER"." 17 | echo 18 | echo "This script will automatically generate a new plugin based on the scaffolding." 19 | echo "The way it works is you enter a plugin name like 'Hello World' and the script " 20 | echo "will create a directory 'hello-world' in the current working directory, or one " 21 | echo "directory up if called from the plugin root, all while performing substitutions " 22 | echo "on the 'wp-foo-bar' scaffolding plugin." 23 | echo 24 | 25 | echo -n "Enter your plugin name and press [ENTER]: " 26 | read name 27 | 28 | # Validate plugin name. 29 | if ! valid_name "$name"; then 30 | echo "Malformed name '$name'. Please use title case words separated by spaces. No hyphens. For example, 'Hello World'." 31 | echo 32 | echo -n "Enter a valid plugin name and press [ENTER]: " 33 | read name 34 | 35 | if ! valid_name "$name"; then 36 | echo 37 | echo "The name you entered is invalid, rage quitting." 38 | exit 1 39 | fi 40 | fi 41 | 42 | slug="$( echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/ /-/g' )" 43 | prefix="$( echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/ /_/g' )" 44 | namespace="$( echo "$name" | sed 's/ //g' )" 45 | class="$( echo "$name" | sed 's/ /_/g' )" 46 | repo="$slug" 47 | 48 | echo 49 | echo "The Organization name will be converted to lowercase for use in the repository " 50 | echo "path (i.e. XWP becomes xwp)." 51 | echo -n "Enter your GitHub organization name, and press [ENTER]: " 52 | read org 53 | 54 | org_lower="$( echo "$org" | tr '[:upper:]' '[:lower:]' )" 55 | 56 | echo 57 | echo -n "Do you want to prepend 'wp-' to your repository name? [Y/N]: " 58 | read prepend 59 | 60 | if [[ "$prepend" != Y ]] && [[ "$prepend" != y ]]; then 61 | echo 62 | echo -n "Do you want to append '-wp' to your repository name? [Y/N]: " 63 | read append 64 | 65 | if [[ "$append" == Y ]] || [[ "$append" == y ]]; then 66 | repo="${slug}-wp" 67 | fi 68 | else 69 | repo="wp-${slug}" 70 | fi 71 | 72 | echo 73 | echo -n "Do you want to make the initial commit? [Y/N]: " 74 | read commit 75 | 76 | if [[ "$commit" == Y ]] || [[ "$commit" == y ]]; then 77 | echo 78 | echo -n "Do you want to push the plugin to your GitHub repository? [Y/N]: " 79 | read push 80 | fi 81 | 82 | echo 83 | echo -n "Do you want to install the dependencies in the new plugin? [Y/N]: " 84 | read deps 85 | 86 | echo 87 | 88 | cwd="$(pwd)" 89 | cd "$(dirname "$0")" 90 | src_repo_path="$(pwd)" 91 | cd "$cwd" 92 | 93 | if [[ -e $( basename "$0" ) ]]; then 94 | echo 95 | echo "Moving up one directory outside of 'wp-foo-bar'" 96 | cd .. 97 | fi 98 | 99 | if [[ -e "$slug" ]]; then 100 | echo 101 | echo "The $slug directory already exists" 102 | exit 1 103 | fi 104 | 105 | echo 106 | 107 | git clone "$src_repo_path" "$repo" 108 | 109 | cd "$repo" 110 | 111 | git mv foo-bar.php "$slug.php" 112 | git mv tests/phpunit/class-test-foo-bar.php "tests/phpunit/class-test-$slug.php" 113 | 114 | git grep -lz "xwp%2Fwp-foo-bar" | xargs -0 sed -i '' -e "s|xwp%2Fwp-foo-bar|$org_lower%2F$repo|g" 115 | git grep -lz "xwp/wp-foo-bar" | xargs -0 sed -i '' -e "s|xwp/wp-foo-bar|$org_lower/$repo|g" 116 | git grep -lz "wp-foo-bar" | xargs -0 sed -i '' -e "s/wp-foo-bar/$repo/g" 117 | git grep -lz "Foo Bar" | xargs -0 sed -i '' -e "s/Foo Bar/$name/g" 118 | git grep -lz "foo-bar" | xargs -0 sed -i '' -e "s/foo-bar/$slug/g" 119 | git grep -lz "foo_bar" | xargs -0 sed -i '' -e "s/foo_bar/$prefix/g" 120 | git grep -lz "FooBar" | xargs -0 sed -i '' -e "s/FooBar/$namespace/g" 121 | git grep -lz "Foo_Bar" | xargs -0 sed -i '' -e "s/Foo_Bar/$class/g" 122 | 123 | # Clean slate. 124 | rm -rf .git 125 | rm -rf node_modules 126 | rm -rf vendor 127 | rm -f init-plugin.sh 128 | rm -f composer.lock 129 | rm -f package-lock.json 130 | 131 | # Setup Git. 132 | git init 133 | git add . 134 | git remote add origin "git@github.com:$org_lower/$repo.git" 135 | 136 | # Install dependencies. 137 | if [[ "$deps" == Y ]] || [[ "$deps" == y ]]; then 138 | npm install 139 | fi 140 | 141 | # Commit and push change. 142 | if [[ "$commit" == Y ]] || [[ "$commit" == y ]]; then 143 | git commit -m "Initial commit" 144 | 145 | if [[ "$push" == Y ]] || [[ "$push" == y ]]; then 146 | git push -u origin master 147 | else 148 | echo 149 | echo "Push changes to GitHub with the following command:" 150 | echo "cd $(pwd) && git push -u origin master" 151 | fi 152 | else 153 | echo 154 | echo "Commit and push changes to GitHub with the following command:" 155 | echo "cd $(pwd) && git commit -m \"Initial commit\" && git push -u origin master" 156 | fi 157 | 158 | echo 159 | echo "Plugin is located at:" 160 | pwd 161 | -------------------------------------------------------------------------------- /instance.php: -------------------------------------------------------------------------------- 1 | =10", 22 | "npm": ">=6.9" 23 | }, 24 | "dependencies": {}, 25 | "devDependencies": { 26 | "@babel/cli": "7.15.4", 27 | "@babel/core": "7.15.5", 28 | "@babel/plugin-proposal-optional-chaining": "7.14.5", 29 | "@babel/polyfill": "7.12.1", 30 | "@testing-library/react": "11.2.2", 31 | "@wordpress/block-editor": "5.3.3", 32 | "@wordpress/blocks": "6.25.2", 33 | "@wordpress/browserslist-config": "2.7.0", 34 | "@wordpress/dom": "2.18.0", 35 | "@wordpress/dom-ready": "2.13.2", 36 | "@wordpress/e2e-test-utils": "4.16.1", 37 | "@wordpress/eslint-plugin": "7.4.0", 38 | "@wordpress/i18n": "3.20.0", 39 | "@wordpress/jest-preset-default": "6.6.0", 40 | "@wordpress/jest-puppeteer-axe": "1.10.0", 41 | "@wordpress/postcss-themes": "2.6.0", 42 | "@wordpress/scripts": "12.6.1", 43 | "@wordpress/server-side-render": "1.21.3", 44 | "@wordpress/url": "2.22.2", 45 | "accessible-autocomplete": "2.0.3", 46 | "acorn": "8.5.0", 47 | "autoprefixer": "10.3.6", 48 | "babel-eslint": "10.1.0", 49 | "babel-jest": "26.6.3", 50 | "babel-loader": "8.2.2", 51 | "babel-plugin-istanbul": "6.0.0", 52 | "babel-plugin-transform-require-context": "0.1.1", 53 | "browserslist": "4.17.0", 54 | "coveralls": "3.1.1", 55 | "cross-env": "7.0.3", 56 | "css-loader": "5.2.7", 57 | "cssnano": "4.1.11", 58 | "dotenv": "8.6.0", 59 | "eslint": "7.32.0", 60 | "eslint-plugin-eslint-comments": "3.2.0", 61 | "eslint-plugin-import": "2.24.2", 62 | "eslint-plugin-jest": "24.4.0", 63 | "eslint-plugin-prettier": "3.4.1", 64 | "eslint-plugin-react": "7.25.1", 65 | "eslint-utils": "2.1.0", 66 | "fs-extra": "9.1.0", 67 | "grunt": "1.4.1", 68 | "grunt-contrib-clean": "2.0.0", 69 | "grunt-contrib-copy": "1.0.0", 70 | "grunt-shell": "3.0.1", 71 | "grunt-wp-deploy": "2.1.2", 72 | "istanbul-lib-coverage": "3.0.0", 73 | "istanbul-lib-report": "3.0.0", 74 | "istanbul-reports": "3.0.2", 75 | "jest-puppeteer-istanbul": "0.5.3", 76 | "jest-silent-reporter": "0.3.0", 77 | "mini-css-extract-plugin": "1.2.1", 78 | "minimist": "1.2.5", 79 | "npm-run-all": "4.1.5", 80 | "null-loader": "4.0.1", 81 | "optimize-css-assets-webpack-plugin": "5.0.8", 82 | "postcss": "8.3.6", 83 | "postcss-color-function": "4.1.0", 84 | "postcss-import": "13.0.0", 85 | "postcss-loader": "4.3.0", 86 | "postcss-nested": "4.2.3", 87 | "postcss-preset-env": "6.7.0", 88 | "prettier": "npm:wp-prettier@^2.0.4", 89 | "puppeteer": "npm:puppeteer-core@3.0.0", 90 | "react": "17.0.2", 91 | "react-dom": "17.0.2", 92 | "rtlcss": "2.6.1", 93 | "rtlcss-webpack-plugin": "4.0.6", 94 | "semver": "7.3.5", 95 | "source-map-loader": "1.1.2", 96 | "svg-inline-loader": "0.8.2", 97 | "terser-webpack-plugin": "^4.2.3", 98 | "typescript": "4.4.3", 99 | "webpack": "4.46.0", 100 | "webpack-cli": "4.8.0", 101 | "webpackbar": "4.0.0" 102 | }, 103 | "scripts": { 104 | "build": "npm-run-all build:*", 105 | "build:prepare": "grunt clean", 106 | "build:js": "wp-scripts build", 107 | "build:run": "grunt build", 108 | "build:zip": "grunt create-build-zip", 109 | "dev": "wp-scripts start", 110 | "docker": "docker-compose run --rm -u 1000 --workdir=/var/www/html/wp-content/plugins/foo-bar wordpress", 111 | "env:logs": "bin/logs.sh", 112 | "env:start": "bin/start.sh", 113 | "env:stop": "bin/stop.sh", 114 | "format": "npm-run-all --parallel format:*", 115 | "format:css": "npm run lint:css -- --fix", 116 | "format:js": "wp-scripts format-js", 117 | "format:php": "composer format", 118 | "lint": "npm-run-all --parallel lint:*", 119 | "lint:css": "wp-scripts lint-style", 120 | "lint:js": "wp-scripts lint-js", 121 | "lint:php": "composer lint", 122 | "postinstall": "composer install", 123 | "readme": "composer readme", 124 | "test": "npm-run-all --parallel test:js test:e2e test:php", 125 | "test-with-coverage": "npm-run-all --parallel test:js:coverage test:e2e:coverage test:php:coverage", 126 | "test:e2e": "WP_BASE_URL=http://localhost:8088 wp-scripts test-e2e --config=tests/e2e/jest.config.js", 127 | "test:e2e:help": "npm run test:e2e -- --help", 128 | "test:e2e:watch": "npm run test:e2e -- --watch", 129 | "test:e2e:interactive": "npm run test:e2e -- --puppeteer-interactive", 130 | "test:e2e:ci": "npm run test:e2e -- --runInBand", 131 | "test:e2e:coverage": "npm run test:e2e -- --coverage --coverageDirectory=tests/coverage/e2e", 132 | "test:e2e:coveralls": "npm run test:e2e:coverage -- --runInBand --coverageReporters=json && npm run test:js:coverage-merge -- --no-js --reporter=text-lcov | coveralls", 133 | "test:js": "wp-scripts test-unit-js tests/js", 134 | "test:js:help": "wp-scripts test-unit-js --help", 135 | "test:js:watch": "npm run test:js -- --watch", 136 | "test:js:coverage": "wp-scripts test-unit-js --coverage --coverageDirectory=tests/coverage/js", 137 | "test:js:coveralls": "npm run test:js:coverage && coveralls < tests/coverage/js/lcov.info", 138 | "test:js:coverage-all": "run-s \"test:js:coverage -- --coverageReporters=json {@}\" \"test:e2e:coverage -- --coverageReporters=json {@}\" test:js:coverage-merge --", 139 | "test:js:coverage-merge": "node tests/merge-coverage.js --reporter=lcov", 140 | "test:php": "composer test", 141 | "test:php:coverage": "composer test-coverage", 142 | "wp": "docker-compose exec -u xfs cli", 143 | "xdebug:status": "bin/xdebug.sh status", 144 | "xdebug:on": "bin/xdebug.sh on", 145 | "xdebug:off": "bin/xdebug.sh off" 146 | }, 147 | "npmPackageJsonLintConfig": { 148 | "extends": "@wordpress/npm-package-json-lint-config", 149 | "rules": { 150 | "require-version": "off" 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /php/baz-bar/class-sample.php: -------------------------------------------------------------------------------- 1 | plugin = $plugin; 33 | } 34 | 35 | /** 36 | * Initiate the class. 37 | * 38 | * @access public 39 | */ 40 | public function init() { 41 | $this->plugin->add_doc_hooks( $this ); 42 | } 43 | 44 | /** 45 | * Demonstrate doc hooks. 46 | * 47 | * @filter body_class, 99, 1 48 | * 49 | * @param array $classes Body classes. 50 | * 51 | * @return array 52 | */ 53 | public function body_class( $classes ) { 54 | return array_merge( $classes, [ 'custom-class-name' ] ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /php/class-exception.php: -------------------------------------------------------------------------------- 1 | locate_plugin(); 72 | $this->slug = $location['dir_basename']; 73 | $this->dir_path = $location['dir_path']; 74 | $this->dir_url = $location['dir_url']; 75 | spl_autoload_register( [ $this, 'autoload' ] ); 76 | $this->add_doc_hooks(); 77 | } 78 | 79 | /** 80 | * Plugin_Base destructor. 81 | */ 82 | public function __destruct() { 83 | $this->remove_doc_hooks(); 84 | } 85 | 86 | /** 87 | * Get reflection object for this class. 88 | * 89 | * @return \ReflectionObject 90 | */ 91 | public function get_object_reflection() { 92 | static $reflection; 93 | if ( empty( $reflection ) ) { 94 | // @codeCoverageIgnoreStart 95 | $reflection = new \ReflectionObject( $this ); 96 | // @codeCoverageIgnoreEnd 97 | } 98 | 99 | return $reflection; 100 | } 101 | 102 | /** 103 | * Autoload for classes that are in the same namespace as $this. 104 | * 105 | * @codeCoverageIgnore 106 | * 107 | * @param string $class Class name. 108 | * 109 | * @return void 110 | */ 111 | public function autoload( $class ) { 112 | if ( ! isset( $this->autoload_matches_cache[ $class ] ) ) { 113 | if ( ! preg_match( '/^(?P.+)\\\\(?P[^\\\\]+)$/', $class, $matches ) ) { 114 | $matches = false; 115 | } 116 | 117 | $this->autoload_matches_cache[ $class ] = $matches; 118 | } else { 119 | $matches = $this->autoload_matches_cache[ $class ]; 120 | } 121 | 122 | if ( empty( $matches ) ) { 123 | return; 124 | } 125 | 126 | $namespace = $this->get_object_reflection()->getNamespaceName(); 127 | 128 | if ( strpos( $matches['namespace'], $namespace ) === false ) { 129 | return; 130 | } 131 | 132 | $class_name = $matches['class']; 133 | $class_path = \trailingslashit( $this->dir_path ); 134 | 135 | if ( $this->autoload_class_dir ) { 136 | $class_path .= \trailingslashit( $this->autoload_class_dir ); 137 | 138 | $sub_path = str_replace( $namespace . '\\', '', $matches['namespace'] ); 139 | if ( ! empty( $sub_path ) && 'FooBar' !== $sub_path ) { 140 | $class_path .= str_replace( '\\-', '/', strtolower( preg_replace( '/(?get_object_reflection()->getFileName(); 160 | 161 | // Convert to forward-slashes as needed for URLs. 162 | if ( '/' !== \DIRECTORY_SEPARATOR ) { 163 | // @codeCoverageIgnoreStart 164 | $file_name = str_replace( \DIRECTORY_SEPARATOR, '/', $file_name ); 165 | // @codeCoverageIgnoreEnd 166 | } 167 | 168 | $plugin_dir = dirname( dirname( $file_name ) ); 169 | $plugin_path = $this->relative_path( $plugin_dir, basename( content_url() ), '/' ); 170 | 171 | $dir_url = content_url( trailingslashit( $plugin_path ) ); 172 | $dir_path = $plugin_dir; 173 | $dir_basename = basename( $plugin_dir ); 174 | 175 | return compact( 'dir_url', 'dir_path', 'dir_basename' ); 176 | } 177 | 178 | /** 179 | * Relative Path 180 | * 181 | * Returns a relative path from a specified starting position of a full path 182 | * 183 | * @param string $path The full path to start with. 184 | * @param string $start The directory after which to start creating the relative path. 185 | * @param string $sep The directory separator. 186 | * 187 | * @return string 188 | */ 189 | public function relative_path( $path, $start, $sep ) { 190 | $path = explode( $sep, untrailingslashit( $path ) ); 191 | if ( count( $path ) > 0 ) { 192 | foreach ( $path as $p ) { 193 | array_shift( $path ); 194 | if ( $p === $start ) { 195 | break; 196 | } 197 | } 198 | } 199 | 200 | return implode( $sep, $path ); 201 | } 202 | 203 | /** 204 | * Get the public URL to the asset file. 205 | * 206 | * @param string $path_relative Path relative to this plugin directory root. 207 | * 208 | * @return string The URL to the asset. 209 | */ 210 | public function asset_url( $path_relative ) { 211 | return $this->dir_url . $path_relative; 212 | } 213 | 214 | /** 215 | * Call trigger_error() if not on VIP production. 216 | * 217 | * @param string $message Warning message. 218 | * @param int $code Warning code. 219 | */ 220 | public function trigger_warning( $message, $code = \E_USER_WARNING ) { 221 | if ( ! $this->is_wpcom_vip_prod() ) { 222 | // phpcs:disable 223 | trigger_error( esc_html( get_class( $this ) . ': ' . $message ), $code ); 224 | // phpcs:enable 225 | } 226 | } 227 | 228 | /** 229 | * Return whether we're on WordPress.com VIP production. 230 | * 231 | * @return bool 232 | */ 233 | public function is_wpcom_vip_prod() { 234 | return ( defined( '\WPCOM_IS_VIP_ENV' ) && \WPCOM_IS_VIP_ENV ); 235 | } 236 | 237 | /** 238 | * Is WP debug mode enabled. 239 | * 240 | * @return boolean 241 | */ 242 | public function is_debug() { 243 | return ( defined( '\WP_DEBUG' ) && \WP_DEBUG ); 244 | } 245 | 246 | /** 247 | * Is WP script debug mode enabled. 248 | * 249 | * @return boolean 250 | */ 251 | public function is_script_debug() { 252 | return ( defined( '\SCRIPT_DEBUG' ) && \SCRIPT_DEBUG ); 253 | } 254 | 255 | /** 256 | * Return the current version of the plugin. 257 | * 258 | * @return mixed 259 | */ 260 | public function version() { 261 | $args = [ 262 | 'Version' => 'Version', 263 | ]; 264 | $meta = get_file_data( $this->dir_path . '/foo-bar.php', $args ); 265 | 266 | return isset( $meta['Version'] ) ? $meta['Version'] : time(); 267 | } 268 | 269 | /** 270 | * Sync the plugin version with the asset version. 271 | * 272 | * @return string 273 | */ 274 | public function asset_version() { 275 | if ( $this->is_debug() || $this->is_script_debug() ) { 276 | return time(); 277 | } 278 | 279 | return $this->version(); 280 | } 281 | 282 | /** 283 | * Hooks a function on to a specific filter. 284 | * 285 | * @param string $name The hook name. 286 | * @param array $callback The class object and method. 287 | * @param array $args An array with priority and arg_count. 288 | * 289 | * @return mixed 290 | */ 291 | public function add_filter( 292 | $name, 293 | $callback, 294 | $args = [ 295 | 'priority' => 10, 296 | 'arg_count' => PHP_INT_MAX, 297 | ] 298 | ) { 299 | return $this->add_hook( 'filter', $name, $callback, $args ); 300 | } 301 | 302 | /** 303 | * Hooks a function on to a specific action. 304 | * 305 | * @param string $name The hook name. 306 | * @param array $callback The class object and method. 307 | * @param array $args An array with priority and arg_count. 308 | * 309 | * @return mixed 310 | */ 311 | public function add_action( 312 | $name, 313 | $callback, 314 | $args = [ 315 | 'priority' => 10, 316 | 'arg_count' => PHP_INT_MAX, 317 | ] 318 | ) { 319 | return $this->add_hook( 'action', $name, $callback, $args ); 320 | } 321 | 322 | /** 323 | * Hooks a function on to a specific action/filter. 324 | * 325 | * @param string $type The hook type. Options are action/filter. 326 | * @param string $name The hook name. 327 | * @param array $callback The class object and method. 328 | * @param array $args An array with priority and arg_count. 329 | * 330 | * @return mixed 331 | */ 332 | protected function add_hook( $type, $name, $callback, $args = [] ) { 333 | $priority = isset( $args['priority'] ) ? $args['priority'] : 10; 334 | $arg_count = isset( $args['arg_count'] ) ? $args['arg_count'] : PHP_INT_MAX; 335 | $fn = sprintf( '\add_%s', $type ); 336 | $retval = \call_user_func( $fn, $name, $callback, $priority, $arg_count ); 337 | 338 | return $retval; 339 | } 340 | 341 | /** 342 | * Add actions/filters from the methods of a class based on DocBlocks. 343 | * 344 | * @param object $object The class object. 345 | */ 346 | public function add_doc_hooks( $object = null ) { 347 | if ( is_null( $object ) ) { 348 | $object = $this; 349 | } 350 | $class_name = get_class( $object ); 351 | if ( isset( $this->_called_doc_hooks[ $class_name ] ) ) { 352 | $notice = sprintf( 'The add_doc_hooks method was already called on %s. Note that the Plugin_Base constructor automatically calls this method.', $class_name ); 353 | if ( ! $this->is_wpcom_vip_prod() ) { 354 | // phpcs:disable 355 | trigger_error( esc_html( $notice ), \E_USER_NOTICE ); 356 | // phpcs:enable 357 | } 358 | 359 | return; 360 | } 361 | $this->_called_doc_hooks[ $class_name ] = true; 362 | 363 | $reflector = new \ReflectionObject( $object ); 364 | foreach ( $reflector->getMethods() as $method ) { 365 | $doc = $method->getDocComment(); 366 | $arg_count = $method->getNumberOfParameters(); 367 | if ( preg_match_all( '#\* @(?Pfilter|action)\s+(?P[a-z0-9\-\._/=]+)(?:,\s+(?P\-?[0-9]+))?#', $doc, $matches, PREG_SET_ORDER ) ) { 368 | foreach ( $matches as $match ) { 369 | $type = $match['type']; 370 | $name = $match['name']; 371 | $priority = empty( $match['priority'] ) ? 10 : intval( $match['priority'] ); 372 | $callback = [ $object, $method->getName() ]; 373 | call_user_func( [ $this, "add_{$type}" ], $name, $callback, compact( 'priority', 'arg_count' ) ); 374 | } 375 | } 376 | } 377 | } 378 | 379 | /** 380 | * Removes the added DocBlock hooks. 381 | * 382 | * @param object $object The class object. 383 | */ 384 | public function remove_doc_hooks( $object = null ) { 385 | if ( is_null( $object ) ) { 386 | $object = $this; 387 | } 388 | $class_name = get_class( $object ); 389 | 390 | $reflector = new \ReflectionObject( $object ); 391 | foreach ( $reflector->getMethods() as $method ) { 392 | $doc = $method->getDocComment(); 393 | if ( preg_match_all( '#\* @(?Pfilter|action)\s+(?P[a-z0-9\-\._/=]+)(?:,\s+(?P\-?[0-9]+))?#', $doc, $matches, PREG_SET_ORDER ) ) { 394 | foreach ( $matches as $match ) { 395 | $type = $match['type']; 396 | $name = $match['name']; 397 | $priority = empty( $match['priority'] ) ? 10 : intval( $match['priority'] ); 398 | $callback = [ $object, $method->getName() ]; 399 | call_user_func( "remove_{$type}", $name, $callback, $priority ); 400 | } 401 | } 402 | } 403 | unset( $this->_called_doc_hooks[ $class_name ] ); 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /php/class-plugin.php: -------------------------------------------------------------------------------- 1 | config = apply_filters( 'foo_bar_plugin_config', $this->config, $this ); 35 | 36 | $this->sample = new Sample( $this ); 37 | $this->sample->init(); 38 | } 39 | 40 | /** 41 | * Load Gutenberg assets. 42 | * 43 | * @action enqueue_block_editor_assets 44 | */ 45 | public function enqueue_block_editor_assets() { 46 | wp_enqueue_script( 47 | 'wp-foo-bar-block-editor-js', 48 | $this->asset_url( 'assets/js/block-editor.js' ), 49 | [ 50 | 'lodash', 51 | 'react', 52 | 'wp-block-editor', 53 | ], 54 | $this->asset_version(), 55 | false 56 | ); 57 | 58 | wp_enqueue_style( 59 | 'wp-foo-bar-block-editor-css', 60 | $this->asset_url( 'assets/css/block-editor-compiled.css' ), 61 | [], 62 | $this->asset_version() 63 | ); 64 | } 65 | 66 | /** 67 | * Enqueue front-end styles and scripts. 68 | * 69 | * @action wp_enqueue_scripts 70 | */ 71 | public function enqueue_front_end_assets() { 72 | wp_enqueue_script( 73 | 'wp-foo-bar-front-end-js', 74 | $this->asset_url( 'assets/js/front-end.js' ), 75 | [], 76 | $this->asset_version(), 77 | true 78 | ); 79 | 80 | wp_enqueue_style( 81 | 'wp-foo-bar-front-end-css', 82 | $this->asset_url( 'assets/css/front-end-compiled.css' ), 83 | [], 84 | $this->asset_version() 85 | ); 86 | } 87 | 88 | /** 89 | * Register Customizer scripts. 90 | * 91 | * @action wp_default_scripts, 11 92 | * 93 | * @param \WP_Scripts $wp_scripts Instance of \WP_Scripts. 94 | */ 95 | public function register_scripts( \WP_Scripts $wp_scripts ) {} 96 | 97 | /** 98 | * Register Customizer styles. 99 | * 100 | * @action wp_default_styles, 11 101 | * 102 | * @param \WP_Styles $wp_styles Instance of \WP_Styles. 103 | */ 104 | public function register_styles( \WP_Styles $wp_styles ) {} 105 | } 106 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | . 30 | 31 | */assets/* 32 | */bin/* 33 | */node_modules/* 34 | */tests/coverage/* 35 | */vendor/* 36 | 37 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | ./tests/phpunit/ 13 | 14 | 15 | 16 | 17 | 18 | ./php 19 | foo-bar.php 20 | instance.php 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require( '@wordpress/postcss-themes' )( { 4 | defaults: { 5 | primary: '#007cba', 6 | secondary: '#11a0d2', 7 | toggle: '#11a0d2', 8 | button: '#007cba', 9 | outlines: '#007cba', 10 | }, 11 | themes: { 12 | 'admin-color-light': { 13 | primary: '#007cba', 14 | secondary: '#c75726', 15 | toggle: '#11a0d2', 16 | button: '#007cba', 17 | outlines: '#007cba', 18 | }, 19 | 'admin-color-blue': { 20 | primary: '#82b4cb', 21 | secondary: '#d9ab59', 22 | toggle: '#82b4cb', 23 | button: '#d9ab59', 24 | outlines: '#417e9B', 25 | }, 26 | 'admin-color-coffee': { 27 | primary: '#c2a68c', 28 | secondary: '#9fa47b', 29 | toggle: '#c2a68c', 30 | button: '#c2a68c', 31 | outlines: '#59524c', 32 | }, 33 | 'admin-color-ectoplasm': { 34 | primary: '#a7b656', 35 | secondary: '#c77430', 36 | toggle: '#a7b656', 37 | button: '#a7b656', 38 | outlines: '#523f6d', 39 | }, 40 | 'admin-color-midnight': { 41 | primary: '#e14d43', 42 | secondary: '#77a6b9', 43 | toggle: '#77a6b9', 44 | button: '#e14d43', 45 | outlines: '#497b8d', 46 | }, 47 | 'admin-color-ocean': { 48 | primary: '#a3b9a2', 49 | secondary: '#a89d8a', 50 | toggle: '#a3b9a2', 51 | button: '#a3b9a2', 52 | outlines: '#5e7d5e', 53 | }, 54 | 'admin-color-sunrise': { 55 | primary: '#d1864a', 56 | secondary: '#c8b03c', 57 | toggle: '#c8b03c', 58 | button: '#d1864a', 59 | outlines: '#837425', 60 | }, 61 | }, 62 | } ), 63 | require( 'postcss-color-function' ), 64 | require( 'postcss-import' ), 65 | require( 'postcss-nested' ), 66 | require( 'postcss-preset-env' )( { 67 | stage: 0, 68 | preserve: false, // Omit pre-polyfilled CSS. 69 | features: { 70 | 'nesting-rules': false, // Uses postcss-nesting which doesn't behave like Sass. 71 | 'custom-properties': { 72 | preserve: true, // Do not remove :root selector. 73 | }, 74 | }, 75 | autoprefixer: { 76 | grid: true, 77 | }, 78 | } ), 79 | ], 80 | }; 81 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Foo Bar Plugin for WordPress 3 | 4 | ![Banner](wp-assets/banner-1544x500.png) 5 | WordPress plugin template for extending Gutenberg at XWP. 6 | 7 | **Contributors:** [xwp](https://profiles.wordpress.org/xwp) 8 | **Requires at least:** 5.0 9 | **Tested up to:** 5.3.2 10 | **Stable tag:** 0.0.1 11 | **License:** [GPLv2 or later](http://www.gnu.org/licenses/gpl-2.0.html) 12 | 13 | [![version](https://badge.fury.io/gh/xwp%2Fwp-foo-bar.svg)](https://badge.fury.io/gh/xwp%2Fwp-foo-bar) [![Build Status](https://travis-ci.com/xwp/wp-foo-bar.svg?branch=develop)](https://travis-ci.com/xwp/wp-foo-bar) [![Coverage Status](https://coveralls.io/repos/xwp/wp-foo-bar/badge.svg?branch=develop)](https://coveralls.io/github/xwp/wp-foo-bar) [![Built with Grunt](https://gruntjs.com/cdn/builtwith.svg)](http://gruntjs.com) 14 | 15 | ## Description ## 16 | 17 | **Scaffolding** 18 | 19 | Use the [`init-plugin.sh`](init-plugin.sh) bash script to scaffold a new plugin. The script will enter an interactive shell on your host machine and copy this plugin while making necessary string replacements: 20 | 21 | ```bash 22 | ./init-plugin.sh 23 | ``` 24 | 25 | The `init-plugin.sh` script will be removed from the generated plugin. You should also update your `readme.txt` and add/change any of the config files your project may need, read more about your options in the [`xwp/wp-dev-lib/readme.md`](https://github.com/xwp/wp-dev-lib) file. 26 | 27 | **Coveralls Pro** 28 | 29 | To use Coveralls Pro with your private repository you will need to change the `service_name` inside `.coveralls.yml` to `travis-pro`, and add the `COVERALLS_REPO_TOKEN` to the settings in Travis CI. If you don't want to use Coveralls then you will need to delete the `.coveralls.yml` and remove `npm run test:js:coveralls` step from the script section in the `.travis.yml` file. 30 | 31 | **Adding Classes** 32 | 33 | When adding a new class you should instantiate it in `Plugin::init` and inject `Plugin` as a dependency. There is a `Sample` class inside the `php` directory that demonstrates this behavior and how doc hooks work. 34 | 35 | ## Installation ## 36 | 37 | 1. Upload the folder to the `/wp-content/plugins/` directory. 38 | 2. Activate the plugin through the 'Plugins' menu in WordPress. 39 | 40 | ## Frequently Asked Questions ## 41 | 42 | ### A question that someone might have ### 43 | An answer to that question. 44 | 45 | 46 | ## Getting Started ## 47 | 48 | If you are a developer, we encourage you to [follow along](https://github.com/xwp/wp-foo-bar) or [contribute](https://github.com/xwp/wp-foo-bar/contributing.md) to the development of this plugin on GitHub. 49 | 50 | ## Screenshots ## 51 | 52 | ### Look at this demo photo! 53 | 54 | ![Look at this demo photo!](wp-assets/screenshot-1.png) 55 | 56 | ## Changelog ## 57 | 58 | For the plugin’s changelog, please see [the Releases page on GitHub](https://github.com/xwp/wp-foo-bar/releases). 59 | 60 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === Foo Bar === 2 | Contributors: xwp 3 | Requires at least: 5.0 4 | Tested up to: 5.3.2 5 | Stable tag: 0.0.1 6 | License: GPLv2 or later 7 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 8 | 9 | WordPress plugin template for extending Gutenberg at XWP. 10 | 11 | == Description == 12 | 13 | **Scaffolding** 14 | 15 | Use the [`init-plugin.sh`](init-plugin.sh) bash script to scaffold a new plugin. The script will enter an interactive shell on your host machine and copy this plugin while making necessary string replacements: 16 | 17 | ```bash 18 | ./init-plugin.sh 19 | ``` 20 | 21 | The `init-plugin.sh` script will be removed from the generated plugin. You should also update your `readme.txt` and add/change any of the config files your project may need, read more about your options in the [`xwp/wp-dev-lib/readme.md`](https://github.com/xwp/wp-dev-lib) file. 22 | 23 | **Coveralls Pro** 24 | 25 | To use Coveralls Pro with your private repository you will need to change the `service_name` inside `.coveralls.yml` to `travis-pro`, and add the `COVERALLS_REPO_TOKEN` to the settings in Travis CI. If you don't want to use Coveralls then you will need to delete the `.coveralls.yml` and remove `npm run test:js:coveralls` step from the script section in the `.travis.yml` file. 26 | 27 | **Adding Classes** 28 | 29 | When adding a new class you should instantiate it in `Plugin::init` and inject `Plugin` as a dependency. There is a `Sample` class inside the `php` directory that demonstrates this behavior and how doc hooks work. 30 | 31 | == Installation == 32 | 33 | 1. Upload the folder to the `/wp-content/plugins/` directory. 34 | 2. Activate the plugin through the 'Plugins' menu in WordPress. 35 | 36 | == Frequently Asked Questions == 37 | 38 | = A question that someone might have = 39 | 40 | An answer to that question. 41 | 42 | == Getting Started == 43 | 44 | If you are a developer, we encourage you to [follow along](https://github.com/xwp/wp-foo-bar) or [contribute](https://github.com/xwp/wp-foo-bar/contributing.md) to the development of this plugin on GitHub. 45 | 46 | == Screenshots == 47 | 48 | 1. Look at this demo photo! 49 | 50 | == Changelog == 51 | 52 | For the plugin’s changelog, please see [the Releases page on GitHub](https://github.com/xwp/wp-foo-bar/releases). 53 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":preserveSemverRanges" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | } 28 | */ 29 | const OBSERVED_CONSOLE_MESSAGE_TYPES = { 30 | warning: 'warn', 31 | error: 'error', 32 | }; 33 | 34 | /** 35 | * Array of page event tuples of [ eventName, handler ]. 36 | * 37 | * @type {Array} 38 | */ 39 | const pageEvents = []; 40 | 41 | // The Jest timeout is increased because these tests are a bit slow 42 | jest.setTimeout( PUPPETEER_TIMEOUT || 100000 ); 43 | 44 | // Set default timeout for individual expect-puppeteer assertions. (Default: 500) 45 | setDefaultOptions( { timeout: EXPECT_PUPPETEER_TIMEOUT || 500 } ); 46 | 47 | /** 48 | * Adds an event listener to the page to handle additions of page event 49 | * handlers, to assure that they are removed at test teardown. 50 | */ 51 | function capturePageEventsForTearDown() { 52 | page.on( 'newListener', ( eventName, listener ) => { 53 | pageEvents.push( [ eventName, listener ] ); 54 | } ); 55 | } 56 | 57 | /** 58 | * Removes all bound page event handlers. 59 | */ 60 | function removePageEvents() { 61 | pageEvents.forEach( ( [ eventName, handler ] ) => { 62 | page.removeListener( eventName, handler ); 63 | } ); 64 | } 65 | 66 | /** 67 | * Adds a page event handler to emit uncaught exception to process if one of 68 | * the observed console logging types is encountered. 69 | */ 70 | function observeConsoleLogging() { 71 | page.on( 'console', message => { 72 | const type = message.type(); 73 | if ( ! OBSERVED_CONSOLE_MESSAGE_TYPES.hasOwnProperty( type ) ) { 74 | return; 75 | } 76 | 77 | let text = message.text(); 78 | 79 | // styled-components warns about dynamically created components. 80 | // @todo Fix issues. 81 | if ( text.includes( ' has been created dynamically.' ) ) { 82 | return; 83 | } 84 | 85 | const logFunction = OBSERVED_CONSOLE_MESSAGE_TYPES[ type ]; 86 | 87 | // As of Puppeteer 1.6.1, `message.text()` wrongly returns an object of 88 | // type JSHandle for error logging, instead of the expected string. 89 | // 90 | // See: https://github.com/GoogleChrome/puppeteer/issues/3397 91 | // 92 | // The recommendation there to asynchronously resolve the error value 93 | // upon a console event may be prone to a race condition with the test 94 | // completion, leaving a possibility of an error not being surfaced 95 | // correctly. Instead, the logic here synchronously inspects the 96 | // internal object shape of the JSHandle to find the error text. If it 97 | // cannot be found, the default text value is used instead. 98 | text = message.args()?.[ 0 ]?._remoteObject?.description || text; 99 | 100 | // Disable reason: We intentionally bubble up the console message 101 | // which, unless the test explicitly anticipates the logging via 102 | // @wordpress/jest-console matchers, will cause the intended test 103 | // failure. 104 | 105 | // eslint-disable-next-line no-console 106 | console[ logFunction ]( text ); 107 | } ); 108 | } 109 | 110 | /** 111 | * Runs Axe tests when the block editor is found on the current page. 112 | * 113 | * @return {?Promise} Promise resolving once Axe texts are finished. 114 | */ 115 | async function runAxeTestsForBlockEditor() { 116 | if ( ! ( await page.$( '.block-editor' ) ) ) { 117 | return; 118 | } 119 | 120 | await expect( page ).toPassAxeTests( { 121 | // Temporary disabled rules to enable initial integration. 122 | // See: https://github.com/WordPress/gutenberg/pull/15018. 123 | disabledRules: [ 124 | 'aria-allowed-role', 125 | 'aria-hidden-focus', 126 | 'aria-input-field-name', 127 | 'aria-valid-attr-value', 128 | 'button-name', 129 | 'color-contrast', 130 | 'dlitem', 131 | 'duplicate-id', 132 | 'label', 133 | 'link-name', 134 | 'listitem', 135 | 'page-has-heading-one', 136 | 'region', 137 | ], 138 | exclude: [ 139 | // Ignores elements created by metaboxes. 140 | '.edit-post-layout__metaboxes', 141 | // Ignores elements created by TinyMCE. 142 | '.mce-container', 143 | ], 144 | } ); 145 | } 146 | 147 | /** 148 | * Before every test suite run, delete all content created by the test. This ensures 149 | * other posts/comments/etc. aren't dirtying tests and tests don't depend on 150 | * each other's side-effects. 151 | */ 152 | // eslint-disable-next-line jest/require-top-level-describe 153 | beforeAll( async () => { 154 | capturePageEventsForTearDown(); 155 | enablePageDialogAccept(); 156 | observeConsoleLogging(); 157 | // 15inch screen. 158 | await setBrowserViewport( { 159 | width: 1680, 160 | height: 948, 161 | } ); 162 | await page.setDefaultNavigationTimeout( 10000 ); 163 | await page.setDefaultTimeout( 10000 ); 164 | } ); 165 | 166 | // eslint-disable-next-line jest/require-top-level-describe 167 | afterEach( async () => { 168 | await runAxeTestsForBlockEditor(); 169 | // 15inch screen. 170 | await setBrowserViewport( { 171 | width: 1680, 172 | height: 948, 173 | } ); 174 | } ); 175 | 176 | // eslint-disable-next-line jest/require-top-level-describe 177 | afterAll( () => { 178 | removePageEvents(); 179 | } ); 180 | -------------------------------------------------------------------------------- /tests/e2e/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | rootDir: '../../', 4 | ...require( '@wordpress/scripts/config/jest-e2e.config' ), 5 | transform: { 6 | '^.+\\.[jt]sx?$': 7 | '/node_modules/@wordpress/scripts/config/babel-transform', 8 | }, 9 | transformIgnorePatterns: [ 'node_modules' ], 10 | setupFilesAfterEnv: [ 11 | '/tests/e2e/config/bootstrap.js', 12 | '@wordpress/jest-puppeteer-axe', 13 | 'expect-puppeteer', 14 | 'jest-puppeteer-istanbul/lib/setup', 15 | ], 16 | testPathIgnorePatterns: [ 17 | '/.git', 18 | '/node_modules', 19 | '/bin', 20 | '/build', 21 | '/tests/coverage', 22 | '/tests/js', 23 | '/vendor', 24 | ], 25 | collectCoverageFrom: [ '/assets/src/**/*.js' ], 26 | reporters: [ 'default', 'jest-puppeteer-istanbul/lib/reporter' ], 27 | }; 28 | -------------------------------------------------------------------------------- /tests/e2e/specs/block-editor/blocks/hello-world/index.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | import { createNewPost } from '@wordpress/e2e-test-utils'; 5 | 6 | /** 7 | * Internal dependencies. 8 | */ 9 | import { insertBlock } from '../../../../utils'; 10 | 11 | describe( 'blocks: foo-bar/hello-world', () => { 12 | beforeEach( async () => { 13 | await createNewPost( {} ); 14 | } ); 15 | 16 | it( 'should be inserted', async () => { 17 | await insertBlock( 'Hello World' ); 18 | 19 | // Check if block was inserted 20 | expect( 21 | await page.$( '[data-type="foo-bar/hello-world"]' ) 22 | ).not.toBeNull(); 23 | } ); 24 | } ); 25 | -------------------------------------------------------------------------------- /tests/e2e/utils/index.js: -------------------------------------------------------------------------------- 1 | export { insertBlock } from './insert-block'; 2 | -------------------------------------------------------------------------------- /tests/e2e/utils/insert-block.js: -------------------------------------------------------------------------------- 1 | /* global page */ 2 | 3 | /** 4 | * Internal dependencies 5 | */ 6 | import { searchForBlock } from './search-for-block'; 7 | 8 | /** 9 | * Opens the inserter, searches for the given term, then selects the first 10 | * result that appears. 11 | * 12 | * @param {string} searchTerm The text to search the inserter for. 13 | */ 14 | export async function insertBlock( searchTerm ) { 15 | await searchForBlock( searchTerm ); 16 | const insertButton = ( 17 | await page.$x( `//button//span[contains(text(), '${ searchTerm }')]` ) 18 | )[ 0 ]; 19 | await insertButton.click(); 20 | } 21 | -------------------------------------------------------------------------------- /tests/e2e/utils/open-global-block-inserter.js: -------------------------------------------------------------------------------- 1 | /* global page */ 2 | 3 | /** 4 | * Opens the global block inserter. 5 | */ 6 | export async function openGlobalBlockInserter() { 7 | if ( ! ( await isGlobalInserterOpen() ) ) { 8 | await toggleGlobalBlockInserter(); 9 | 10 | // Waiting here is necessary because sometimes the inserter takes more time to 11 | // render than Puppeteer takes to complete the 'click' action 12 | await page.waitForSelector( '.block-editor-inserter__toggle' ); 13 | } 14 | } 15 | 16 | export async function closeGlobalBlockInserter() { 17 | if ( await isGlobalInserterOpen() ) { 18 | await toggleGlobalBlockInserter(); 19 | } 20 | } 21 | 22 | async function isGlobalInserterOpen() { 23 | return await page.evaluate( () => { 24 | return !! document.querySelector( 25 | '.edit-post-header-toolbar__inserter-toggle.is-pressed' 26 | ); 27 | } ); 28 | } 29 | 30 | async function toggleGlobalBlockInserter() { 31 | await page.click( '.edit-post-header-toolbar__inserter-toggle' ); 32 | } 33 | -------------------------------------------------------------------------------- /tests/e2e/utils/search-for-block.js: -------------------------------------------------------------------------------- 1 | /* global page */ 2 | 3 | /** 4 | * WordPress dependencies 5 | */ 6 | import { pressKeyWithModifier } from '@wordpress/e2e-test-utils'; 7 | 8 | /** 9 | * Internal dependencies 10 | */ 11 | import { openGlobalBlockInserter } from './open-global-block-inserter'; 12 | 13 | /** 14 | * Search for block in the global inserter 15 | * 16 | * @param {string} searchTerm The text to search the inserter for. 17 | */ 18 | export async function searchForBlock( searchTerm ) { 19 | await openGlobalBlockInserter(); 20 | await page.focus( '.block-editor-inserter__search' ); 21 | await pressKeyWithModifier( 'primary', 'a' ); 22 | await page.keyboard.type( searchTerm ); 23 | } 24 | -------------------------------------------------------------------------------- /tests/js/block-editor/blocks/hello-world/index.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import { render } from '@testing-library/react'; 5 | 6 | /** 7 | * Internal dependencies 8 | */ 9 | import { 10 | Edit, 11 | Save, 12 | } from '../../../../../assets/src/block-editor/blocks/hello-world'; 13 | 14 | describe( 'blocks: foo-bar/hello-world', () => { 15 | describe( 'Edit', () => { 16 | it( 'should equal Hello Editor', () => { 17 | render( ); 18 | expect( document.querySelector( 'h2' ).textContent ).toStrictEqual( 19 | 'Hello Editor' 20 | ); 21 | } ); 22 | } ); 23 | 24 | describe( 'Save', () => { 25 | it( 'should equal Hello Website', () => { 26 | render( ); 27 | expect( document.querySelector( 'h2' ).textContent ).toStrictEqual( 28 | 'Hello Website' 29 | ); 30 | } ); 31 | } ); 32 | } ); 33 | -------------------------------------------------------------------------------- /tests/js/block-editor/helpers/index.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import { registerBlocks } from '../../../../assets/src/block-editor/helpers'; 5 | 6 | const mockRegisterBlockType = jest.fn(); 7 | jest.mock( '@wordpress/blocks', () => { 8 | return { 9 | registerBlockType: ( ...args ) => mockRegisterBlockType( ...args ), 10 | }; 11 | } ); 12 | 13 | const blocks = { 14 | biz: { 15 | name: 'foo-bar/biz', 16 | settings: { 17 | title: 'Biz', 18 | }, 19 | }, 20 | baz: { 21 | name: 'foo-bar/baz', 22 | settings: { 23 | title: 'Baz', 24 | }, 25 | }, 26 | }; 27 | 28 | const mockBlocks = { 29 | './blocks/biz/index.js': { 30 | ...blocks.biz, 31 | }, 32 | './blocks/baz/index.js': { 33 | ...blocks.baz, 34 | }, 35 | }; 36 | 37 | // Mocks the return value of the require.context() Webpack function. 38 | const mockBlocksToRegister = modulePath => { 39 | return mockBlocks[ modulePath ]; 40 | }; 41 | 42 | mockBlocksToRegister.keys = () => { 43 | return Object.keys( mockBlocks ); 44 | }; 45 | 46 | describe( 'helpers: registerBlocks', () => { 47 | it( 'should register all of the expected blocks, with the expected arguments', () => { 48 | registerBlocks( mockBlocksToRegister ); 49 | 50 | expect( mockRegisterBlockType ).toHaveBeenNthCalledWith( 51 | 1, 52 | blocks.biz.name, 53 | blocks.biz.settings 54 | ); 55 | 56 | expect( mockRegisterBlockType ).toHaveBeenNthCalledWith( 57 | 2, 58 | blocks.baz.name, 59 | blocks.baz.settings 60 | ); 61 | } ); 62 | } ); 63 | -------------------------------------------------------------------------------- /tests/js/classic-editor/index.spec.js: -------------------------------------------------------------------------------- 1 | import { add } from '../../../assets/src/classic-editor/'; 2 | 3 | describe( 'demo: add', () => { 4 | it( 'should equal the sum of two numbers', () => { 5 | expect( add( 2, 2 ) ).toStrictEqual( 4 ); 6 | } ); 7 | } ); 8 | -------------------------------------------------------------------------------- /tests/js/customizer/customize-controls.spec.js: -------------------------------------------------------------------------------- 1 | import { subtract } from '../../../assets/src/customizer/customize-controls'; 2 | 3 | describe( 'demo: subtract', () => { 4 | it( 'should equal the difference of two numbers', () => { 5 | expect( subtract( 2, 2 ) ).toStrictEqual( 0 ); 6 | } ); 7 | } ); 8 | -------------------------------------------------------------------------------- /tests/js/customizer/customize-preview.spec.js: -------------------------------------------------------------------------------- 1 | import { multiply } from '../../../assets/src/customizer/customize-preview'; 2 | 3 | describe( 'demo: multiply', () => { 4 | it( 'should equal the product of two numbers', () => { 5 | expect( multiply( 2, 2 ) ).toStrictEqual( 4 ); 6 | } ); 7 | } ); 8 | -------------------------------------------------------------------------------- /tests/merge-coverage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Merge JS and e2e coverage reports. 3 | */ 4 | 5 | const fs = require( 'fs-extra' ); 6 | const istanbulReports = require( 'istanbul-reports' ); 7 | const libReport = require( 'istanbul-lib-report' ); 8 | const istanbulCoverage = require( 'istanbul-lib-coverage' ); 9 | const args = require( 'minimist' )( process.argv.slice( 2 ) ); 10 | 11 | /* [ Configuration ] */ 12 | const rootDir = 'tests/coverage'; 13 | const reportOut = 'tests/coverage/js-e2e'; 14 | 15 | const mergeAllReports = ( coverageMap, reports ) => { 16 | if ( Array.isArray( reports ) === false ) { 17 | return; 18 | } 19 | 20 | reports.forEach( reportFile => { 21 | const coverageReport = fs.readJSONSync( reportFile ); 22 | coverageMap.merge( coverageReport ); 23 | } ); 24 | }; 25 | 26 | const generateReport = ( coverageMap, type ) => { 27 | // create a context for report generation 28 | const context = libReport.createContext( { 29 | dir: reportOut, 30 | defaultSummarizer: 'nested', 31 | coverageMap, 32 | } ); 33 | 34 | const reportType = Array.isArray( type ) ? type.pop() : type; 35 | 36 | // create an instance of the relevant report class 37 | const report = istanbulReports.create( reportType ); 38 | 39 | // call execute to synchronously create and write the report to disk 40 | report.execute( context ); 41 | 42 | // show the report text summary in console only if the reporter is not a text report. 43 | if ( ! reportType.includes( 'text' ) ) { 44 | const text = istanbulReports.create( 'text' ); 45 | 46 | text.execute( context ); 47 | } 48 | }; 49 | 50 | async function main() { 51 | const coverageMap = istanbulCoverage.createCoverageMap( {} ); 52 | 53 | const reports = [ rootDir + '/e2e/coverage-final.json' ]; 54 | 55 | if ( false !== args.js ) { 56 | reports.push( rootDir + '/js/coverage-final.json' ); 57 | } 58 | 59 | if ( Array.isArray( reports ) ) { 60 | mergeAllReports( coverageMap, reports ); 61 | generateReport( coverageMap, args.reporter || 'lcov' ); 62 | } 63 | } 64 | 65 | main().catch( err => { 66 | console.error( err ); // eslint-disable-line 67 | process.exit( 1 ); 68 | } ); 69 | -------------------------------------------------------------------------------- /tests/phpunit/class-test-foo-bar.php: -------------------------------------------------------------------------------- 1 | assertContains( '
', $buffer ); 27 | } 28 | 29 | /** 30 | * Test _foo_bar_php_version_text(). 31 | * 32 | * @see _foo_bar_php_version_text() 33 | */ 34 | public function test_foo_bar_php_version_text() { 35 | $this->assertContains( 'Foo Bar plugin error:', _foo_bar_php_version_text() ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/phpunit/php/baz-bar/class-test-sample.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf( Sample::class, \FooBar\get_plugin_instance()->sample ); 24 | } 25 | 26 | /** 27 | * Test init. 28 | * 29 | * @see Sample::init() 30 | */ 31 | public function test_init() { 32 | $sample = new Sample( new Plugin() ); 33 | $sample->init(); 34 | $this->assertEquals( 99, has_filter( 'body_class', [ $sample, 'body_class' ] ) ); 35 | } 36 | 37 | /** 38 | * Test for body_class() method. 39 | * 40 | * @see Sample::body_class() 41 | */ 42 | public function test_body_class() { 43 | $this->assertEquals( [ 'custom-class-name' ], \FooBar\get_plugin_instance()->sample->body_class( [] ) ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/phpunit/php/class-test-plugin-base.php: -------------------------------------------------------------------------------- 1 | plugin = get_plugin_instance(); 37 | $this->basename = basename( dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) ); 38 | } 39 | 40 | /** 41 | * Test locate_plugin. 42 | * 43 | * @see Plugin_Base::locate_plugin() 44 | */ 45 | public function test_locate_plugin() { 46 | $location = $this->plugin->locate_plugin(); 47 | 48 | 49 | $this->assertEquals( $this->basename, $location['dir_basename'] ); 50 | $this->assertEquals( WP_CONTENT_DIR . '/plugins/' . $this->basename, $location['dir_path'] ); 51 | $this->assertEquals( content_url( '/plugins/' . $this->basename . '/' ), $location['dir_url'] ); 52 | } 53 | 54 | /** 55 | * Test relative_path. 56 | * 57 | * @see Plugin_Base::relative_path() 58 | */ 59 | public function test_relative_path() { 60 | $this->assertEquals( 'plugins/foo-bar', $this->plugin->relative_path( '/var/www/html/wp-content/plugins/foo-bar', 'wp-content', '/' ) ); 61 | $this->assertEquals( 'themes/twentysixteen/plugins/foo-bar', $this->plugin->relative_path( '/var/www/html/wp-content/themes/twentysixteen/plugins/foo-bar', 'wp-content', '/' ) ); 62 | } 63 | 64 | /** 65 | * Test asset_url. 66 | * 67 | * @see Plugin_Base::asset_url() 68 | */ 69 | public function test_asset_url() { 70 | $this->assertContains( '/plugins/' . $this->basename . '/editor.js', $this->plugin->asset_url( 'editor.js' ) ); 71 | } 72 | 73 | /** 74 | * Tests for trigger_warning(). 75 | * 76 | * @see Plugin_Base::trigger_warning() 77 | */ 78 | public function test_trigger_warning() { 79 | $obj = $this; 80 | // phpcs:disable 81 | set_error_handler( 82 | function( $errno, $errstr ) use ( $obj ) { 83 | $obj->assertEquals( 'FooBar\Plugin: Param is 0!', $errstr ); 84 | $obj->assertEquals( \E_USER_WARNING, $errno ); 85 | } 86 | ); 87 | // phpcs:enable 88 | $this->plugin->trigger_warning( 'Param is 0!', \E_USER_WARNING ); 89 | restore_error_handler(); 90 | } 91 | 92 | /** 93 | * Test asset_version(). 94 | * 95 | * @see Plugin_Base::asset_version() 96 | */ 97 | public function test_asset_version() { 98 | $mock = $this->getMockBuilder( 'FooBar\Plugin' ) 99 | ->setMethods( 100 | [ 101 | 'is_debug', 102 | 'is_script_debug', 103 | ] 104 | ) 105 | ->getMock(); 106 | 107 | $mock->method( 'is_debug' ) 108 | ->willReturn( false ); 109 | 110 | $mock->method( 'is_script_debug' ) 111 | ->willReturn( false ); 112 | 113 | $this->assertFalse( $mock->is_debug() ); 114 | $this->assertFalse( $mock->is_script_debug() ); 115 | $this->assertEquals( $mock->version(), $mock->asset_version() ); 116 | 117 | $mock = $this->getMockBuilder( 'FooBar\Plugin' ) 118 | ->setMethods( 119 | [ 120 | 'is_debug', 121 | ] 122 | ) 123 | ->getMock(); 124 | 125 | $mock->method( 'is_debug' ) 126 | ->willReturn( true ); 127 | 128 | $this->assertNotEquals( $mock->version(), $mock->asset_version() ); 129 | } 130 | 131 | /** 132 | * Test is_wpcom_vip_prod(). 133 | * 134 | * @see Plugin_Base::is_wpcom_vip_prod() 135 | */ 136 | public function test_is_wpcom_vip_prod() { 137 | if ( ! defined( 'WPCOM_IS_VIP_ENV' ) ) { 138 | $this->assertFalse( $this->plugin->is_wpcom_vip_prod() ); 139 | define( 'WPCOM_IS_VIP_ENV', true ); 140 | } 141 | $this->assertEquals( \WPCOM_IS_VIP_ENV, $this->plugin->is_wpcom_vip_prod() ); 142 | } 143 | 144 | /** 145 | * Test is_debug(). 146 | * 147 | * @see Plugin_Base::is_debug() 148 | */ 149 | public function test_is_debug() { 150 | $this->assertEquals( \WP_DEBUG, $this->plugin->is_debug() ); 151 | } 152 | 153 | /** 154 | * Test is_script_debug(). 155 | * 156 | * @see Plugin_Base::is_script_debug() 157 | */ 158 | public function test_is_script_debug() { 159 | $this->assertEquals( \SCRIPT_DEBUG, $this->plugin->is_script_debug() ); 160 | } 161 | 162 | /** 163 | * Test add_doc_hooks(). 164 | * 165 | * @see Plugin_Base::add_doc_hooks() 166 | */ 167 | public function test_add_doc_hooks() { 168 | $object = new Test_Doc_Hooks(); 169 | 170 | $this->assertEquals( 10, has_action( 'init', array( $object, 'init_action' ) ) ); 171 | $this->assertEquals( 10, has_action( 'the_content', array( $object, 'the_content_filter' ) ) ); 172 | $object->remove_doc_hooks( $object ); 173 | } 174 | 175 | /** 176 | * Test add_doc_hooks(). 177 | * 178 | * @see Plugin_Base::add_doc_hooks() 179 | */ 180 | public function test_add_doc_hooks_error() { 181 | $mock = $this->getMockBuilder( 'FooBar\Plugin' ) 182 | ->setMethods( array( 'is_wpcom_vip_prod' ) ) 183 | ->getMock(); 184 | 185 | $mock->method( 'is_wpcom_vip_prod' ) 186 | ->willReturn( false ); 187 | 188 | $this->assertFalse( $mock->is_wpcom_vip_prod() ); 189 | 190 | $obj = $this; 191 | // phpcs:disable 192 | set_error_handler( 193 | function( $errno, $errstr ) use ( $obj, $mock ) { 194 | $obj->assertEquals( sprintf( 'The add_doc_hooks method was already called on %s. Note that the Plugin_Base constructor automatically calls this method.', get_class( $mock ) ), $errstr ); 195 | $obj->assertEquals( \E_USER_NOTICE, $errno ); 196 | } 197 | ); 198 | // phpcs:enable 199 | $mock->add_doc_hooks(); 200 | restore_error_handler(); 201 | 202 | $mock->remove_doc_hooks(); 203 | } 204 | 205 | /** 206 | * Test remove_doc_hooks(). 207 | * 208 | * @see Plugin_Base::remove_doc_hooks() 209 | */ 210 | public function test_remove_doc_hooks() { 211 | $object = new Test_Doc_Hooks(); 212 | $this->assertEquals( 10, has_action( 'init', array( $object, 'init_action' ) ) ); 213 | $this->assertEquals( 10, has_action( 'the_content', array( $object, 'the_content_filter' ) ) ); 214 | 215 | $object->remove_doc_hooks( $object ); 216 | $this->assertFalse( has_action( 'init', array( $object, 'init_action' ) ) ); 217 | $this->assertFalse( has_action( 'the_content', array( $object, 'the_content_filter' ) ) ); 218 | } 219 | 220 | /** 221 | * Test __destruct(). 222 | * 223 | * @see Plugin_Base::__destruct() 224 | */ 225 | public function test___destruct() { 226 | $object = new Test_Doc_Hooks(); 227 | $this->assertEquals( 10, has_action( 'init', array( $object, 'init_action' ) ) ); 228 | $this->assertEquals( 10, has_action( 'the_content', array( $object, 'the_content_filter' ) ) ); 229 | 230 | $object->__destruct( $object ); 231 | $this->assertFalse( has_action( 'init', array( $object, 'init_action' ) ) ); 232 | $this->assertFalse( has_action( 'the_content', array( $object, 'the_content_filter' ) ) ); 233 | } 234 | } 235 | 236 | // phpcs:disable 237 | /** 238 | * Test_Doc_Hooks class. 239 | */ 240 | class Test_Doc_Hooks extends Plugin { 241 | 242 | /** 243 | * Load this on the init action hook. 244 | * 245 | * @action init 246 | */ 247 | public function init_action() {} 248 | 249 | /** 250 | * Load this on the the_content filter hook. 251 | * 252 | * @filter the_content 253 | * 254 | * @param string $content The content. 255 | * @return string 256 | */ 257 | public function the_content_filter( $content ) { 258 | return $content; 259 | } 260 | } 261 | // phpcs:enable 262 | -------------------------------------------------------------------------------- /tests/phpunit/php/class-test-plugin.php: -------------------------------------------------------------------------------- 1 | assertEquals( 9, has_action( 'after_setup_theme', [ $plugin, 'init' ] ) ); 25 | $this->assertEquals( 10, has_action( 'enqueue_block_editor_assets', [ $plugin, 'enqueue_block_editor_assets' ] ) ); 26 | $this->assertEquals( 10, has_action( 'wp_enqueue_scripts', [ $plugin, 'enqueue_front_end_assets' ] ) ); 27 | $this->assertEquals( 11, has_action( 'wp_default_scripts', [ $plugin, 'register_scripts' ] ) ); 28 | $this->assertEquals( 11, has_action( 'wp_default_styles', [ $plugin, 'register_styles' ] ) ); 29 | } 30 | 31 | /** 32 | * Test for init() method. 33 | * 34 | * @see Plugin::init() 35 | */ 36 | public function test_init() { 37 | $plugin = get_plugin_instance(); 38 | 39 | add_filter( 'foo_bar_plugin_config', [ $this, 'filter_config' ], 10, 2 ); 40 | $plugin->init(); 41 | 42 | $this->assertInternalType( 'array', $plugin->config ); 43 | $this->assertArrayHasKey( 'foo', $plugin->config ); 44 | $this->assertInstanceOf( Sample::class, $plugin->sample ); 45 | } 46 | 47 | /** 48 | * Test for enqueue_block_editor_assets() method. 49 | * 50 | * @see Plugin::enqueue_block_editor_assets() 51 | */ 52 | public function test_enqueue_block_editor_assets() { 53 | $plugin = get_plugin_instance(); 54 | $plugin->enqueue_block_editor_assets(); 55 | $this->assertTrue( wp_script_is( 'wp-foo-bar-block-editor-js', 'enqueued' ) ); 56 | $this->assertTrue( wp_style_is( 'wp-foo-bar-block-editor-css', 'enqueued' ) ); 57 | } 58 | 59 | /** 60 | * Test for enqueue_front_end_assets() method. 61 | * 62 | * @see Plugin::enqueue_front_end_assets() 63 | */ 64 | public function test_enqueue_front_end_assets() { 65 | $plugin = get_plugin_instance(); 66 | $plugin->enqueue_front_end_assets(); 67 | $this->assertTrue( wp_script_is( 'wp-foo-bar-front-end-js', 'enqueued' ) ); 68 | $this->assertTrue( wp_style_is( 'wp-foo-bar-front-end-css', 'enqueued' ) ); 69 | } 70 | 71 | /** 72 | * Filter to test 'foo_bar_plugin_config'. 73 | * 74 | * @see Plugin::init() 75 | * @param array $config Plugin config. 76 | * @param Plugin_Base $plugin Plugin instance. 77 | * @return array 78 | */ 79 | public function filter_config( $config, $plugin ) { 80 | unset( $config, $plugin ); // Test should actually use these. 81 | return [ 'foo' => 'bar' ]; 82 | } 83 | 84 | /* Put other test functions here... */ 85 | } 86 | -------------------------------------------------------------------------------- /tests/wp-tests-config.php: -------------------------------------------------------------------------------- 1 | ! rule.test.toString().match( '.css' ) 44 | ), 45 | { 46 | test: /\.css$/, 47 | use: [ 48 | // prettier-ignore 49 | MiniCssExtractPlugin.loader, 50 | 'css-loader', 51 | 'postcss-loader', 52 | ], 53 | }, 54 | ], 55 | }, 56 | plugins: [ 57 | // Remove the CleanWebpackPlugin and FixStyleWebpackPlugin plugins from `@wordpress/scripts` due to version conflicts. 58 | ...defaultConfig.plugins.filter( 59 | plugin => 60 | ! [ 'CleanWebpackPlugin', 'FixStyleWebpackPlugin' ].includes( 61 | plugin.constructor.name 62 | ) 63 | ), 64 | new MiniCssExtractPlugin( { 65 | filename: '../css/[name]-compiled.css', 66 | } ), 67 | new RtlCssPlugin( { 68 | filename: '../css/[name]-compiled-rtl.css', 69 | } ), 70 | ], 71 | }; 72 | 73 | const blockEditor = { 74 | ...defaultConfig, 75 | ...sharedConfig, 76 | entry: { 77 | 'block-editor': [ 78 | './assets/src/block-editor/index.js', 79 | './assets/css/src/block-editor.css', 80 | ], 81 | }, 82 | plugins: [ 83 | ...sharedConfig.plugins, 84 | new WebpackBar( { 85 | name: 'Block Editor', 86 | color: '#1773a8', 87 | } ), 88 | ], 89 | }; 90 | 91 | const classicEditor = { 92 | ...defaultConfig, 93 | ...sharedConfig, 94 | entry: { 95 | 'classic-editor': [ 96 | './assets/src/classic-editor/index.js', 97 | './assets/css/src/classic-editor.css', 98 | ], 99 | }, 100 | plugins: [ 101 | ...sharedConfig.plugins, 102 | new WebpackBar( { 103 | name: 'Classic Editor', 104 | color: '#dc3232', 105 | } ), 106 | ], 107 | }; 108 | 109 | const customizer = { 110 | ...defaultConfig, 111 | ...sharedConfig, 112 | entry: { 113 | 'customize-controls': [ 114 | './assets/src/customizer/customize-controls.js', 115 | './assets/css/src/customize-controls.css', 116 | ], 117 | 'customize-preview': [ 118 | './assets/src/customizer/customize-preview.js', 119 | './assets/css/src/customize-preview.css', 120 | ], 121 | }, 122 | plugins: [ 123 | ...sharedConfig.plugins, 124 | new WebpackBar( { 125 | name: 'Customizer', 126 | color: '#f27136', 127 | } ), 128 | ], 129 | }; 130 | 131 | const frontEnd = { 132 | ...defaultConfig, 133 | ...sharedConfig, 134 | entry: { 135 | 'front-end': [ 136 | './assets/src/front-end/index.js', 137 | './assets/css/src/front-end.css', 138 | ], 139 | }, 140 | plugins: [ 141 | ...sharedConfig.plugins, 142 | new WebpackBar( { 143 | name: 'Front End', 144 | color: '#36f271', 145 | } ), 146 | ], 147 | }; 148 | 149 | module.exports = [ 150 | // prettier-ignore 151 | blockEditor, 152 | classicEditor, 153 | customizer, 154 | frontEnd, 155 | ]; 156 | -------------------------------------------------------------------------------- /wp-assets/banner-1544x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwp/wp-foo-bar/a6551d7525693ca6b6afd21600f6e670f32d5c79/wp-assets/banner-1544x500.png -------------------------------------------------------------------------------- /wp-assets/banner-772x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwp/wp-foo-bar/a6551d7525693ca6b6afd21600f6e670f32d5c79/wp-assets/banner-772x250.png -------------------------------------------------------------------------------- /wp-assets/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwp/wp-foo-bar/a6551d7525693ca6b6afd21600f6e670f32d5c79/wp-assets/icon-128x128.png -------------------------------------------------------------------------------- /wp-assets/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwp/wp-foo-bar/a6551d7525693ca6b6afd21600f6e670f32d5c79/wp-assets/icon-256x256.png -------------------------------------------------------------------------------- /wp-assets/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwp/wp-foo-bar/a6551d7525693ca6b6afd21600f6e670f32d5c79/wp-assets/screenshot-1.png --------------------------------------------------------------------------------