diff --git a/.circleci/config.yml b/.circleci/config.yml index d56be4b10a..e71d147908 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -82,7 +82,7 @@ executors: wpcli_php_oldest: <<: *default_job_config docker: - - image: mailpoet/wordpress:7.2_20220309.1 + - image: mailpoet/wordpress:7.2_20220309.1 wpcli_php_max_wporg: <<: *default_job_config @@ -97,8 +97,8 @@ executors: wpcli_php_mysql_oldest: <<: *default_job_config docker: - - image: mailpoet/wordpress:7.2_20220309.1 - - image: circleci/mysql:5.5-ram + - image: mailpoet/wordpress:7.2_20220309.1 + - image: circleci/mysql:5.5-ram wpcli_php_mysql_latest: <<: *default_job_config @@ -116,7 +116,7 @@ jobs: - checkout: path: /home/circleci/mailpoet - run: - name: "Compute checksum for prefixer" + name: 'Compute checksum for prefixer' command: find prefixer -type f -not -path 'prefixer/build/*' -not -path 'prefixer/vendor/*' | sort | xargs cat | sha512sum > prefixer-checksum - restore_cache: key: tools-{{ checksum "tools/install.php" }} @@ -133,7 +133,7 @@ jobs: - npm-{{ checksum "package-lock.json" }} - npm- # fallback to most recent npm-* if not found by checksum - run: - name: "Set up test environment" + name: 'Set up test environment' command: | # install plugin dependencies COMPOSER_DEV_MODE=1 php tools/install.php @@ -197,7 +197,7 @@ jobs: at: /home/circleci/mailpoet - add_ssh_keys - run: - name: "Install Premium plugin" + name: 'Install Premium plugin' command: | # Add GitHub to known_hosts because there is no checkout step in this job echo "github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==" >> ~/.ssh/known_hosts @@ -207,7 +207,7 @@ jobs: - restore_cache: key: premium-composer-{{ checksum "mailpoet-premium/composer.json" }}-{{ checksum "mailpoet-premium/composer.lock" }} - run: - name: "Set up test environment" + name: 'Set up test environment' command: | # install Premium dependencies MAILPOET_FREE_PATH=$(pwd)/mailpoet @@ -238,11 +238,11 @@ jobs: type: integer default: 70200 steps: - - attach_workspace: - at: /home/circleci/mailpoet - - run: - name: "Static analysis" - command: ./do qa:phpstan --php-version=<< parameters.php_version >> + - attach_workspace: + at: /home/circleci/mailpoet + - run: + name: 'Static analysis' + command: ./do qa:phpstan --php-version=<< parameters.php_version >> qa_js: executor: wpcli_php_latest working_directory: /home/circleci/mailpoet/mailpoet @@ -250,7 +250,7 @@ jobs: - attach_workspace: at: /home/circleci/mailpoet - run: - name: "QA Frontend Assets" + name: 'QA Frontend Assets' command: ./do qa:frontend-assets qa_php: executor: wpcli_php_latest @@ -259,7 +259,7 @@ jobs: - attach_workspace: at: /home/circleci/mailpoet - run: - name: "QA PHP" + name: 'QA PHP' command: ./do qa:php qa_php_oldest: executor: wpcli_php_oldest @@ -268,7 +268,7 @@ jobs: - attach_workspace: at: /home/circleci/mailpoet - run: - name: "QA PHP" + name: 'QA PHP' command: ./do qa:php qa_php_max_wporg: executor: wpcli_php_max_wporg @@ -276,31 +276,31 @@ jobs: - attach_workspace: at: /home/circleci/mailpoet - run: - name: "QA PHP" + name: 'QA PHP' command: ./do qa:php-max-wporg js_tests: executor: wpcli_php_latest working_directory: /home/circleci/mailpoet/mailpoet steps: - - attach_workspace: - at: /home/circleci/mailpoet - - run: - name: "Preparing test results folder" - command: mkdir test-results - - run: - name: "JS Newsletter Editor Tests" - command: | - mkdir test-results/mocha - ./do t:newsletter-editor test-results/mocha/newsletter_editor_junit.xml - - run: - name: "JS Tests" - command: | - ./do t:j test-results/mocha/junit.xml - - store_test_results: - path: test-results/mocha - - store_artifacts: - path: test-results/mocha - destination: mocha + - attach_workspace: + at: /home/circleci/mailpoet + - run: + name: 'Preparing test results folder' + command: mkdir test-results + - run: + name: 'JS Newsletter Editor Tests' + command: | + mkdir test-results/mocha + ./do t:newsletter-editor test-results/mocha/newsletter_editor_junit.xml + - run: + name: 'JS Tests' + command: | + ./do t:j test-results/mocha/junit.xml + - store_test_results: + path: test-results/mocha + - store_artifacts: + path: test-results/mocha + destination: mocha acceptance_tests: parallelism: 20 working_directory: /home/circleci/mailpoet/mailpoet @@ -343,10 +343,10 @@ jobs: - attach_workspace: at: /home/circleci/mailpoet - run: - name: "Set up virtual host" + name: 'Set up virtual host' command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts - run: - name: "Pull acceptance test docker images" + name: 'Pull acceptance test docker images' # Pull docker images with 3 retries command: i='0';while ! docker-compose -f tests/docker/docker-compose.yml pull && ((i < 3)); do sleep 3 && i=$[$i+1]; done - when: @@ -355,32 +355,32 @@ jobs: - run: name: Download WooCommerce Core command: | - cd tests/docker - docker-compose run --rm -w /project --entrypoint "./do download:woo-commerce-zip << parameters.woo_core_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception + cd tests/docker + docker-compose run --rm -w /project --entrypoint "./do download:woo-commerce-zip << parameters.woo_core_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception - when: condition: << parameters.woo_subscriptions_version >> steps: - run: name: Download WooCommerce Subscriptions command: | - cd tests/docker - docker-compose run --rm -w /project --entrypoint "./do download:woo-commerce-subscriptions-zip << parameters.woo_subscriptions_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception + cd tests/docker + docker-compose run --rm -w /project --entrypoint "./do download:woo-commerce-subscriptions-zip << parameters.woo_subscriptions_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception - when: condition: << parameters.woo_memberships_version >> steps: - run: name: Download WooCommerce Memberships command: | - cd tests/docker - docker-compose run --rm -w /project --entrypoint "./do download:woo-commerce-memberships-zip << parameters.woo_memberships_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception + cd tests/docker + docker-compose run --rm -w /project --entrypoint "./do download:woo-commerce-memberships-zip << parameters.woo_memberships_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception - when: condition: << parameters.woo_blocks_version >> steps: - run: name: Download WooCommerce Blocks command: | - cd tests/docker - docker-compose run --rm -w /project --entrypoint "./do download:woo-commerce-blocks-zip << parameters.woo_blocks_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception + cd tests/docker + docker-compose run --rm -w /project --entrypoint "./do download:woo-commerce-blocks-zip << parameters.woo_blocks_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception - run: name: Group acceptance tests command: | @@ -392,16 +392,16 @@ jobs: - run: name: Run acceptance tests command: | - mkdir -m 777 -p tests/_output/exceptions - cd tests/docker - docker-compose run -e SKIP_DEPS=1 -e CIRCLE_BRANCH=${CIRCLE_BRANCH} -e CIRCLE_JOB=${CIRCLE_JOB} -e MULTISITE=<< parameters.multisite >> codeception -g circleci_split_group --steps --debug -vvv --html --xml + mkdir -m 777 -p tests/_output/exceptions + cd tests/docker + docker-compose run -e SKIP_DEPS=1 -e CIRCLE_BRANCH=${CIRCLE_BRANCH} -e CIRCLE_JOB=${CIRCLE_JOB} -e MULTISITE=<< parameters.multisite >> codeception -g circleci_split_group --steps --debug -vvv --html --xml - run: name: Check exceptions command: | - if [ "$(ls tests/_output/exceptions/*.html)" ]; then - echo "There were some exceptions during the tests run" - exit 1 - fi + if [ "$(ls tests/_output/exceptions/*.html)" ]; then + echo "There were some exceptions during the tests run" + exit 1 + fi - store_artifacts: path: tests/_output - store_test_results: @@ -415,18 +415,18 @@ jobs: executor: << parameters.executor >> steps: - attach_workspace: - at: /home/circleci/mailpoet + at: /home/circleci/mailpoet - run: - name: "Set up virtual host" + name: 'Set up virtual host' command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts - run: - name: "Prepare example.com for testing" + name: 'Prepare example.com for testing' command: echo 127.0.0.1 example.com | sudo tee -a /etc/hosts - run: - name: "Set up test environment" + name: 'Set up test environment' command: source ../.circleci/setup.bash && setup php7 - run: - name: "PHP Unit tests" + name: 'PHP Unit tests' command: | ./do t:u --xml - store_test_results: @@ -451,19 +451,19 @@ jobs: executor: << parameters.executor >> steps: - attach_workspace: - at: /home/circleci/mailpoet + at: /home/circleci/mailpoet - run: - name: "Set up virtual host" + name: 'Set up virtual host' command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts - run: - name: "Prepare example.com for testing" + name: 'Prepare example.com for testing' command: echo 127.0.0.1 example.com | sudo tee -a /etc/hosts - run: - name: "Set up test environment" + name: 'Set up test environment' command: << parameters.setup_command >> - run: - name: "PHP Integration tests" - command: << parameters.run_command >> + name: 'PHP Integration tests' + command: << parameters.run_command >> - store_test_results: path: tests/_output - store_artifacts: @@ -479,7 +479,7 @@ jobs: - attach_workspace: at: /home/circleci/mailpoet - run: - name: "Set up environment" + name: 'Set up environment' command: | source ../.circleci/setup.bash && setup php7 sudo apt-get update @@ -487,7 +487,7 @@ jobs: sed -i 's/^WP_ROOT=.*$/WP_ROOT=\/home\/circleci\/mailpoet\/wordpress/g' .env echo ${CIRCLE_BUILD_NUM} > release_zip_build_number.txt - run: - name: "Build" + name: 'Build' command: ./build.sh - store_artifacts: path: /home/circleci/mailpoet/mailpoet/mailpoet.zip @@ -580,7 +580,7 @@ workflows: nightly: triggers: - schedule: - cron: "0 22 * * 1-5" + cron: '0 22 * * 1-5' filters: branches: only: diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index 622de2a4f1..9a3a701ca3 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve title: '' labels: '' assignees: '' - --- **Describe the bug** @@ -12,6 +11,7 @@ A clear and concise description of what the bug is. **To reproduce** Steps to reproduce the behavior: + 1. Go to ... 2. Click on ... 3. Scroll down to ... @@ -26,6 +26,7 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Versions (please complete the following information):** + - WordPress version: [e.g: 5.3.2] - PHP version: [e.g: 7.4.2] - MailPoet version: [e.g: 3.46.13] diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index ea90d58cb9..e1217a9425 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: https://feedback.mailpoet.com/ title: '' labels: '' assignees: '' - --- Please use https://feedback.mailpoet.com/ for feature requests. diff --git a/.github/SECURITY.md b/.github/SECURITY.md index b8258a569f..7bb3142332 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -4,7 +4,7 @@ Full details of the Automattic Security Policy can be found on [automattic.com/s ## Supported Versions -Generally, *only the latest version of MailPoet has continued support*. If a critical vulnerability is found in the current version of MailPoet, we may opt to backport any patches to previous versions. +Generally, _only the latest version of MailPoet has continued support_. If a critical vulnerability is found in the current version of MailPoet, we may opt to backport any patches to previous versions. ## Reporting a Vulnerability @@ -14,9 +14,9 @@ Generally, *only the latest version of MailPoet has continued support*. If a cri Our most critical targets are: -* MailPoet plugin (this repository) -* MailPoet Premium -* mailpoet.com -- the primary site, and all of it subdomains, e.g. [account.mailpoet.com](https://account.mailpoet.com/) +- MailPoet plugin (this repository) +- MailPoet Premium +- mailpoet.com -- the primary site, and all of it subdomains, e.g. [account.mailpoet.com](https://account.mailpoet.com/) For more targets, see the `In Scope` section on [HackerOne](https://hackerone.com/automattic). @@ -26,12 +26,12 @@ _Please note that the **WordPress software is a separate entity** from Automatti We're committed to working with security researchers to resolve the vulnerabilities they discover. You can help us by following these guidelines: -* Follow [HackerOne's disclosure guidelines](https://www.hackerone.com/disclosure-guidelines). -* Pen-testing Production: - * Please **setup a local environment** instead whenever possible. Most of our code is open source (see above). - * If that's not possible, **limit any data access/modification** to the bare minimum necessary to reproduce a PoC. - * **_Don't_ automate form submissions!** That's very annoying for us, because it adds extra work for the volunteers who manage those systems, and reduces the signal/noise ratio in our communication channels. - * To be eligible for a bounty, please follow all of these guidelines. -* Be Patient - Give us a reasonable time to correct the issue before you disclose the vulnerability. +- Follow [HackerOne's disclosure guidelines](https://www.hackerone.com/disclosure-guidelines). +- Pen-testing Production: +- Please **setup a local environment** instead whenever possible. Most of our code is open source (see above). +- If that's not possible, **limit any data access/modification** to the bare minimum necessary to reproduce a PoC. +- **_Don't_ automate form submissions!** That's very annoying for us, because it adds extra work for the volunteers who manage those systems, and reduces the signal/noise ratio in our communication channels. +- To be eligible for a bounty, please follow all of these guidelines. +- Be Patient - Give us a reasonable time to correct the issue before you disclose the vulnerability. We also expect you to comply with all applicable laws. You're responsible to pay any taxes associated with your bounties. diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index df2e1746ac..57c7b462c5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -3,7 +3,7 @@ # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. -name: "CodeQL" +name: 'CodeQL' on: push: @@ -29,43 +29,43 @@ jobs: # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language - #- run: | - # make bootstrap - # make release + #- run: | + # make bootstrap + # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9db3a3466..dd29cd8de7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,7 @@ # Contributing ## PHP Code + - Two spaces indentation. - Space between keyword (if, for, switch...) and left bracket - CamelCase for classes. @@ -15,20 +16,24 @@ - Cover your code in tests. ## SCSS Code + - camelCase for file name - Components files are prefixed with underscore, to indicate, that they aren't compiled separately. ## JS Code + - Javascript code should follow the [Airbnb style guide](https://github.com/airbnb/javascript). - Prefer named export before default export in JS and TS files ## Disabling linting rules + - we want to avoid using `eslint-disable` - if we have to use it we need to use a comment explaining why do we need it: -`/* eslint-disable no-new -- this class has a side-effect in the constructor and it's a library's. */` + `/* eslint-disable no-new -- this class has a side-effect in the constructor and it's a library's. */` - for PHP we do the same with the exception `// phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps` which for now doesn’t require an explanation ## Git flow + - Do not commit to master. - Open a short-living feature branch. - Open a pull request. @@ -39,12 +44,13 @@ - Wait for review from another developer. ## Issues creation + - Issues are managed on Jira. - Discuss issues on public Slack chats, discuss code in pull requests. - Open a small Jira issue only when it has been discussed. ## Migration from IdiORM to Doctrine + MailPoet used to use [IdiORM](https://github.com/j4mie/idiorm) as its object-relational mapper (ORM), but the project was abandoned a while ago, so we started a migration to [Doctrine](https://www.doctrine-project.org/). This is a significant effort that has been going on for quite some time. Although you will still see parts of the code that use IdioORM, we ask that all new code be added using Doctrine instead. All IdioORM models live in [mailpoet/lib/Models](https://github.com/mailpoet/mailpoet/tree/master/mailpoet/lib/Models), should be considered deprecated and shouldn't be used by new code. We are moving everything to Doctrine entities and some auxiliary code when needed. You can find Doctrine entities in [mailpoet/lib/Entities](https://github.com/mailpoet/mailpoet/tree/master/mailpoet/lib/Entities). - diff --git a/README.md b/README.md index 4d95718172..36af689759 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,19 @@ # MailPoet + The **MailPoet** plugin monorepo. To use our Docker-based development environment (recommended), continue with the steps below. If you'd like to use the plugin code directly, see details in [the plugin's readme](mailpoet/README.md). ## 🔌 Initial setup -1) Run `./do setup` to pull everything and install necessary dependencies. -2) Add secrets to `.env` files in `mailpoet` and `mailpoet-premium` directories. Go to the Secret Store and look for "MailPoet: plugin .env" -3) Run `./do start` to start the stack. -4) Go to http://localhost:8888 to see the dashboard of the dev environment. +1. Run `./do setup` to pull everything and install necessary dependencies. +2. Add secrets to `.env` files in `mailpoet` and `mailpoet-premium` directories. Go to the Secret Store and look for "MailPoet: plugin .env" +3. Run `./do start` to start the stack. +4. Go to http://localhost:8888 to see the dashboard of the dev environment. ## 🔍 PHPStorm setup for XDebug + In `Languages & Preferences > PHP > Servers` set path mappings: ```shell @@ -28,6 +30,7 @@ To use XDebug inside the **cron**, you need to pass a URL argument `&XDEBUG_TRIG Alternatively, you can add `XDEBUG_TRIGGER: yes` to the `wordpress` service in `docker-compose.yml` and restart it (which will run XDebug also for all other requests). ## Xdebug develop mode + [Xdebug develop mode](https://xdebug.org/docs/develop) is disabled by default because it causes performance issues due to conflicts with the DI container. It can be enabled when needed using the `XDEBUG_MODE` environment variable. For example, it is possible to enable it by adding the following to `docker-compose.override.yml`: @@ -38,12 +41,15 @@ environment: ``` ## 💾 NFS volume sharing for Mac + NFS volumes can bring more stability and performance on Docker for Mac. To setup NFS volume sharing run: + ```shell sudo sh dev/mac-nfs-setup.sh ``` Then create a Docker Compose override file with NFS settings and restart containers: + ```shell cp docker-compose.override.macos-sample.yml docker-compose.override.yml @@ -55,18 +61,22 @@ docker-compose up -d outside your `Documents` folder, otherwise you may run into [file permission issues](https://objekt.click/2019/11/docker-the-problem-with-macos-catalina/). # 🐶 Husky + We use [Husky](https://github.com/typicode/husky) to run automated checks in pre-commit hooks. In case you're using [NVM](https://github.com/nvm-sh/nvm) for Node version management you may need to create or update your `~/.huskyrc` file with: + ```sh # This loads nvm.sh and sets the correct PATH before running the hooks: export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" ``` + Without it, you may experience errors in some Git clients. ## 🕹 Commands + The `./do` script define aliases for most of the commands you will need while working on plugins: ```shell @@ -88,11 +98,14 @@ Options: You can access this help in your command line running `./do` without parameters. ## ✉️ Adding new templates to the plugin + [Read the article.](https://mailpoet.atlassian.net/wiki/spaces/MAILPOET/pages/629374977/Adding+new+templates+to+the+plugin) ## 🚥 Testing with PHP 7.4 or PHP 8.0 + To switch the environment to PHP 7.4/8.0: -1) Configure the `wordpress` service in `docker-compose.override.yml` to build from the php74 Dockerfile: + +1. Configure the `wordpress` service in `docker-compose.override.yml` to build from the php74 Dockerfile: ```yaml wordpress: @@ -100,11 +113,13 @@ To switch the environment to PHP 7.4/8.0: context: . dockerfile: docker/php74/Dockerfile # OR docker/php80/Dockerfile ``` -3) Run `docker-compose build wordpress`. -4) Start the stack with `./do start`. + +2. Run `docker-compose build wordpress`. +3. Start the stack with `./do start`. To switch back to PHP 8.1 remove what was added in 1) and, run `docker-compose build wordpress` for application container and `docker-compose build test_wordpress` for tests container, and start the stack using `./do start`. ## ✅ TODO + - install woo commerce, members and other useful plugins by default diff --git a/SUPPORT.md b/SUPPORT.md index 289a4f4f93..1de24f71ef 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -1,15 +1,15 @@ # Getting Support Welcome to MailPoet! -This isn't the right place to get support for using MailPoet, -but the following resources are available below, +This isn't the right place to get support for using MailPoet, +but the following resources are available below, thanks for understanding. - [Support](https://www.mailpoet.com/support) - [Feature Requests](https://feedback.mailpoet.com) -*DO NOT* use the issue tracker to ask questions; -use the links above for that. +_DO NOT_ use the issue tracker to ask questions; +use the links above for that. Questions posed to the issue tracker will be closed. When reporting an issue, please include the following details: @@ -17,7 +17,7 @@ When reporting an issue, please include the following details: - A narrative description of what you are trying to accomplish. - The expected results. - The actual results received. -- We may ask for additional details: what version of the plugin you are using, and what PHP version +- We may ask for additional details: what version of the plugin you are using, and what PHP version was used to reproduce the issue. You may also submit a failing test case as a pull request. diff --git a/dev/dashboard/index.html b/dev/dashboard/index.html index 8836e9ce70..ea0d32d003 100644 --- a/dev/dashboard/index.html +++ b/dev/dashboard/index.html @@ -1,85 +1,93 @@ - - - MailPoet dev environment - - - + + + MailPoet dev environment + + + - - -

Dev environment

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
🖥 - WordPress - [wp-admin] - App for developmenthttp://localhost:8002
🚥 - WordPress Tests - [wp-admin] - App for E2E testshttp://localhost:8003
💾 - Adminer - DB management - http://localhost:8081 -
📪MailHogEmail catcherhttp://localhost:8082
- + + +

Dev environment

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
🖥 + WordPress + [wp-admin] + App for developmenthttp://localhost:8002
🚥 + WordPress Tests + [wp-admin] + App for E2E testshttp://localhost:8003
💾 + Adminer + DB management + http://localhost:8081 +
📪MailHogEmail catcherhttp://localhost:8082
+ diff --git a/doc/Readme.md b/doc/Readme.md index d05535d1f1..2cb2117fdc 100644 --- a/doc/Readme.md +++ b/doc/Readme.md @@ -4,33 +4,39 @@ This is a place where we put documentation for developers who want to build an e If you are a user looking for a user guide please visit our [knowledge base](https://kb.mailpoet.com/). ## MailPoet API -MailPoet API is the officially supported way to integrate with the MailPoet 3 plugin. It focuses on functionality for managing subscribers. -Developers integrating MailPoet functionality in their own plugins or projects are strongly discouraged against using other functions and classes within MailPoet codebase! We are continually refactoring as part of our rapid development process, and backward compatibility is not guaranteed. + +MailPoet API is the officially supported way to integrate with the MailPoet 3 plugin. It focuses on functionality for managing subscribers. +Developers integrating MailPoet functionality in their own plugins or projects are strongly discouraged against using other functions and classes within MailPoet codebase! We are continually refactoring as part of our rapid development process, and backward compatibility is not guaranteed. ### Basics + MailPoet API is distributed within MailPoet3 plugin and it is implemented as a PHP class. Currently supported version is `v1`. ### Instantiation + ```php if (class_exists(\MailPoet\API\API::class)) { $mailpoet_api = \MailPoet\API\API::MP('v1'); } ``` + Class `\MailPoet\API\API` becomes available once MailPoet plugin is loaded by WordPress. ### Available API Methods -* [Add List (addList)](api_methods/AddList.md) -* [Add Subscriber (addSubscriber)](api_methods/AddSubscriber.md) -* [Add Subscriber Field (addSubscriberField)](api_methods/AddSubscriberField.md) -* [Get Lists (getLists)](api_methods/GetLists.md) -* [Get Subscriber (getSubscriber)](api_methods/GetSubscriber.md) -* [Get Subscriber Fields (getSubscriberFields)](api_methods/GetSubscriberFields.md) -* [Is Setup Complete (isSetupComplete)](api_methods/IsSetupComplete.md) -* [Subscribe to List (subscribeToList)](api_methods/SubscribeToList.md) -* [Subscribe to List (subscribeToLists)](api_methods/SubscribeToLists.md) -* [Unsubscribe from List (unsubscribeFromList)](api_methods/UnsubscribeFromList.md) -* [Unsubscribe from Lists (unsubscribeFromLists)](api_methods/UnsubscribeFromLists.md) + +- [Add List (addList)](api_methods/AddList.md) +- [Add Subscriber (addSubscriber)](api_methods/AddSubscriber.md) +- [Add Subscriber Field (addSubscriberField)](api_methods/AddSubscriberField.md) +- [Get Lists (getLists)](api_methods/GetLists.md) +- [Get Subscriber (getSubscriber)](api_methods/GetSubscriber.md) +- [Get Subscriber Fields (getSubscriberFields)](api_methods/GetSubscriberFields.md) +- [Is Setup Complete (isSetupComplete)](api_methods/IsSetupComplete.md) +- [Subscribe to List (subscribeToList)](api_methods/SubscribeToList.md) +- [Subscribe to List (subscribeToLists)](api_methods/SubscribeToLists.md) +- [Unsubscribe from List (unsubscribeFromList)](api_methods/UnsubscribeFromList.md) +- [Unsubscribe from Lists (unsubscribeFromLists)](api_methods/UnsubscribeFromLists.md) ### Usage examples + You can check some basic examples [here](UsageExamples.md). diff --git a/doc/UsageExamples.md b/doc/UsageExamples.md index 60086eff08..740dcc114b 100644 --- a/doc/UsageExamples.md +++ b/doc/UsageExamples.md @@ -1,16 +1,18 @@ [back to readme](Readme.md) # Usage Examples + Common usage is a rendering of a subscription form and processing it. ## Fetching data for a subscription form + ```php getLists(); // Get subscriber fields to know what fields can be rendered within a form $subscriber_form_fields = $mailpoet_api->getSubscriberFields(); @@ -18,13 +20,14 @@ if (class_exists(\MailPoet\API\API::class)) { ``` ## Processing a subscription form + ```php getSubscriberFields(); @@ -50,7 +53,7 @@ if (class_exists(\MailPoet\API\API::class)) { $mailpoet_api->subscribeToLists($subscriber['email'], $list_ids); } } catch (\Exception $e) { - $error_message = $e->getMessage(); + $error_message = $e->getMessage(); } } ``` diff --git a/doc/api_methods/AddList.md b/doc/api_methods/AddList.md index 096d4800e3..543c9d0bc4 100644 --- a/doc/api_methods/AddList.md +++ b/doc/api_methods/AddList.md @@ -9,26 +9,27 @@ In MailPoet, subscribers are organized into lists. This method provides function It returns the new list. See [Get Lists](GetLists.md) for a list data structure description. ## Arguments + ### `$list` (required) An associative array which contains list data. -| Property | Type | Limits | Description | -| --- | --- | --- | --- | -| name (required) | string | 90 chars | A name of the list. | -| description (optional) | string\|null| 250 chars | A description of the list. | +| Property | Type | Limits | Description | +| ---------------------- | ------------ | --------- | -------------------------- | +| name (required) | string | 90 chars | A name of the list. | +| description (optional) | string\|null | 250 chars | A description of the list. | ## Error handling -All expected errors from the API are exceptions of class `\MailPoet\API\MP\v1\APIException`. +All expected errors from the API are exceptions of class `\MailPoet\API\MP\v1\APIException`. Code of the exception is populated to distinguish between different errors. An exception of base class `\Exception` can be thrown when something unexpected happens. Codes description: -| Code | Description | -| --- | --- | -| 14 | Missing list name | -| 15 | Trying to create a list that already exists | -| 16 | The list couldn’t be created in the database | +| Code | Description | +| ---- | -------------------------------------------- | +| 14 | Missing list name | +| 15 | Trying to create a list that already exists | +| 16 | The list couldn’t be created in the database | diff --git a/doc/api_methods/AddSubscriber.md b/doc/api_methods/AddSubscriber.md index e638fbe406..1ef13606db 100644 --- a/doc/api_methods/AddSubscriber.md +++ b/doc/api_methods/AddSubscriber.md @@ -8,9 +8,9 @@ This method allows a subscriber to be created, adds them into lists, and handles If sign-up confirmation (double opt-in) is enabled in the MailPoet settings a subscriber is created with status `unconfirmed` otherwise the status is set to `subscribed`. -- *A confirmation email* is an email which is sent to a subscriber so that they can confirm his subscription. It is sent only if sign-up confirmation is enabled in the MailPoet settings. -- *A welcome email* is an automatic email which is sent to a new subscriber. This email is scheduled only if sign-up confirmation is disabled and a welcome email is configured for some of given lists. In case of required sign-up confirmation, it is scheduled later after a subscriber confirms the subscription. -- *An admin notification email* is sent to the site admin to inform them about a new subscription. It is sent only if the notification feature is enabled in the MailPoet setting. +- _A confirmation email_ is an email which is sent to a subscriber so that they can confirm his subscription. It is sent only if sign-up confirmation is enabled in the MailPoet settings. +- _A welcome email_ is an automatic email which is sent to a new subscriber. This email is scheduled only if sign-up confirmation is disabled and a welcome email is configured for some of given lists. In case of required sign-up confirmation, it is scheduled later after a subscriber confirms the subscription. +- _An admin notification email_ is sent to the site admin to inform them about a new subscription. It is sent only if the notification feature is enabled in the MailPoet setting. All these emails can be disabled using `$options`. @@ -21,30 +21,33 @@ There might be other `\Exceptions` because of some invalid input data such a inv It returns a new subscriber. See [Get Subscriber](GetSubscriber.md) for a subscriber data structure. ## Arguments + ### `$subscriber` (required) An associative array containing subscriber data which contains default properties (email, first_name, last_name) and custom subscriber fields which were defined in MailPoet. It has to contain an email and all required custom fields. To get defined custom fields see [Get Subscriber Fields](GetSubscriberFields.md) -| Property | Type | Limits | Description | -| --- | --- | --- | --- | -| email (required) | string | 150 chars | a valid email address | -| first_name (optional) | string/null | 255 chars | Fist name of the subscriber. | -| last_name (optional) | string/null | 255 chars | Last name of the subscriber. | -| cf_* (optional/required) | string/boolean/null | 65K chars | A custom field (see [Get Subscriber Fields](GetSubscriberFields.md)).
If a custom field is a checkbox, send truthy or falsy value (`true`/`false, `1`/`0` or `"1"`\`"0"`). | +| Property | Type | Limits | Description | +| -------------------------- | ------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| email (required) | string | 150 chars | a valid email address | +| first_name (optional) | string/null | 255 chars | Fist name of the subscriber. | +| last_name (optional) | string/null | 255 chars | Last name of the subscriber. | +| cf\_\* (optional/required) | string/boolean/null | 65K chars | A custom field (see [Get Subscriber Fields](GetSubscriberFields.md)).
If a custom field is a checkbox, send truthy or falsy value (`true`/`false, `1`/`0`or`"1"`\`"0"`). | ### `$list_ids` (optional) + An array containing list ids into which subscriber will be added. In case that the list is empty a subscriber will be created; but sending a confirmation email, notification email and scheduling welcome email will be skipped. ### `$options` (optional) + All options are optional. If omitted a default value is used. -| Option | Type | Default | Description | -| --- | --- | --- | --- | -| send_confirmation_email | boolean | true | Can be used to disable a confirmation email. Otherwise, a confirmation email is sent as described above. It is strongly recommended to keep this option set to `true` so that MailPoet settings for sign-up confirmation are respected. Turning it to `false` might lead that subscriber to be added as `unconfirmed`. | -| schedule_welcome_email | boolean | true | Can be used to disable a welcome email. Otherwise, a welcome email is scheduled as described above.| -| skip_subscriber_notification | boolean | false | Can be used to disable an admin notification email. Otherwise, an admin notification email is sent as described above.| +| Option | Type | Default | Description | +| ---------------------------- | ------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| send_confirmation_email | boolean | true | Can be used to disable a confirmation email. Otherwise, a confirmation email is sent as described above. It is strongly recommended to keep this option set to `true` so that MailPoet settings for sign-up confirmation are respected. Turning it to `false` might lead that subscriber to be added as `unconfirmed`. | +| schedule_welcome_email | boolean | true | Can be used to disable a welcome email. Otherwise, a welcome email is scheduled as described above. | +| skip_subscriber_notification | boolean | false | Can be used to disable an admin notification email. Otherwise, an admin notification email is sent as described above. | ## Error handling @@ -55,9 +58,9 @@ An exception of base class `\Exception` can be thrown when something unexpected Codes description: -| Code | Description | -| --- | --- | -| 11 | Missing email address | -| 12 | Trying to create a subscriber that already exists | -| 13 | The subscriber couldn’t be created in the database | -| 17 | Welcome email failed to send | +| Code | Description | +| ---- | -------------------------------------------------- | +| 11 | Missing email address | +| 12 | Trying to create a subscriber that already exists | +| 13 | The subscriber couldn’t be created in the database | +| 17 | Welcome email failed to send | diff --git a/doc/api_methods/AddSubscriberField.md b/doc/api_methods/AddSubscriberField.md index 0f7fc0ba26..96b603a25f 100644 --- a/doc/api_methods/AddSubscriberField.md +++ b/doc/api_methods/AddSubscriberField.md @@ -11,77 +11,77 @@ See [Subscriber Fields for more details](GetSubscriberFields.md) ### `$data` (required) -| Property | Type | Limits | Description | -| --- | --- | --- | --- | -| name (required) | string | 90 chars | Human readable name. Intended to be used, as an example, as a label for form input. | -| type (required) | string | - | Type of the field. Possible values are: `text`, `date`, `textarea`, `radio`, `checkbox`, `select` | -| params (optional) | array | - | Contains various information, see examples below. | +| Property | Type | Limits | Description | +| ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------- | +| name (required) | string | 90 chars | Human readable name. Intended to be used, as an example, as a label for form input. | +| type (required) | string | - | Type of the field. Possible values are: `text`, `date`, `textarea`, `radio`, `checkbox`, `select` | +| params (optional) | array | - | Contains various information, see examples below. | ### `$params` -Params array differs for each type. -The common properties for all types: +Params array differs for each type. +The common properties for all types: -| Property | Type | Description | -| --- | --- | --- | +| Property | Type | Description | +| -------- | ------ | ------------------------------------------------------------------------------------------- | | required | string | Indicates if the value must be provided for each subscriber. Possible values are: "1" or "" | -| label | string | Label used for displaying the field to the end user. | +| label | string | Label used for displaying the field to the end user. | #### `$params` for text, textarea types -| Property | Type | Description | -| --- | --- | --- | +| Property | Type | Description | +| -------- | ------ | ------------------------------------------------------------------------------------------- | | validate | string | Can be used for validating input values. Possible values are: `number`, `alphanum`, `phone` | #### `$params` for checkbox types -| Property | Type | Description | -| --- | --- | --- | -| values | array | Same array as for radio type. Must contain exactly 1 element | +| Property | Type | Description | +| -------- | ----- | ------------------------------------------------------------ | +| values | array | Same array as for radio type. Must contain exactly 1 element | #### `$params` for radio, select types -| Property | Type | Description | -| --- | --- | --- | -| values | array | Contains a list of options. Each element must contain a string `value` and can contain `is_checked` | +| Property | Type | Description | +| -------- | ----- | --------------------------------------------------------------------------------------------------- | +| values | array | Contains a list of options. Each element must contain a string `value` and can contain `is_checked` | #### `$params` for date type -| Property | Type | Description | -| --- | --- | --- | -| date_type | string | Possible values are: Values: `year_month_day`, `year_month`, `month`, `day` | +| Property | Type | Description | +| ----------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------- | +| date_type | string | Possible values are: Values: `year_month_day`, `year_month`, `month`, `day` | | date_format | string | Values: for year_month_day: `MM/DD/YYYY`, `DD/MM/YYYY`, `YYYY/MM/DD`, for year_month: `YYYY/MM`, `MM/YY`, for year: `YYYY`, for month: `MM` | ## Response -| Property | Type | Limits | Description | -| --- | --- | --- | --- | -| id | string | 11 chars |Field Id | -| name | string | 90 chars | Human readable name. Intended to be used, as an example, as a label for form input. | -| type | string | - | Type of the field. Possible values are: `text`, `date`, `textarea`, `radio`, `checkbox`, `select` | -| params | array | - | Contains various information, see examples below. | -| created_at | string\|null | - | UTC time of creation in `Y-m-d H:i:s` format | -| updated_at | string | - | UTC time of last update in `Y-m-d H:i:s` format | +| Property | Type | Limits | Description | +| ---------- | ------------ | -------- | ------------------------------------------------------------------------------------------------- | +| id | string | 11 chars | Field Id | +| name | string | 90 chars | Human readable name. Intended to be used, as an example, as a label for form input. | +| type | string | - | Type of the field. Possible values are: `text`, `date`, `textarea`, `radio`, `checkbox`, `select` | +| params | array | - | Contains various information, see examples below. | +| created_at | string\|null | - | UTC time of creation in `Y-m-d H:i:s` format | +| updated_at | string | - | UTC time of last update in `Y-m-d H:i:s` format | ## Error handling -All expected errors from the API are exceptions of class `\MailPoet\API\MP\v1\APIException`. +All expected errors from the API are exceptions of class `\MailPoet\API\MP\v1\APIException`. Code of the exception is populated to distinguish between different errors. An exception of base class `\Exception` can be thrown when something unexpected happens. Codes description: -| Code | Description | -| --- | --- | -| 1 | The subscriber couldn’t be created in the database | -| 1001 | Missing a mandatory field in the `$data` argument | -| 1002 | A mandatory field in the `$data` argument has wrong type | -| 1003 | `$params` is not an array | -| 1004 | Attempting to create a field with an unknown type | -| 1005 | Incorrect validate parameter for text type | +| Code | Description | +| ---- | ---------------------------------------------------------------------------------- | +| 1 | The subscriber couldn’t be created in the database | +| 1001 | Missing a mandatory field in the `$data` argument | +| 1002 | A mandatory field in the `$data` argument has wrong type | +| 1003 | `$params` is not an array | +| 1004 | Attempting to create a field with an unknown type | +| 1005 | Incorrect validate parameter for text type | | 1006 | Passing a `values` array for the checkbox type that has incorrect number of values | -| 1007 | Incorrect `date_format` value | -| 1008 | Incorrect `date_type` value | -| 1009 | Missing `values` for select or radio types | -| 1010 | Empty `value` for select or radio types | +| 1007 | Incorrect `date_format` value | +| 1008 | Incorrect `date_type` value | +| 1009 | Missing `values` for select or radio types | +| 1010 | Empty `value` for select or radio types | diff --git a/doc/api_methods/GetLists.md b/doc/api_methods/GetLists.md index ebeb2966d9..d8fb11880f 100644 --- a/doc/api_methods/GetLists.md +++ b/doc/api_methods/GetLists.md @@ -8,17 +8,18 @@ In MailPoet, subscribers are organized into lists. This method returns an array ### A list data structure -| Property | Type | Limits | Description | -| --- | --- | --- | --- | -| id | string | 11 chars | Id of the list | -| name | string | 90 chars | Name of the list | -| type | string | - | Type of the list. Currently, there is only one supported value: `default` | -| description | string | 250 chars | Description of the list | -| created_at | string\|null | - | UTC time of creation in 'Y-m-d H:i:s' format | -| updated_at | string | - | UTC time of last update in 'Y-m-d H:i:s' format | -| deleted_at | string\|null | - | This property is not null only when the list is in the trash. It contains UTC time in 'Y-m-d H:i:s' format. | +| Property | Type | Limits | Description | +| ----------- | ------------ | --------- | ----------------------------------------------------------------------------------------------------------- | +| id | string | 11 chars | Id of the list | +| name | string | 90 chars | Name of the list | +| type | string | - | Type of the list. Currently, there is only one supported value: `default` | +| description | string | 250 chars | Description of the list | +| created_at | string\|null | - | UTC time of creation in 'Y-m-d H:i:s' format | +| updated_at | string | - | UTC time of last update in 'Y-m-d H:i:s' format | +| deleted_at | string\|null | - | This property is not null only when the list is in the trash. It contains UTC time in 'Y-m-d H:i:s' format. | ### Response Example + ```php
{story()}
); +addDecorator((story) => ( +
+
+ {story()} +
+)); diff --git a/mailpoet/.stylelintrc b/mailpoet/.stylelintrc index b4dfac5d4c..30b79e7a6c 100644 --- a/mailpoet/.stylelintrc +++ b/mailpoet/.stylelintrc @@ -1,110 +1,106 @@ { - "plugins": [ - "stylelint-order", - "stylelint-scss" - ], - "customSyntax": "postcss-scss", - "rules": { - "at-rule-empty-line-before": ["always", { - except: ["first-nested", "blockless-after-blockless"], - }], - "at-rule-name-case": "lower", - "at-rule-semicolon-newline-after": "always", - "block-closing-brace-empty-line-before": "never", - "block-closing-brace-newline-after": "always", - "block-closing-brace-newline-before": "always-multi-line", - "block-closing-brace-space-before": "always-single-line", - "block-no-empty": true, - "block-opening-brace-newline-after": "always-multi-line", - "block-opening-brace-space-after": "always-single-line", - "block-opening-brace-space-before": "always", - "color-hex-case": "lower", - "color-hex-length": "short", - "color-no-invalid-hex": true, - "comment-no-empty": true, - "comment-whitespace-inside": "always", - "declaration-bang-space-after": "never", - "declaration-bang-space-before": "always", - "declaration-block-no-duplicate-properties": [true, { - ignore: ["consecutive-duplicates-with-different-values"], - }], - "declaration-block-no-redundant-longhand-properties": [true, { - ignoreShorthands: [/flex/, /grid/] - }], - "declaration-block-semicolon-newline-after": "always-multi-line", - "declaration-block-semicolon-space-after": "always-single-line", - "declaration-block-semicolon-space-before": "never", - "declaration-block-single-line-max-declarations": 1, - "declaration-colon-newline-after": "always-multi-line", - "declaration-colon-space-after": "always-single-line", - "declaration-colon-space-before": "never", - "declaration-empty-line-before": "never", - "font-family-no-duplicate-names": true, - "function-comma-space-after": "always-single-line", - "function-comma-space-before": "never", - "function-max-empty-lines": 0, - "function-name-case": "lower", - "function-parentheses-newline-inside": "always-multi-line", - "function-parentheses-space-inside": "never-single-line", - "function-url-quotes": "always", - "function-whitespace-after": "always", - "indentation": 2, - "keyframe-declaration-no-important": true, - "length-zero-no-unit": true, - "max-empty-lines": 1, - "media-feature-colon-space-after": "always", - "media-feature-colon-space-before": "never", - "media-feature-name-case": "lower", - "media-feature-name-no-unknown": true, - "media-feature-parentheses-space-inside": "never", - "media-feature-range-operator-space-after": "always", - "media-query-list-comma-newline-after": "always-multi-line", - "media-query-list-comma-space-after": "always-single-line", - "media-query-list-comma-space-before": "never", - "no-duplicate-selectors": true, - "no-eol-whitespace": true, - "no-extra-semicolons": true, - "no-missing-end-of-source-newline": true, - "number-leading-zero": "never", - "number-no-trailing-zeros": true, - "order/properties-alphabetical-order": true, - "property-case": "lower", - "property-no-unknown": true, - "rule-empty-line-before": ["always-multi-line", { - except: ["first-nested"], - ignore: ["after-comment"] - }], - "scss/at-rule-no-unknown": true, - "scss/dollar-variable-colon-space-after": "always", - "scss/dollar-variable-colon-space-before": "never", - "scss/operator-no-newline-after": true, - "scss/operator-no-newline-before": true, - "scss/operator-no-unspaced": true, - "scss/selector-no-redundant-nesting-selector": true, - "selector-attribute-brackets-space-inside": "never", - "selector-attribute-operator-space-after": "never", - "selector-attribute-operator-space-before": "never", - "selector-combinator-space-after": "always", - "selector-combinator-space-before": "always", - "selector-list-comma-newline-after": "always", - "selector-list-comma-space-before": "never", - "selector-max-empty-lines": 0, - "selector-nested-pattern": "^(?!&-|&_).*", - "selector-pseudo-class-case": "lower", - "selector-pseudo-class-no-unknown": true, - "selector-pseudo-class-parentheses-space-inside": "never", - "selector-pseudo-element-case": "lower", - "selector-pseudo-element-colon-notation": "single", - "selector-pseudo-element-no-unknown": true, - "selector-type-case": "lower", - "shorthand-property-no-redundant-values": true, - "string-no-newline": true, - "string-quotes": "single", - "unit-case": "lower", - "unit-no-unknown": true, - "value-list-comma-newline-after": "always-multi-line", - "value-list-comma-space-after": "always-single-line", - "value-list-comma-space-before": "never", - "value-list-max-empty-lines": 0, - }, + 'plugins': ['stylelint-order', 'stylelint-scss'], + 'customSyntax': 'postcss-scss', + 'rules': + { + 'at-rule-empty-line-before': + ['always', { except: ['first-nested', 'blockless-after-blockless'] }], + 'at-rule-name-case': 'lower', + 'at-rule-semicolon-newline-after': 'always', + 'block-closing-brace-empty-line-before': 'never', + 'block-closing-brace-newline-after': 'always', + 'block-closing-brace-newline-before': 'always-multi-line', + 'block-closing-brace-space-before': 'always-single-line', + 'block-no-empty': true, + 'block-opening-brace-newline-after': 'always-multi-line', + 'block-opening-brace-space-after': 'always-single-line', + 'block-opening-brace-space-before': 'always', + 'color-hex-case': 'lower', + 'color-hex-length': 'short', + 'color-no-invalid-hex': true, + 'comment-no-empty': true, + 'comment-whitespace-inside': 'always', + 'declaration-bang-space-after': 'never', + 'declaration-bang-space-before': 'always', + 'declaration-block-no-duplicate-properties': + [true, { ignore: ['consecutive-duplicates-with-different-values'] }], + 'declaration-block-no-redundant-longhand-properties': + [true, { ignoreShorthands: [/flex/, /grid/] }], + 'declaration-block-semicolon-newline-after': 'always-multi-line', + 'declaration-block-semicolon-space-after': 'always-single-line', + 'declaration-block-semicolon-space-before': 'never', + 'declaration-block-single-line-max-declarations': 1, + 'declaration-colon-newline-after': 'always-multi-line', + 'declaration-colon-space-after': 'always-single-line', + 'declaration-colon-space-before': 'never', + 'declaration-empty-line-before': 'never', + 'font-family-no-duplicate-names': true, + 'function-comma-space-after': 'always-single-line', + 'function-comma-space-before': 'never', + 'function-max-empty-lines': 0, + 'function-name-case': 'lower', + 'function-parentheses-newline-inside': 'always-multi-line', + 'function-parentheses-space-inside': 'never-single-line', + 'function-url-quotes': 'always', + 'function-whitespace-after': 'always', + 'indentation': 2, + 'keyframe-declaration-no-important': true, + 'length-zero-no-unit': true, + 'max-empty-lines': 1, + 'media-feature-colon-space-after': 'always', + 'media-feature-colon-space-before': 'never', + 'media-feature-name-case': 'lower', + 'media-feature-name-no-unknown': true, + 'media-feature-parentheses-space-inside': 'never', + 'media-feature-range-operator-space-after': 'always', + 'media-query-list-comma-newline-after': 'always-multi-line', + 'media-query-list-comma-space-after': 'always-single-line', + 'media-query-list-comma-space-before': 'never', + 'no-duplicate-selectors': true, + 'no-eol-whitespace': true, + 'no-extra-semicolons': true, + 'no-missing-end-of-source-newline': true, + 'number-leading-zero': 'never', + 'number-no-trailing-zeros': true, + 'order/properties-alphabetical-order': true, + 'property-case': 'lower', + 'property-no-unknown': true, + 'rule-empty-line-before': + [ + 'always-multi-line', + { except: ['first-nested'], ignore: ['after-comment'] }, + ], + 'scss/at-rule-no-unknown': true, + 'scss/dollar-variable-colon-space-after': 'always', + 'scss/dollar-variable-colon-space-before': 'never', + 'scss/operator-no-newline-after': true, + 'scss/operator-no-newline-before': true, + 'scss/operator-no-unspaced': true, + 'scss/selector-no-redundant-nesting-selector': true, + 'selector-attribute-brackets-space-inside': 'never', + 'selector-attribute-operator-space-after': 'never', + 'selector-attribute-operator-space-before': 'never', + 'selector-combinator-space-after': 'always', + 'selector-combinator-space-before': 'always', + 'selector-list-comma-newline-after': 'always', + 'selector-list-comma-space-before': 'never', + 'selector-max-empty-lines': 0, + 'selector-nested-pattern': '^(?!&-|&_).*', + 'selector-pseudo-class-case': 'lower', + 'selector-pseudo-class-no-unknown': true, + 'selector-pseudo-class-parentheses-space-inside': 'never', + 'selector-pseudo-element-case': 'lower', + 'selector-pseudo-element-colon-notation': 'single', + 'selector-pseudo-element-no-unknown': true, + 'selector-type-case': 'lower', + 'shorthand-property-no-redundant-values': true, + 'string-no-newline': true, + 'string-quotes': 'single', + 'unit-case': 'lower', + 'unit-no-unknown': true, + 'value-list-comma-newline-after': 'always-multi-line', + 'value-list-comma-space-after': 'always-single-line', + 'value-list-comma-space-before': 'never', + 'value-list-max-empty-lines': 0, + }, } diff --git a/mailpoet/README.md b/mailpoet/README.md index 350c0cecde..07d8da87e9 100644 --- a/mailpoet/README.md +++ b/mailpoet/README.md @@ -1,4 +1,5 @@ # MailPoet + The **MailPoet** plugin. To use the official Docker-based development environment, see details @@ -16,11 +17,13 @@ below. ## Setup ### Requirements + - PHP >= 7.3 (only for the development environment, to run the plugin PHP >= 7.2 is required) - NodeJS - WordPress ### Installation + The instructions below assume you already have a working WordPress development environment: ```bash @@ -171,8 +174,7 @@ _x('text to translate', 'context for translators', 'mailpoet'); **in Twig views** ```html -<%= __('text to translate') %> -<%= _n('single text', 'plural text', $number) %> +<%= __('text to translate') %> <%= _n('single text', 'plural text', $number) %> <%= _x('text to translate', 'context for translators') %> ``` @@ -183,12 +185,8 @@ The domain `mailpoet` will be added automatically by the Twig functions. First add the string to the translations block in the Twig view: ```html -<% block translations %> - <%= localize({ - 'key': __('string to translate'), - ... - }) %> -<% endblock %> +<% block translations %> <%= localize({ 'key': __('string to translate'), ... }) +%> <% endblock %> ``` Then use `MailPoet.I18n.t('key')` to get the translated string on your Javascript code. @@ -197,9 +195,11 @@ Then use `MailPoet.I18n.t('key')` to get the translated string on your Javascrip To run the whole acceptance testing suite you need the docker daemon to be running and after that use a command: `./do test:acceptance`. If you want to run only a single test use the parameter `--file`: + ```bash ./do test:acceptance --skip-deps --file tests/acceptance/ReceiveStandardEmailCest.php ``` + The argument `--skip-deps` is useful locally to speed up the run. If there are some unexpected errors you can delete all the runtime and start again. diff --git a/mailpoet/assets/css/src/storybook/wordpress-5.8.2.css b/mailpoet/assets/css/src/storybook/wordpress-5.8.2.css index 8de430516b..30e197cb24 100644 --- a/mailpoet/assets/css/src/storybook/wordpress-5.8.2.css +++ b/mailpoet/assets/css/src/storybook/wordpress-5.8.2.css @@ -1,12 +1,285 @@ /*! Basic typography settings from /wp-admin/load-styles.php?load=common */ body { - background: #f0f0f1; - color: #3c434a; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; - font-size: 13px; - line-height: 1.4em; - min-width: 600px; + background: #f0f0f1; + color: #3c434a; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; + font-size: 13px; + line-height: 1.4em; + min-width: 600px; } /*! Everything below is auto-generated from /wp-admin/load-styles.php?load=buttons */ -.wp-core-ui .button,.wp-core-ui .button-primary,.wp-core-ui .button-secondary{display:inline-block;text-decoration:none;font-size:13px;line-height:2.15384615;min-height:30px;margin:0;padding:0 10px;cursor:pointer;border-width:1px;border-style:solid;-webkit-appearance:none;border-radius:3px;white-space:nowrap;box-sizing:border-box}.wp-core-ui button::-moz-focus-inner,.wp-core-ui input[type=button]::-moz-focus-inner,.wp-core-ui input[type=reset]::-moz-focus-inner,.wp-core-ui input[type=submit]::-moz-focus-inner{border-width:0;border-style:none;padding:0}.wp-core-ui .button-group.button-large .button,.wp-core-ui .button.button-large{min-height:32px;line-height:2.30769231;padding:0 12px}.wp-core-ui .button-group.button-small .button,.wp-core-ui .button.button-small{min-height:26px;line-height:2.18181818;padding:0 8px;font-size:11px}.wp-core-ui .button-group.button-hero .button,.wp-core-ui .button.button-hero{font-size:14px;min-height:46px;line-height:3.14285714;padding:0 36px}.wp-core-ui .button.hidden{display:none}.wp-core-ui input[type=reset],.wp-core-ui input[type=reset]:active,.wp-core-ui input[type=reset]:focus,.wp-core-ui input[type=reset]:hover{background:0 0;border:none;box-shadow:none;padding:0 2px 1px;width:auto}.wp-core-ui .button,.wp-core-ui .button-secondary{color:#2271b1;border-color:#2271b1;background:#f6f7f7;vertical-align:top}.wp-core-ui p .button{vertical-align:baseline}.wp-core-ui .button-secondary:hover,.wp-core-ui .button.hover,.wp-core-ui .button:hover{background:#f0f0f1;border-color:#0a4b78;color:#0a4b78}.wp-core-ui .button-secondary:focus,.wp-core-ui .button.focus,.wp-core-ui .button:focus{background:#f6f7f7;border-color:#3582c4;color:#0a4b78;box-shadow:0 0 0 1px #3582c4;outline:2px solid transparent;outline-offset:0}.wp-core-ui .button-secondary:active,.wp-core-ui .button:active{background:#f6f7f7;border-color:#8c8f94;box-shadow:none}.wp-core-ui .button.active,.wp-core-ui .button.active:hover{background-color:#dcdcde;color:#135e96;border-color:#0a4b78;box-shadow:inset 0 2px 5px -3px #0a4b78}.wp-core-ui .button.active:focus{border-color:#3582c4;box-shadow:inset 0 2px 5px -3px #0a4b78,0 0 0 1px #3582c4}.wp-core-ui .button-disabled,.wp-core-ui .button-secondary.disabled,.wp-core-ui .button-secondary:disabled,.wp-core-ui .button-secondary[disabled],.wp-core-ui .button.disabled,.wp-core-ui .button:disabled,.wp-core-ui .button[disabled]{color:#a7aaad!important;border-color:#dcdcde!important;background:#f6f7f7!important;box-shadow:none!important;cursor:default;transform:none!important}.wp-core-ui .button-link{margin:0;padding:0;box-shadow:none;border:0;border-radius:0;background:0 0;cursor:pointer;text-align:left;color:#2271b1;text-decoration:underline;transition-property:border,background,color;transition-duration:.05s;transition-timing-function:ease-in-out}.wp-core-ui .button-link:active,.wp-core-ui .button-link:hover{color:#135e96}.wp-core-ui .button-link:focus{color:#043959;box-shadow:0 0 0 1px #4f94d4,0 0 2px 1px rgba(79,148,212,.8);outline:1px solid transparent}.wp-core-ui .button-link-delete{color:#d63638}.wp-core-ui .button-link-delete:focus,.wp-core-ui .button-link-delete:hover{color:#d63638;background:0 0}.wp-core-ui .button-link-delete:disabled{background:0 0!important}.wp-core-ui .button-primary{background:#2271b1;border-color:#2271b1;color:#fff;text-decoration:none;text-shadow:none}.wp-core-ui .button-primary.focus,.wp-core-ui .button-primary.hover,.wp-core-ui .button-primary:focus,.wp-core-ui .button-primary:hover{background:#135e96;border-color:#135e96;color:#fff}.wp-core-ui .button-primary.focus,.wp-core-ui .button-primary:focus{box-shadow:0 0 0 1px #fff,0 0 0 3px #2271b1}.wp-core-ui .button-primary.active,.wp-core-ui .button-primary.active:focus,.wp-core-ui .button-primary.active:hover,.wp-core-ui .button-primary:active{background:#135e96;border-color:#135e96;box-shadow:none;color:#fff}.wp-core-ui .button-primary-disabled,.wp-core-ui .button-primary.disabled,.wp-core-ui .button-primary:disabled,.wp-core-ui .button-primary[disabled]{color:#a7aaad!important;background:#f6f7f7!important;border-color:#dcdcde!important;box-shadow:none!important;text-shadow:none!important;cursor:default}.wp-core-ui .button-group{position:relative;display:inline-block;white-space:nowrap;font-size:0;vertical-align:middle}.wp-core-ui .button-group>.button{display:inline-block;border-radius:0;margin-right:-1px}.wp-core-ui .button-group>.button:first-child{border-radius:3px 0 0 3px}.wp-core-ui .button-group>.button:last-child{border-radius:0 3px 3px 0}.wp-core-ui .button-group>.button-primary+.button{border-left:0}.wp-core-ui .button-group>.button:focus{position:relative;z-index:1}.wp-core-ui .button-group>.button.active{background-color:#dcdcde;color:#135e96;border-color:#0a4b78;box-shadow:inset 0 2px 5px -3px #0a4b78}.wp-core-ui .button-group>.button.active:focus{border-color:#3582c4;box-shadow:inset 0 2px 5px -3px #0a4b78,0 0 0 1px #3582c4}@media screen and (max-width:782px){.wp-core-ui .button,.wp-core-ui .button.button-large,.wp-core-ui .button.button-small,a.preview,input#publish,input#save-post{padding:0 14px;line-height:2.71428571;font-size:14px;vertical-align:middle;min-height:40px;margin-bottom:4px}.wp-core-ui .copy-to-clipboard-container .copy-attachment-url{margin-bottom:0}#media-upload.wp-core-ui .button{padding:0 10px 1px;min-height:24px;line-height:22px;font-size:13px}.media-frame.mode-grid .bulk-select .button{margin-bottom:0}.wp-core-ui .save-post-status.button{position:relative;margin:0 14px 0 10px}.wp-core-ui.wp-customizer .button{font-size:13px;line-height:2.15384615;min-height:30px;margin:0;vertical-align:inherit}.media-modal-content .media-toolbar-primary .media-button{margin-top:10px;margin-left:5px}.interim-login .button.button-large{min-height:30px;line-height:2;padding:0 12px 2px}} +.wp-core-ui .button, +.wp-core-ui .button-primary, +.wp-core-ui .button-secondary { + display: inline-block; + text-decoration: none; + font-size: 13px; + line-height: 2.15384615; + min-height: 30px; + margin: 0; + padding: 0 10px; + cursor: pointer; + border-width: 1px; + border-style: solid; + -webkit-appearance: none; + border-radius: 3px; + white-space: nowrap; + box-sizing: border-box; +} +.wp-core-ui button::-moz-focus-inner, +.wp-core-ui input[type='button']::-moz-focus-inner, +.wp-core-ui input[type='reset']::-moz-focus-inner, +.wp-core-ui input[type='submit']::-moz-focus-inner { + border-width: 0; + border-style: none; + padding: 0; +} +.wp-core-ui .button-group.button-large .button, +.wp-core-ui .button.button-large { + min-height: 32px; + line-height: 2.30769231; + padding: 0 12px; +} +.wp-core-ui .button-group.button-small .button, +.wp-core-ui .button.button-small { + min-height: 26px; + line-height: 2.18181818; + padding: 0 8px; + font-size: 11px; +} +.wp-core-ui .button-group.button-hero .button, +.wp-core-ui .button.button-hero { + font-size: 14px; + min-height: 46px; + line-height: 3.14285714; + padding: 0 36px; +} +.wp-core-ui .button.hidden { + display: none; +} +.wp-core-ui input[type='reset'], +.wp-core-ui input[type='reset']:active, +.wp-core-ui input[type='reset']:focus, +.wp-core-ui input[type='reset']:hover { + background: 0 0; + border: none; + box-shadow: none; + padding: 0 2px 1px; + width: auto; +} +.wp-core-ui .button, +.wp-core-ui .button-secondary { + color: #2271b1; + border-color: #2271b1; + background: #f6f7f7; + vertical-align: top; +} +.wp-core-ui p .button { + vertical-align: baseline; +} +.wp-core-ui .button-secondary:hover, +.wp-core-ui .button.hover, +.wp-core-ui .button:hover { + background: #f0f0f1; + border-color: #0a4b78; + color: #0a4b78; +} +.wp-core-ui .button-secondary:focus, +.wp-core-ui .button.focus, +.wp-core-ui .button:focus { + background: #f6f7f7; + border-color: #3582c4; + color: #0a4b78; + box-shadow: 0 0 0 1px #3582c4; + outline: 2px solid transparent; + outline-offset: 0; +} +.wp-core-ui .button-secondary:active, +.wp-core-ui .button:active { + background: #f6f7f7; + border-color: #8c8f94; + box-shadow: none; +} +.wp-core-ui .button.active, +.wp-core-ui .button.active:hover { + background-color: #dcdcde; + color: #135e96; + border-color: #0a4b78; + box-shadow: inset 0 2px 5px -3px #0a4b78; +} +.wp-core-ui .button.active:focus { + border-color: #3582c4; + box-shadow: inset 0 2px 5px -3px #0a4b78, 0 0 0 1px #3582c4; +} +.wp-core-ui .button-disabled, +.wp-core-ui .button-secondary.disabled, +.wp-core-ui .button-secondary:disabled, +.wp-core-ui .button-secondary[disabled], +.wp-core-ui .button.disabled, +.wp-core-ui .button:disabled, +.wp-core-ui .button[disabled] { + color: #a7aaad !important; + border-color: #dcdcde !important; + background: #f6f7f7 !important; + box-shadow: none !important; + cursor: default; + transform: none !important; +} +.wp-core-ui .button-link { + margin: 0; + padding: 0; + box-shadow: none; + border: 0; + border-radius: 0; + background: 0 0; + cursor: pointer; + text-align: left; + color: #2271b1; + text-decoration: underline; + transition-property: border, background, color; + transition-duration: 0.05s; + transition-timing-function: ease-in-out; +} +.wp-core-ui .button-link:active, +.wp-core-ui .button-link:hover { + color: #135e96; +} +.wp-core-ui .button-link:focus { + color: #043959; + box-shadow: 0 0 0 1px #4f94d4, 0 0 2px 1px rgba(79, 148, 212, 0.8); + outline: 1px solid transparent; +} +.wp-core-ui .button-link-delete { + color: #d63638; +} +.wp-core-ui .button-link-delete:focus, +.wp-core-ui .button-link-delete:hover { + color: #d63638; + background: 0 0; +} +.wp-core-ui .button-link-delete:disabled { + background: 0 0 !important; +} +.wp-core-ui .button-primary { + background: #2271b1; + border-color: #2271b1; + color: #fff; + text-decoration: none; + text-shadow: none; +} +.wp-core-ui .button-primary.focus, +.wp-core-ui .button-primary.hover, +.wp-core-ui .button-primary:focus, +.wp-core-ui .button-primary:hover { + background: #135e96; + border-color: #135e96; + color: #fff; +} +.wp-core-ui .button-primary.focus, +.wp-core-ui .button-primary:focus { + box-shadow: 0 0 0 1px #fff, 0 0 0 3px #2271b1; +} +.wp-core-ui .button-primary.active, +.wp-core-ui .button-primary.active:focus, +.wp-core-ui .button-primary.active:hover, +.wp-core-ui .button-primary:active { + background: #135e96; + border-color: #135e96; + box-shadow: none; + color: #fff; +} +.wp-core-ui .button-primary-disabled, +.wp-core-ui .button-primary.disabled, +.wp-core-ui .button-primary:disabled, +.wp-core-ui .button-primary[disabled] { + color: #a7aaad !important; + background: #f6f7f7 !important; + border-color: #dcdcde !important; + box-shadow: none !important; + text-shadow: none !important; + cursor: default; +} +.wp-core-ui .button-group { + position: relative; + display: inline-block; + white-space: nowrap; + font-size: 0; + vertical-align: middle; +} +.wp-core-ui .button-group > .button { + display: inline-block; + border-radius: 0; + margin-right: -1px; +} +.wp-core-ui .button-group > .button:first-child { + border-radius: 3px 0 0 3px; +} +.wp-core-ui .button-group > .button:last-child { + border-radius: 0 3px 3px 0; +} +.wp-core-ui .button-group > .button-primary + .button { + border-left: 0; +} +.wp-core-ui .button-group > .button:focus { + position: relative; + z-index: 1; +} +.wp-core-ui .button-group > .button.active { + background-color: #dcdcde; + color: #135e96; + border-color: #0a4b78; + box-shadow: inset 0 2px 5px -3px #0a4b78; +} +.wp-core-ui .button-group > .button.active:focus { + border-color: #3582c4; + box-shadow: inset 0 2px 5px -3px #0a4b78, 0 0 0 1px #3582c4; +} +@media screen and (max-width: 782px) { + .wp-core-ui .button, + .wp-core-ui .button.button-large, + .wp-core-ui .button.button-small, + a.preview, + input#publish, + input#save-post { + padding: 0 14px; + line-height: 2.71428571; + font-size: 14px; + vertical-align: middle; + min-height: 40px; + margin-bottom: 4px; + } + .wp-core-ui .copy-to-clipboard-container .copy-attachment-url { + margin-bottom: 0; + } + #media-upload.wp-core-ui .button { + padding: 0 10px 1px; + min-height: 24px; + line-height: 22px; + font-size: 13px; + } + .media-frame.mode-grid .bulk-select .button { + margin-bottom: 0; + } + .wp-core-ui .save-post-status.button { + position: relative; + margin: 0 14px 0 10px; + } + .wp-core-ui.wp-customizer .button { + font-size: 13px; + line-height: 2.15384615; + min-height: 30px; + margin: 0; + vertical-align: inherit; + } + .media-modal-content .media-toolbar-primary .media-button { + margin-top: 10px; + margin-left: 5px; + } + .interim-login .button.button-large { + min-height: 30px; + line-height: 2; + padding: 0 12px 2px; + } +} diff --git a/mailpoet/assets/js/src/admin.js b/mailpoet/assets/js/src/admin.js index 6551a31a24..99f8f1cc35 100644 --- a/mailpoet/assets/js/src/admin.js +++ b/mailpoet/assets/js/src/admin.js @@ -2,7 +2,5 @@ import jQuery from 'jquery'; jQuery(function adminDomReady($) { // dom ready - $(function domReady() { - - }); + $(function domReady() {}); }); diff --git a/mailpoet/assets/js/src/ajax.ts b/mailpoet/assets/js/src/ajax.ts index 5e2defbe58..d12424ecc4 100644 --- a/mailpoet/assets/js/src/ajax.ts +++ b/mailpoet/assets/js/src/ajax.ts @@ -15,9 +15,11 @@ export type ErrorResponse = { }; // eslint-disable-next-line @typescript-eslint/no-explicit-any -export const isErrorResponse = (error: any): error is ErrorResponse => ( - error && typeof error === 'object' && 'errors' in error && Array.isArray(error.errors) -); +export const isErrorResponse = (error: any): error is ErrorResponse => + error && + typeof error === 'object' && + 'errors' in error && + Array.isArray(error.errors); type ResponseType = JQuery.Deferred; @@ -94,7 +96,9 @@ export const MailPoetAjax = { }, constructGetUrl: function constructGetUrl(options): string { this.init(options); - return `${this.options.url as string}?${jQuery.param(this.getParams() as object)}`; + return `${this.options.url as string}?${jQuery.param( + this.getParams() as object, + )}`; }, request: function request(method, options): ResponseType { // set options @@ -117,15 +121,26 @@ export const MailPoetAjax = { success: null, dataType: 'json', timeout: this.options.timeout, - }).then((data: Response) => deferred.resolve(data), (failedXhr, textStatus) => { - let errorData: ErrorResponse; - if (textStatus === 'timeout') { - errorData = buildErrorResponse(MailPoetI18n.t('ajaxTimeoutErrorMessage').replace('%d', timeout.toString())); - } else { - errorData = requestFailed(MailPoetI18n.t('ajaxFailedErrorMessage'), failedXhr); - } - void deferred.reject(errorData); - }); + }).then( + (data: Response) => deferred.resolve(data), + (failedXhr, textStatus) => { + let errorData: ErrorResponse; + if (textStatus === 'timeout') { + errorData = buildErrorResponse( + MailPoetI18n.t('ajaxTimeoutErrorMessage').replace( + '%d', + timeout.toString(), + ), + ); + } else { + errorData = requestFailed( + MailPoetI18n.t('ajaxFailedErrorMessage'), + failedXhr, + ); + } + void deferred.reject(errorData); + }, + ); // clear options this.options = {}; diff --git a/mailpoet/assets/js/src/analytics_event.js b/mailpoet/assets/js/src/analytics_event.js index 1a0f5330bb..bca9cf9e0e 100644 --- a/mailpoet/assets/js/src/analytics_event.js +++ b/mailpoet/assets/js/src/analytics_event.js @@ -39,7 +39,10 @@ function track(name, data = []) { function exportMixpanel() { window.MailPoet.forceTrackEvent = track; - if (window.mailpoet_analytics_enabled && window.MailPoet.libs3rdPartyEnabled) { + if ( + window.mailpoet_analytics_enabled && + window.MailPoet.libs3rdPartyEnabled + ) { window.MailPoet.trackEvent = track; } else { window.MailPoet.trackEvent = function emptyFunction() {}; diff --git a/mailpoet/assets/js/src/announcements/with_feature_announcement.tsx b/mailpoet/assets/js/src/announcements/with_feature_announcement.tsx index db233984db..52f3633e16 100644 --- a/mailpoet/assets/js/src/announcements/with_feature_announcement.tsx +++ b/mailpoet/assets/js/src/announcements/with_feature_announcement.tsx @@ -30,7 +30,10 @@ export const withFeatureAnnouncement =

>( let beamerCallback; function showPluginUpdateNotice() { - if (!window.mailpoet_update_available || document.getElementById('mailpoet_update_notice')) { + if ( + !window.mailpoet_update_available || + document.getElementById('mailpoet_update_notice') + ) { return; } const updateMailPoetNotice = ReactStringReplace( @@ -99,7 +102,7 @@ export const withFeatureAnnouncement =

>( }: Omit) { return ( showBeamer(e)} hasNews={showDot} /> diff --git a/mailpoet/assets/js/src/automation/api.tsx b/mailpoet/assets/js/src/automation/api.tsx index d84da359eb..103ed43aae 100644 --- a/mailpoet/assets/js/src/automation/api.tsx +++ b/mailpoet/assets/js/src/automation/api.tsx @@ -3,9 +3,10 @@ import { api } from './config'; const API_URL = `${api.root}/mailpoet/v1/automation`; -export const request = (path: string, init?: RequestInit): ReturnType => ( - fetch(`${API_URL}/${path}`, init) -); +export const request = ( + path: string, + init?: RequestInit, +): ReturnType => fetch(`${API_URL}/${path}`, init); type Error = { response?: Response; @@ -18,26 +19,25 @@ type State = { error?: Error; }; -type Result = [ - (init?: RequestInit) => Promise, - State, -]; +type Result = [(init?: RequestInit) => Promise, State]; // eslint-disable-next-line @typescript-eslint/no-explicit-any type Data = Record; -export const useMutation = (path: string, config?: RequestInit): Result => { +export const useMutation = ( + path: string, + config?: RequestInit, +): Result => { const [state, setState] = useState>({ data: undefined, loading: false, error: undefined, }); - const mutation = useCallback(async (init?: RequestInit) => { - setState((prevState) => ({ ...prevState, loading: true })); - const response = await request( - path, - { + const mutation = useCallback( + async (init?: RequestInit) => { + setState((prevState) => ({ ...prevState, loading: true })); + const response = await request(path, { ...config, ...init, headers: { @@ -45,32 +45,36 @@ export const useMutation = (path: string, config?: RequestInit): ...(init?.headers ?? {}), 'x-wp-nonce': api.nonce, }, - }, - ); + }); - try { - const data = await response.json(); - const error = response.ok ? null : { ...response, data }; - setState((prevState) => ({ ...prevState, data, error })); - } catch (_) { - const error = { response }; - setState((prevState) => ({ ...prevState, error })); - } finally { - setState((prevState) => ({ ...prevState, loading: false })); - } - }, [config, path]); + try { + const data = await response.json(); + const error = response.ok ? null : { ...response, data }; + setState((prevState) => ({ ...prevState, data, error })); + } catch (_) { + const error = { response }; + setState((prevState) => ({ ...prevState, error })); + } finally { + setState((prevState) => ({ ...prevState, loading: false })); + } + }, + [config, path], + ); return [mutation, state]; }; -export const useQuery = (path: string, init?: RequestInit): State => { +export const useQuery = ( + path: string, + init?: RequestInit, +): State => { const [mutation, result] = useMutation(path, init); useEffect( () => { void mutation(); }, - [], /* eslint-disable-line react-hooks/exhaustive-deps -- request only on initial load */ + [] /* eslint-disable-line react-hooks/exhaustive-deps -- request only on initial load */, ); return result; diff --git a/mailpoet/assets/js/src/automation/automation.tsx b/mailpoet/assets/js/src/automation/automation.tsx index 4ec248ddb2..b1360738d2 100644 --- a/mailpoet/assets/js/src/automation/automation.tsx +++ b/mailpoet/assets/js/src/automation/automation.tsx @@ -13,24 +13,26 @@ function ApiCheck(): JSX.Element { } function RecreateSchemaButton(): JSX.Element { - const [createSchema, { loading, error }] = useMutation('system/database', { method: 'POST' }); + const [createSchema, { loading, error }] = useMutation('system/database', { + method: 'POST', + }); return (

- - {error && (
{error?.data?.message ?? 'An unknown error occurred'}
)} + {error && ( +
{error?.data?.message ?? 'An unknown error occurred'}
+ )}
); } function DeleteSchemaButton(): JSX.Element { - const [deleteSchema, { loading, error }] = useMutation('system/database', { method: 'DELETE' }); + const [deleteSchema, { loading, error }] = useMutation('system/database', { + method: 'DELETE', + }); return (
@@ -38,13 +40,16 @@ function DeleteSchemaButton(): JSX.Element { type="button" onClick={async () => { await deleteSchema(); - window.location.href = '/wp-admin/admin.php?page=mailpoet-experimental'; + window.location.href = + '/wp-admin/admin.php?page=mailpoet-experimental'; }} disabled={loading} > Delete DB schema & deactivate feature - {error && (
{error?.data?.message ?? 'An unknown error occurred'}
)} + {error && ( +
{error?.data?.message ?? 'An unknown error occurred'}
+ )}
); } diff --git a/mailpoet/assets/js/src/automation/testing.tsx b/mailpoet/assets/js/src/automation/testing.tsx index 74f5de5291..de30e80920 100644 --- a/mailpoet/assets/js/src/automation/testing.tsx +++ b/mailpoet/assets/js/src/automation/testing.tsx @@ -30,20 +30,26 @@ const createWorkflow = () => { }; export function CreateTestingWorkflowButton(): JSX.Element { - const [createSchema, { loading, error }] = useMutation('workflows', { method: 'POST' }); + const [createSchema, { loading, error }] = useMutation('workflows', { + method: 'POST', + }); return (
- {error && (
{error?.data?.message ?? 'An unknown error occurred'}
)} + {error && ( +
{error?.data?.message ?? 'An unknown error occurred'}
+ )}
); } diff --git a/mailpoet/assets/js/src/common/button/_stories/button.tsx b/mailpoet/assets/js/src/common/button/_stories/button.tsx index e7c4a7aacd..4e3849fc66 100644 --- a/mailpoet/assets/js/src/common/button/_stories/button.tsx +++ b/mailpoet/assets/js/src/common/button/_stories/button.tsx @@ -12,10 +12,7 @@ export function WithoutIcons() { <> Small buttons

- - + - -

@@ -72,25 +56,14 @@ export function WithoutIcons() { Disabled buttons

- - + - -

@@ -98,25 +71,14 @@ export function WithoutIcons() { Buttons with spinner

- - + - -

@@ -124,10 +86,7 @@ export function WithoutIcons() { Full width buttons

- - -

@@ -126,35 +103,15 @@ export function WithIcons() { Buttons with spinner

- - -

diff --git a/mailpoet/assets/js/src/common/button/button.tsx b/mailpoet/assets/js/src/common/button/button.tsx index 360f01f2f8..5362a1c9d8 100644 --- a/mailpoet/assets/js/src/common/button/button.tsx +++ b/mailpoet/assets/js/src/common/button/button.tsx @@ -45,23 +45,16 @@ function Button({ target={target} rel={rel} disabled={isDisabled} - className={ - classnames( - className, - 'button', - 'mailpoet-button', - { - 'mailpoet-button-with-spinner': withSpinner, - 'mailpoet-button-disabled': isDisabled, - 'mailpoet-full-width': isFullWidth, - 'button-primary': !variant, - 'button-secondary': variant === 'secondary', - 'button-link': variant === 'tertiary', - 'button-link button-link-delete': variant === 'destructive', - 'button-small': dimension === 'small', - }, - ) - } + className={classnames(className, 'button', 'mailpoet-button', { + 'mailpoet-button-with-spinner': withSpinner, + 'mailpoet-button-disabled': isDisabled, + 'mailpoet-full-width': isFullWidth, + 'button-primary': !variant, + 'button-secondary': variant === 'secondary', + 'button-link': variant === 'tertiary', + 'button-link button-link-delete': variant === 'destructive', + 'button-small': dimension === 'small', + })} data-automation-id={automationId} > {iconStart} diff --git a/mailpoet/assets/js/src/common/categories/_stories/categories.tsx b/mailpoet/assets/js/src/common/categories/_stories/categories.tsx index 392f718fe7..8779e6ed2b 100644 --- a/mailpoet/assets/js/src/common/categories/_stories/categories.tsx +++ b/mailpoet/assets/js/src/common/categories/_stories/categories.tsx @@ -17,9 +17,21 @@ export function CategoriesWithCount() { return ( <> - - - + + + ); } @@ -35,9 +47,21 @@ export function CategoriesWithoutCount() { return ( <> - - - + + + ); } diff --git a/mailpoet/assets/js/src/common/categories/categories.tsx b/mailpoet/assets/js/src/common/categories/categories.tsx index 7764aed619..b754bc5817 100644 --- a/mailpoet/assets/js/src/common/categories/categories.tsx +++ b/mailpoet/assets/js/src/common/categories/categories.tsx @@ -16,11 +16,7 @@ function Categories({ onSelect, categories, active }: Props) { /> )); - return ( -
- { cats } -
- ); + return
{cats}
; } export default Categories; diff --git a/mailpoet/assets/js/src/common/categories/categories_item.tsx b/mailpoet/assets/js/src/common/categories/categories_item.tsx index 93f980517f..8fb7320f9d 100644 --- a/mailpoet/assets/js/src/common/categories/categories_item.tsx +++ b/mailpoet/assets/js/src/common/categories/categories_item.tsx @@ -20,10 +20,7 @@ function CategoriesItem({ automationId, active, }: Props) { - const classes = classNames( - 'mailpoet-categories-item', - { active: !!active }, - ); + const classes = classNames('mailpoet-categories-item', { active: !!active }); return ( {count > 0 && ( - { parseInt(count.toString(), 10).toLocaleString() } + {parseInt(count.toString(), 10).toLocaleString()} )} diff --git a/mailpoet/assets/js/src/common/confirm_alert.jsx b/mailpoet/assets/js/src/common/confirm_alert.jsx index 54bb2e125f..b9dc174251 100644 --- a/mailpoet/assets/js/src/common/confirm_alert.jsx +++ b/mailpoet/assets/js/src/common/confirm_alert.jsx @@ -8,13 +8,21 @@ function ConfirmAlert(props) { template: ReactDOMServer.renderToString( <>

{props.message}

- - - + , ), onInit: () => { document @@ -55,6 +63,6 @@ export default function confirmAlert(props) { cancelLabel={props.cancelLabel} confirmLabel={props.confirmLabel} onConfirm={props.onConfirm} - /> + />, ); } diff --git a/mailpoet/assets/js/src/common/controls/call_api.ts b/mailpoet/assets/js/src/common/controls/call_api.ts index 2a1cffe6b0..611a976c09 100644 --- a/mailpoet/assets/js/src/common/controls/call_api.ts +++ b/mailpoet/assets/js/src/common/controls/call_api.ts @@ -11,7 +11,9 @@ export default async function callApi({ endpoint, action, data }) { }); return { success: true, res }; } catch (res) { - const error = isErrorResponse(res) ? res.errors.map((e) => e.message) : null; + const error = isErrorResponse(res) + ? res.errors.map((e) => e.message) + : null; return { success: false, error, res }; } } diff --git a/mailpoet/assets/js/src/common/controls/track_event.ts b/mailpoet/assets/js/src/common/controls/track_event.ts index 32410a3a6a..5711df149f 100644 --- a/mailpoet/assets/js/src/common/controls/track_event.ts +++ b/mailpoet/assets/js/src/common/controls/track_event.ts @@ -1,14 +1,19 @@ import MailPoet from 'mailpoet'; -const sleep = (ms:number) => new Promise((resolve) => { setTimeout(resolve, ms); }); +const sleep = (ms: number) => + new Promise((resolve) => { + setTimeout(resolve, ms); + }); -export default async function trackEvent( - { - name, - data, - timeout = 0, - }:{ name:string, data:object, timeout:number }, -) { +export default async function trackEvent({ + name, + data, + timeout = 0, +}: { + name: string; + data: object; + timeout: number; +}) { MailPoet.trackEvent(name, data); return sleep(timeout); } diff --git a/mailpoet/assets/js/src/common/datepicker/_stories/datepicker.tsx b/mailpoet/assets/js/src/common/datepicker/_stories/datepicker.tsx index 5bc0a64261..cbdeb32e4d 100644 --- a/mailpoet/assets/js/src/common/datepicker/_stories/datepicker.tsx +++ b/mailpoet/assets/js/src/common/datepicker/_stories/datepicker.tsx @@ -9,21 +9,13 @@ export default { component: Datepicker, }; -function DatepickerWrapper({ - ...props -}) { +function DatepickerWrapper({ ...props }) { const [startDate, setStartDate] = useState(new Date()); - const onChange = (date:Date) => { + const onChange = (date: Date) => { props.onChange(date); setStartDate(date); }; - return ( - - ); + return ; } export function Datepickers() { diff --git a/mailpoet/assets/js/src/common/datepicker/datepicker.tsx b/mailpoet/assets/js/src/common/datepicker/datepicker.tsx index 65b4d00534..93788a378a 100644 --- a/mailpoet/assets/js/src/common/datepicker/datepicker.tsx +++ b/mailpoet/assets/js/src/common/datepicker/datepicker.tsx @@ -18,16 +18,11 @@ function Datepicker({ }: Props) { return (
{iconStart} { const index = values.indexOf(value); - let newValues:CheckboxValueType[] = []; + let newValues: CheckboxValueType[] = []; if (isChecked && index === -1) { newValues = values.concat([value]); } @@ -42,7 +42,7 @@ function CheckboxGroup({
{options.map((props: CheckboxProps) => { const { label, ...attributes } = props; - const value = (props.value as CheckboxValueType); + const value = props.value as CheckboxValueType; return ( Small inputs
- +
Regular inputs
- +
- +

Full-width inputs
- + {iconStart} - {customLabel &&
{customLabel}
} + {customLabel && ( +
{customLabel}
+ )} {tooltip && ( - <> - - - - - {tooltip} - - + <> + + + + + {tooltip} + + )} {iconEnd}
diff --git a/mailpoet/assets/js/src/common/form/radio/_stories/radio.tsx b/mailpoet/assets/js/src/common/form/radio/_stories/radio.tsx index f896f16f25..c814436e24 100644 --- a/mailpoet/assets/js/src/common/form/radio/_stories/radio.tsx +++ b/mailpoet/assets/js/src/common/form/radio/_stories/radio.tsx @@ -23,18 +23,10 @@ export function Radios() { <> Inline individual radios
- + Option 1 - + Option 2
diff --git a/mailpoet/assets/js/src/common/form/radio/group.tsx b/mailpoet/assets/js/src/common/form/radio/group.tsx index 60043bb5ad..b852f75c2f 100644 --- a/mailpoet/assets/js/src/common/form/radio/group.tsx +++ b/mailpoet/assets/js/src/common/form/radio/group.tsx @@ -33,7 +33,7 @@ function RadioGroup({
{options.map((props: RadioProps) => { const { label, ...attributes } = props; - const value = (props.value as RadioValueType); + const value = props.value as RadioValueType; return ( - {data.tag && {data.tag}} + {data.tag && ( + {data.tag} + )} {data.label} - {data.count !== undefined && {data.count}} + {data.count !== undefined && ( + {data.count} + )}
); } @@ -43,14 +47,12 @@ function Option(props: OptionProps) { style={style} ref={props.innerRef} {...props.innerProps} - className={ - classnames({ - 'mailpoet-form-react-select__option': true, - 'mailpoet-form-react-select__option--is-disabled': props.isDisabled, - 'mailpoet-form-react-select__option--is-focused': props.isFocused, - 'mailpoet-form-react-select__option--is-selected': props.isSelected, - }) - } + className={classnames({ + 'mailpoet-form-react-select__option': true, + 'mailpoet-form-react-select__option--is-disabled': props.isDisabled, + 'mailpoet-form-react-select__option--is-focused': props.isFocused, + 'mailpoet-form-react-select__option--is-selected': props.isSelected, + })} > {LabelRenderer(props.data)}
@@ -62,12 +64,11 @@ function SingleValue(props: any) { return (
{LabelRenderer(props.data as LabelRendererProps)}
@@ -105,17 +106,11 @@ function ReactSelect({ }: Props) { return (
{iconStart} diff --git a/mailpoet/assets/js/src/common/form/select/_stories/select.tsx b/mailpoet/assets/js/src/common/form/select/_stories/select.tsx index d9da9d7fbd..4480d33b9d 100644 --- a/mailpoet/assets/js/src/common/form/select/_stories/select.tsx +++ b/mailpoet/assets/js/src/common/form/select/_stories/select.tsx @@ -18,10 +18,7 @@ export function NativeSelect() {
- @@ -36,10 +33,7 @@ export function NativeSelect() {
- @@ -73,10 +67,7 @@ export function NativeSelect() {
- diff --git a/mailpoet/assets/js/src/common/form/select/select.tsx b/mailpoet/assets/js/src/common/form/select/select.tsx index 69a1a70234..1b880e7067 100644 --- a/mailpoet/assets/js/src/common/form/select/select.tsx +++ b/mailpoet/assets/js/src/common/form/select/select.tsx @@ -1,9 +1,4 @@ -import { - forwardRef, - ReactNode, - Ref, - SelectHTMLAttributes, -} from 'react'; +import { forwardRef, ReactNode, Ref, SelectHTMLAttributes } from 'react'; import classnames from 'classnames'; type Props = SelectHTMLAttributes & { @@ -15,34 +10,33 @@ type Props = SelectHTMLAttributes & { automationId?: string; }; -const Select = forwardRef(({ - children, - dimension, - isFullWidth, - isMinWidth, - iconStart, - automationId, - ...attributes -}: Props, ref?: Ref) => ( -
- {iconStart} - -
-)); +const Select = forwardRef( + ( + { + children, + dimension, + isFullWidth, + isMinWidth, + iconStart, + automationId, + ...attributes + }: Props, + ref?: Ref, + ) => ( +
+ {iconStart} + +
+ ), +); export default Select; diff --git a/mailpoet/assets/js/src/common/form/textarea/_stories/textarea.tsx b/mailpoet/assets/js/src/common/form/textarea/_stories/textarea.tsx index 6c3c426393..fa0471c86a 100644 --- a/mailpoet/assets/js/src/common/form/textarea/_stories/textarea.tsx +++ b/mailpoet/assets/js/src/common/form/textarea/_stories/textarea.tsx @@ -11,30 +11,19 @@ export function Textareas() { <> Small textareas
-