Compare commits

..

1 Commits

Author SHA1 Message Date
32e5785e54 Release 3.97.0 2022-09-12 13:36:43 -05:00
4257 changed files with 116682 additions and 234137 deletions

View File

@ -1,19 +0,0 @@
#!/bin/bash
# Fetch the versions of WooCommerce from the WordPress API
VERSIONS=$(curl -s https://api.wordpress.org/plugins/info/1.0/woocommerce.json | jq -r '.versions | keys_unsorted | .[]' | grep -v 'trunk')
LATEST_VERSION=""
# Find the latest version
for version in $VERSIONS; do
LATEST_VERSION=$version
done
# Check if the latest version is a beta/RC version
if [[ $LATEST_VERSION != *'beta'* && $LATEST_VERSION != *'rc'* ]]; then
echo "No WooCommerce beta/RC version found."
echo "LATEST_BETA="
else
echo "Latest WooCommerce beta/RC version: $LATEST_VERSION"
echo "LATEST_BETA=$LATEST_VERSION"
fi

View File

@ -1,28 +0,0 @@
#!/bin/bash
# Fetch the WordPress releases RSS feed
RSS_FEED=$(curl -s https://wordpress.org/news/category/releases/feed/)
# Extract the latest version from the feed and convert it to lowercase
LAST_VERSION=$(echo "$RSS_FEED" | grep -o '<title>WordPress [^<]*</title>' | sed -E 's/<\/?title>//g' | head -n 1 | tr [:upper:] [:lower:])
# Check if a beta or RC version is found
if [[ $LAST_VERSION == *'beta'* ]]; then
# Extract titles containing beta versions from the feed
VERSION_LINE=$(echo "$RSS_FEED" | grep -o '<code>wp core update [^<]*</code>' | sed -E 's/<\/?code>//g' | head -n 1 | grep 'beta')
LATEST_BETA=$(echo "$VERSION_LINE" | sed -E 's/.*--version=([0-9\.]+-beta[0-9]+).*/\1/')
echo "Latest WordPress beta version: $LATEST_BETA"
echo "LATEST_BETA=$LATEST_BETA"
elif [[ $LAST_VERSION == *'release candidate'* ]]; then
# Extract titles containing RC versions from the feed
VERSION_LINE=$(echo "$RSS_FEED" | grep -o '<code>wp core update [^<]*</code>' | sed -E 's/<\/?code>//g' | head -n 1 | grep 'RC')
LATEST_BETA=$(echo "$VERSION_LINE" | sed -E 's/.*--version=([0-9\.]+-RC[0-9]+).*/\1/')
echo "Latest WordPress RC version: $LATEST_BETA"
echo "LATEST_BETA=$LATEST_BETA"
else
echo "No WordPress beta/RC version found."
echo "LATEST_BETA="
fi

View File

@ -2,7 +2,6 @@ version: 2.1
orbs:
slack: circleci/slack@4.2.0
jq: circleci/jq@3.0.0
slack-fail-post-step: &slack-fail-post-step
post-steps:
@ -72,12 +71,6 @@ anchors:
- trunk
- release
only_release: &only_release
filters:
branches:
only:
- release
multisite_acceptance_config: &multisite_acceptance_config
multisite: 1
requires:
@ -85,37 +78,32 @@ anchors:
- static_analysis_php8
<<: *only_trunk_and_release
command_add_github_key: &command_add_github_key
command: |
# Add GitHub to known_hosts (for jobs without a checkout step that need to clone repos)
echo "github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=" >> ~/.ssh/known_hosts
executors:
wpcli_php_oldest:
<<: *default_job_config
docker:
- image: mailpoet/wordpress:7.4_20231129.1
- image: mailpoet/wordpress:7.2_20220309.1
wpcli_php_max_wporg:
<<: *default_job_config
docker:
- image: mailpoet/wordpress:8.1_20230307.1 # We need to use 8.1 to emulate the WP.org environment
- image: mailpoet/wordpress:7.4_20210122.1
wpcli_php_latest:
<<: *default_job_config
docker:
- image: mailpoet/wordpress:8.2_20241126.1
- image: mailpoet/wordpress:8.1_20220718.1
wpcli_php_mysql_oldest:
<<: *default_job_config
docker:
- image: mailpoet/wordpress:7.4_20231129.1
- image: mailpoet/wordpress:7.2_20220309.1
- image: cimg/mysql:5.7
wpcli_php_mysql_latest:
<<: *default_job_config
docker:
- image: mailpoet/wordpress:8.2_20241126.1
- image: mailpoet/wordpress:8.1_20220718.1
- image: cimg/mysql:8.0
command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_520_ci
@ -141,8 +129,6 @@ jobs:
key: composer-{{ checksum "tasks/code_sniffer/composer.json" }}-{{ checksum "tasks/code_sniffer/composer.lock" }}
- restore_cache:
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
- restore_cache:
key: composer-{{ checksum "../tests_env/composer.json" }}-{{ checksum "../tests_env/composer.lock" }}
- restore_cache:
key: composer-prefixed-{{ checksum "prefixer-checksum" }}
- restore_cache:
@ -157,9 +143,9 @@ jobs:
./tools/vendor/composer.phar validate --no-check-all --no-check-publish --working-dir=prefixer
touch .env
./do install
./do compile:all --env production --skip-tests
./do compile:all --env production
./do doctrine:generate-cache
../tests_env/vendor/bin/codecept build
vendor/bin/codecept build
./do twig:generate-cache
- run:
name: 'Check Prettier formatting'
@ -180,10 +166,6 @@ jobs:
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
paths:
- vendor
- save_cache:
key: composer-{{ checksum "../tests_env/composer.json" }}-{{ checksum "../tests_env/composer.lock" }}
paths:
- ../tests_env/vendor
- save_cache:
key: composer-prefixed-{{ checksum "prefixer-checksum" }}
paths:
@ -197,10 +179,10 @@ jobs:
- run:
name: Download additional WP Plugins for tests
command: |
./do download:woo-commerce-zip 9.6.1
./do download:woo-commerce-subscriptions-zip 7.1.0
./do download:woo-commerce-memberships-zip 1.26.5
./do download:automate-woo-zip 6.1.5
./do download:woo-commerce-zip 6.8.2
./do download:woo-commerce-subscriptions-zip 4.5.1
./do download:woo-commerce-memberships-zip 1.23.0
./do download:woo-commerce-blocks-zip 8.4.0
- run:
name: Dump tests ENV variables for acceptance tests
command: |
@ -218,12 +200,11 @@ jobs:
- attach_workspace:
at: /home/circleci
- add_ssh_keys
- run:
name: 'Add GitHub to known_hosts'
<<: *command_add_github_key
- run:
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
git clone ${MAILPOET_PREMIUM_REPO_URL} mailpoet-premium
- restore_cache:
key: premium-tools-{{ checksum "mailpoet-premium/tools/install.php" }}
@ -256,38 +237,18 @@ jobs:
static_analysis:
executor: wpcli_php_latest
resource_class: medium+
resource_class: medium
working_directory: /home/circleci/mailpoet/mailpoet
parameters:
php_version:
type: integer
default: 70400
default: 70200
steps:
- attach_workspace:
at: /home/circleci
- run:
name: 'Static analysis'
command: ./do qa:phpstan --php-version=<< parameters.php_version >>
security_analysis:
working_directory: /home/circleci/mailpoet/mailpoet
machine:
image: default
docker_layer_caching: false
steps:
- attach_workspace:
at: /home/circleci
- add_ssh_keys
- run:
name: 'Add GitHub to known_hosts'
<<: *command_add_github_key
- run:
name: 'Set up PHP'
command: |
sudo apt update
sudo apt install -y php8.1-cli
- run:
name: 'QA Security'
command: ./do qa:semgrep
qa_js:
executor: wpcli_php_latest
working_directory: /home/circleci/mailpoet/mailpoet
@ -336,7 +297,6 @@ jobs:
name: 'JS Newsletter Editor Tests'
command: |
mkdir test-results/mocha
./do compile:js --env production --only-tests
./do t:newsletter-editor test-results/mocha/newsletter_editor_junit.xml
- run:
name: 'JS Tests'
@ -351,19 +311,15 @@ jobs:
parallelism: 20
working_directory: /home/circleci/mailpoet/mailpoet
machine:
image: default
docker_layer_caching: false
image: ubuntu-2004:202111-01
parameters:
multisite:
type: integer
default: 0
group:
type: string
default: ''
mysql_command:
type: string
default: ''
mysql_image:
mysql_image_version:
type: string
default: ''
codeception_image_version:
@ -381,159 +337,70 @@ jobs:
woo_memberships_version:
type: string
default: ''
automate_woo_version:
type: string
default: ''
blockbased_theme:
type: integer
default: 0
enable_hpos:
type: integer
default: 0
enable_hpos_sync:
type: integer
default: 0
disable_hpos:
type: integer
default: 0
use_wordpress_beta:
type: boolean
default: false
use_woocommerce_beta:
type: boolean
default: false
wordpress_version:
woo_blocks_version:
type: string
default: ''
environment:
MYSQL_COMMAND: << parameters.mysql_command >>
MYSQL_IMAGE: << parameters.mysql_image >>
MYSQL_IMAGE_VERSION: << parameters.mysql_image_version >>
CODECEPTION_IMAGE_VERSION: << parameters.codeception_image_version >>
WORDPRESS_IMAGE_VERSION: << parameters.wordpress_image_version >>
WORDPRESS_VERSION: << parameters.wordpress_version >>
steps:
- attach_workspace:
at: /home/circleci
- run:
name: 'Set up virtual host'
command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts
- run:
name: Check for Latest WordPress Beta Version
command: |
if [ "<< parameters.use_wordpress_beta >>" == "true" ]; then
LATEST_WP_BETA=$(../.circleci/check_wordpress_beta.sh | grep 'LATEST_BETA' | cut -d'=' -f2)
if [ -z "$LATEST_WP_BETA" ]; then
echo "No latest beta version found. Ending job early."
circleci-agent step halt
else
echo "export WORDPRESS_VERSION=$LATEST_WP_BETA" >> $BASH_ENV
echo "Using WordPress Beta Version: $LATEST_WP_BETA"
fi
else
echo "Not using WordPress Beta Version"
fi
- run:
name: Check for Latest WooCommerce Beta Version
command: |
if [ "<< parameters.use_woocommerce_beta >>" == "true" ]; then
LATEST_WC_BETA=$(../.circleci/check_woocommerce_beta.sh | grep 'LATEST_BETA' | cut -d'=' -f2)
if [ -z "$LATEST_WC_BETA" ]; then
echo "No WooCommerce Beta version found. Ending job early."
circleci-agent step halt
else
echo "export WOOCOMMERCE_VERSION=$LATEST_WC_BETA" >> $BASH_ENV
echo "Using WooCommerce Beta Version: $LATEST_WC_BETA"
fi
else
echo "export WOOCOMMERCE_VERSION=<< parameters.woo_core_version >>" >> $BASH_ENV
echo "Not using WooCommerce Beta Version"
fi
- run:
name: 'Pull test docker images'
# Pull docker images with 3 retries
command: i='0';while ! docker compose -f ../tests_env/docker/docker-compose.yml pull && ((i < 3)); do sleep 3 && i=$[$i+1]; done
- run:
name: Create docker containers for test
# We experienced some failures when creating containers so we do it explicitly with one retry
command: |
cd ../tests_env/docker
docker compose create || docker compose create
- run:
# Some tools we use may need different version based on PHP version used in docker
name: Ensure correct versions of tools
command: |
cd ../tests_env/docker
docker compose run --rm -w /project -e COMPOSER_DEV_MODE=1 --entrypoint "php tools/install.php" codeception_acceptance
command: i='0';while ! docker-compose -f tests/docker/docker-compose.yml pull && ((i < 3)); do sleep 3 && i=$[$i+1]; done
- when:
condition: ${WOOCOMMERCE_VERSION}
condition: << parameters.woo_core_version >>
steps:
- run:
name: Download WooCommerce Core
command: |
cd ../tests_env/docker
docker compose run --rm -w /project --entrypoint "./do download:woo-commerce-zip ${WOOCOMMERCE_VERSION}" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_acceptance
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_acceptance
- when:
condition: << parameters.woo_subscriptions_version >>
steps:
- run:
name: Download WooCommerce Subscriptions
command: |
cd ../tests_env/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_acceptance
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_acceptance
- when:
condition: << parameters.woo_memberships_version >>
steps:
- run:
name: Download WooCommerce Memberships
command: |
cd ../tests_env/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_acceptance
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_acceptance
- when:
condition: << parameters.automate_woo_version >>
condition: << parameters.woo_blocks_version >>
steps:
- run:
name: Download AutomateWoo
name: Download WooCommerce Blocks
command: |
cd ../tests_env/docker
docker compose run --rm -w /project --entrypoint "./do download:automate-woo-zip << parameters.automate_woo_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_acceptance
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_acceptance
- run:
name: Group acceptance tests
command: |
# Convert test result filename values to be relative paths because the circleci CLI's split command requires exact matches
if [ -e $CIRCLE_INTERNAL_TASK_DATA/circle-test-results/results.json ]; then
sed -i.bak 's#/wp-core/wp-content/plugins/mailpoet/##g' $CIRCLE_INTERNAL_TASK_DATA/circle-test-results/results.json
fi
sed -i.bak 's#/wp-core/wp-content/plugins/mailpoet/##g' $CIRCLE_INTERNAL_TASK_DATA/circle-test-results/results.json
# `circleci tests split` returns different values based on the container it's run on
# in case group is defined find only tests containing the group
if [[ -n '<< parameters.group >>' ]]; then
grep -rw 'tests/acceptance' -e '@group << parameters.group >>' | sed -e "s/:.*//" | circleci tests split --split-by=timings > tests/acceptance/_groups/circleci_split_group
else
circleci tests glob "tests/acceptance/**/*Cest.php" | circleci tests split --split-by=timings > tests/acceptance/_groups/circleci_split_group
fi
circleci tests glob "tests/acceptance/**/*Cest.php" | circleci tests split --split-by=timings > tests/acceptance/_groups/circleci_split_group
cat tests/acceptance/_groups/circleci_split_group
- run:
name: Run acceptance tests
command: |
mkdir -m 777 -p tests/_output/exceptions
cd ../tests_env/docker
args=(
--steps
--debug
-vvv
--html
--xml
-g circleci_split_group
)
docker compose run -e SKIP_DEPS=1 \
-e CIRCLE_BRANCH=${CIRCLE_BRANCH} \
-e CIRCLE_JOB=${CIRCLE_JOB} \
-e MULTISITE=<< parameters.multisite >> \
-e BLOCKBASED_THEME=<< parameters.blockbased_theme >> \
-e ENABLE_HPOS=<< parameters.enable_hpos >> \
-e ENABLE_HPOS_SYNC=<< parameters.enable_hpos_sync >> \
-e DISABLE_HPOS=<< parameters.disable_hpos >> \
-e WORDPRESS_VERSION=${WORDPRESS_VERSION} \
codeception_acceptance "${args[@]}"
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_acceptance -g circleci_split_group --steps --debug -vvv --html --xml
- run:
name: Check exceptions
command: |
@ -545,70 +412,6 @@ jobs:
path: tests/_output
- store_test_results:
path: tests/_output
performance_tests:
working_directory: /home/circleci/mailpoet/mailpoet
machine:
image: default
docker_layer_caching: false
parameters:
mysql_command:
type: string
default: ''
mysql_image:
type: string
default: ''
wordpress_image_version:
type: string
default: ''
url:
type: string
default: 'http://localhost:9500'
us:
type: string
default: 'admin'
pw:
type: string
default: 'password'
scenario:
type: string
default: 'pullrequests'
steps:
- attach_workspace:
at: /home/circleci
- run:
name: 'Set up PHP'
command: |
sudo apt update
sudo apt install -y php8.1-cli
- run:
name: 'Pull test docker images'
# Pull docker images with 3 retries
command: i='0';while ! docker compose -f tests/performance/docker-compose.yml pull && ((i < 3)); do sleep 3 && i=$[$i+1]; done
- run:
name: Create docker containers for test
# We experienced some failures when creating containers so we do it explicitly with one retry
command: |
cd tests/performance
docker compose create || docker compose create
- run:
name: Run performance tests
command: |
mkdir -p tests/performance/_output
mkdir -p tests/performance/_screenshots
./do test:performance --url=<< parameters.url >> --pw=<< parameters.pw >> --scenario=<< parameters.scenario >>
- run:
name: Check exceptions
command: |
if [ "$(ls tests/performance/_output/exceptions/*.html)" ]; then
echo "There were some exceptions during the tests run"
exit 1
fi
- store_artifacts:
path: tests/performance/_output
- store_artifacts:
path: tests/performance/_screenshots
- store_test_results:
path: tests/performance/_output
unit_tests:
working_directory: /home/circleci/mailpoet/mailpoet
parameters:
@ -625,10 +428,6 @@ jobs:
- run:
name: 'Prepare example.com for testing'
command: echo 127.0.0.1 example.com | sudo tee -a /etc/hosts
- run:
# Some tools we use may need different version based on PHP version used in docker
name: Ensure correct versions of tools
command: COMPOSER_DEV_MODE=1 php tools/install.php
- run:
name: 'Set up test environment'
command: source ../.circleci/setup.bash && setup php7
@ -647,21 +446,13 @@ jobs:
integration_tests:
working_directory: /home/circleci/mailpoet/mailpoet
machine:
image: default
docker_layer_caching: false
image: ubuntu-2004:202111-01
environment:
CODECEPTION_IMAGE_VERSION: << parameters.codeception_image_version >>
MYSQL_COMMAND: << parameters.mysql_command >>
MYSQL_IMAGE: << parameters.mysql_image >>
WORDPRESS_IMAGE_VERSION: << parameters.wordpress_image_version >>
WORDPRESS_VERSION: << parameters.wordpress_version >>
parameters:
codeception_image_version:
type: string
default: ''
wordpress_image_version:
type: string
default: ''
group:
type: string
default: ''
@ -671,132 +462,21 @@ jobs:
skip_plugins:
type: integer
default: 0
enable_hpos:
type: integer
default: 0
enable_hpos_sync:
type: integer
default: 0
disable_hpos:
type: integer
default: 0
multisite:
type: integer
default: 0
mysql_command:
type: string
default: ''
mysql_image:
type: string
default: ''
woo_core_version:
type: string
default: ''
woo_subscriptions_version:
type: string
default: ''
woo_memberships_version:
type: string
default: ''
automate_woo_version:
type: string
default: ''
use_wordpress_beta:
type: boolean
default: false
use_woocommerce_beta:
type: boolean
default: false
wordpress_version:
type: string
default: ''
steps:
- attach_workspace:
at: /home/circleci
- run:
name: Check for Latest WordPress Beta Version
command: |
if [ "<< parameters.use_wordpress_beta >>" == "true" ]; then
LATEST_WP_BETA=$(../.circleci/check_wordpress_beta.sh | grep 'LATEST_BETA' | cut -d'=' -f2)
if [ -z "$LATEST_WP_BETA" ]; then
echo "No latest beta version found. Ending job early."
circleci-agent step halt
else
echo "export WORDPRESS_VERSION=$LATEST_WP_BETA" >> $BASH_ENV
echo "Using WordPress Beta Version: $LATEST_WP_BETA"
fi
else
echo "Not using WordPress Beta Version"
fi
- run:
name: Check for Latest WooCommerce Beta Version
command: |
if [ "<< parameters.use_woocommerce_beta >>" == "true" ]; then
LATEST_WC_BETA=$(../.circleci/check_woocommerce_beta.sh | grep 'LATEST_BETA' | cut -d'=' -f2)
if [ -z "$LATEST_WC_BETA" ]; then
echo "No WooCommerce Beta version found. Ending job early."
circleci-agent step halt
else
echo "export WOOCOMMERCE_VERSION=$LATEST_WC_BETA" >> $BASH_ENV
echo "Using WooCommerce Beta Version: $LATEST_WC_BETA"
fi
else
echo "export WOOCOMMERCE_VERSION=<< parameters.woo_core_version >>" >> $BASH_ENV
echo "Not using WooCommerce Beta Version"
fi
- run:
name: 'Pull test docker images'
# Pull docker images with 3 retries
command: i='0';while ! docker compose -f ../tests_env/docker/docker-compose.yml pull && ((i < 3)); do sleep 3 && i=$[$i+1]; done
- run:
name: Create docker containers for test
# We experienced some failures when creating containers so we do it explicitly with one retry
command: |
cd ../tests_env/docker
docker compose create || docker compose create
- run:
# Some tools we use may need different version based on PHP version used in docker
name: Ensure correct versions of tools
command: |
cd ../tests_env/docker
docker compose run --rm -w /project -e COMPOSER_DEV_MODE=1 --entrypoint "php tools/install.php" codeception_integration
- when:
condition: ${WOOCOMMERCE_VERSION}
steps:
- run:
name: Download WooCommerce Core
command: |
cd ../tests_env/docker
docker compose run --rm -w /project --entrypoint "./do download:woo-commerce-zip ${WOOCOMMERCE_VERSION}" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_integration
- when:
condition: << parameters.woo_subscriptions_version >>
steps:
- run:
name: Download WooCommerce Subscriptions
command: |
cd ../tests_env/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_integration
- when:
condition: << parameters.woo_memberships_version >>
steps:
- run:
name: Download WooCommerce Memberships
command: |
cd ../tests_env/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_integration
- when:
condition: << parameters.automate_woo_version >>
steps:
- run:
name: Download AutomateWoo
command: |
cd ../tests_env/docker
docker compose run --rm -w /project --entrypoint "./do download:automate-woo-zip << parameters.automate_woo_version >>" --no-deps -e WP_GITHUB_USERNAME=${WP_GITHUB_USERNAME} -e WP_GITHUB_TOKEN=${WP_GITHUB_TOKEN} codeception_integration
command: i='0';while ! docker-compose -f tests/docker/docker-compose.yml pull && ((i < 3)); do sleep 3 && i=$[$i+1]; done
- run:
name: 'PHP Integration tests'
command: |
mkdir -m 777 -p tests/_output/exceptions
cd ../tests_env/docker
cd tests/docker
args=(
--steps
--debug
@ -810,7 +490,7 @@ jobs:
if [[ -n '<< parameters.skip_group >>' ]]; then
args+=(--skip-group << parameters.skip_group >>)
fi
docker compose run -e SKIP_DEPS=1 \
docker-compose run -e SKIP_DEPS=1 \
-e CIRCLE_BRANCH=${CIRCLE_BRANCH} \
-e CIRCLE_JOB=${CIRCLE_JOB} \
-e SKIP_PLUGINS=<< parameters.skip_plugins >> \
@ -827,10 +507,6 @@ jobs:
-e WP_TEST_MAILER_SMTP_LOGIN=${WP_TEST_MAILER_SMTP_LOGIN} \
-e WP_TEST_MAILER_SMTP_PASSWORD=${WP_TEST_MAILER_SMTP_PASSWORD} \
-e MULTISITE=<< parameters.multisite >> \
-e ENABLE_HPOS=<< parameters.enable_hpos >> \
-e ENABLE_HPOS_SYNC=<< parameters.enable_hpos_sync >> \
-e DISABLE_HPOS=<< parameters.disable_hpos >> \
-e WORDPRESS_VERSION=${WORDPRESS_VERSION} \
codeception_integration "${args[@]}"
- store_test_results:
path: tests/_output
@ -842,7 +518,7 @@ jobs:
destination: mailhog-data
build_release_zip:
executor: wpcli_php_mysql_latest
resource_class: large
resource_class: medium+
steps:
- attach_workspace:
at: /home/circleci
@ -863,41 +539,12 @@ jobs:
root: /home/circleci/mailpoet
paths:
- mailpoet/release_zip_build_number.txt
- mailpoet/mailpoet.zip
qit_security_scan:
executor: wpcli_php_latest
steps:
- attach_workspace:
at: /home/circleci
- run:
name: 'Set up environment'
command: |
# Copy built ZIP to local directory for easier access from QIT
cp /home/circleci/mailpoet/mailpoet.zip .
# Authenticate in QIT
./vendor/bin/qit partner:add --user="${QIT_PARTNER_USER}" --application_password="${QIT_PARTNER_SECRET}"
- run:
name: 'QIT Security Test'
command: ./do qa:qit-security | tee tests/_output/qit-security
- run:
name: 'Retrieve test results'
command: |
# Download HTML report from QIT servers
grep "Result Url" tests/_output/qit-security | awk '{ print $3 }' | xargs curl -o tests/_output/report.html
when: always
- store_artifacts:
path: tests/_output
workflows:
build_and_test:
jobs:
- build:
<<: *slack-fail-post-step
- build_premium:
<<: *slack-fail-post-step
<<: *only_release
requires:
- build
- unit_tests:
<<: *slack-fail-post-step
name: unit_tests
@ -906,17 +553,13 @@ workflows:
- static_analysis:
<<: *slack-fail-post-step
name: static_analysis_php7
php_version: 70400
php_version: 70200
requires:
- build
- static_analysis:
<<: *slack-fail-post-step
name: static_analysis_php8
php_version: 80200
requires:
- build
- security_analysis:
<<: *slack-fail-post-step
php_version: 80000
requires:
- build
- qa_js:
@ -937,31 +580,12 @@ workflows:
- build
- acceptance_tests:
<<: *slack-fail-post-step
name: acceptance_tests_base_and_woo
enable_hpos: 1
name: acceptance_tests
requires:
- build
- acceptance_tests:
<<: *slack-fail-post-step
name: acceptance_tests_woo_hpos_sync_on
group: woo
enable_hpos_sync: 1
requires:
- build
- acceptance_tests:
<<: *slack-fail-post-step
name: acceptance_tests_woo_hpos_off
group: woo
disable_hpos: 1
requires:
- build
- acceptance_tests:
<<: *slack-fail-post-step
name: acceptance_tests_blockbased_theme
group: frontend
blockbased_theme: 1
requires:
- build
- unit_tests
- static_analysis_php8
- qa_js
- qa_php
- js_tests:
<<: *slack-fail-post-step
requires:
@ -969,93 +593,50 @@ workflows:
- integration_tests:
<<: *slack-fail-post-step
group: woo
enable_hpos_sync: 1
name: integration_test_woo_hpos_sync_on
name: integration_test_woocommerce
requires:
- build
- integration_tests:
<<: *slack-fail-post-step
group: woo
name: integration_test_woo_hpos_on
enable_hpos: 1
requires:
- build
- integration_tests:
<<: *slack-fail-post-step
group: woo
disable_hpos: 1
name: integration_test_woo_hpos_off
requires:
- build
- unit_tests
- static_analysis_php8
- qa_js
- qa_php
- integration_tests:
<<: *slack-fail-post-step
skip_group: woo
skip_plugins: 1
name: integration_test_base
requires:
- build
- unit_tests
- static_analysis_php8
- qa_js
- qa_php
- acceptance_tests:
<<: *slack-fail-post-step
<<: *multisite_acceptance_config
name: acceptance_tests_multisite
requires:
- build
- integration_tests:
<<: *slack-fail-post-step
<<: *only_trunk_and_release
multisite: 1
name: integration_tests_multisite
requires:
- build
- acceptance_tests:
<<: *slack-fail-post-step
<<: *only_release
name: acceptance_with_premium_latest
requires:
- build_premium
- unit_tests:
<<: *slack-fail-post-step
<<: *only_release
name: unit_with_premium_latest
requires:
- build_premium
- integration_tests:
<<: *slack-fail-post-step
<<: *only_release
name: integration_with_premium_latest
requires:
- build_premium
- unit_tests
- static_analysis_php7
- static_analysis_php8
- qa_js
- qa_php
- build_release_zip:
<<: *slack-fail-post-step
requires:
- build
- acceptance_tests_base_and_woo
- acceptance_tests
- js_tests
- integration_test_woocommerce
- integration_test_base
- integration_test_woo_hpos_on
- integration_test_woo_hpos_off
- integration_test_woo_hpos_sync_on
- integration_with_premium_latest
- acceptance_tests_woo_hpos_sync_on
- acceptance_tests_woo_hpos_off
- acceptance_tests_blockbased_theme
- static_analysis_php8
- static_analysis_php7
- unit_tests
- qa_js
- qa_php
- qa_php_oldest
- security_analysis
- qa_php_max_wporg
- qit_security_scan:
<<: *slack-fail-post-step
requires:
- build_release_zip
nightly:
triggers:
- schedule:
cron: '0 1 * * 1-5'
cron: '0 22 * * 1-5'
filters:
branches:
only:
@ -1066,40 +647,23 @@ workflows:
- acceptance_tests:
<<: *slack-fail-post-step
name: acceptance_latest
mysql_image: mysql:8.3
mysql_command: --default-authentication-plugin=mysql_native_password
requires:
- build
- acceptance_tests:
<<: *slack-fail-post-step
name: acceptance_latest_blockbased_theme
group: frontend
blockbased_theme: 1
mysql_image: mysql:8.3
mysql_command: --default-authentication-plugin=mysql_native_password
woo_core_version: latest
woo_subscriptions_version: latest
woo_memberships_version: latest
woo_blocks_version: latest
requires:
- build
- acceptance_tests:
<<: *slack-fail-post-step
name: acceptance_oldest
woo_core_version: 9.5.2
woo_subscriptions_version: 7.0.0
woo_memberships_version: 1.25.2
automate_woo_version: 6.0.33
woo_core_version: 6.6.0
woo_subscriptions_version: 4.3.0
woo_memberships_version: 1.21.0
woo_blocks_version: 8.2.0
mysql_command: --max_allowed_packet=100M
mysql_image: mysql:5.5
codeception_image_version: 7.4-cli_20220605.0
wordpress_image_version: 6.1.1-php7.4 # We use image with PHP 7.4 and install required WordPress version via CLI
wordpress_version: 6.6.2
requires:
- build
- performance_tests:
<<: *slack-fail-post-step
name: performance_latest
url: https://mpperftesting.com
us: $WP_TEST_PERFORMANCE_US
pw: $WP_TEST_PERFORMANCE_PW
scenario: nightlytests
mysql_image_version: 5.7.36
codeception_image_version: 7.4-cli_20210126.1
wordpress_image_version: wp-5.6_php7.2_20220406.1
requires:
- build
- unit_tests:
@ -1116,22 +680,12 @@ workflows:
- integration_tests:
<<: *slack-fail-post-step
name: integration_latest
mysql_image: mysql:8.3
mysql_command: --default-authentication-plugin=mysql_native_password
requires:
- build
- integration_tests:
<<: *slack-fail-post-step
name: integration_oldest
woo_core_version: 9.5.2
woo_subscriptions_version: 7.0.0
woo_memberships_version: 1.25.2
automate_woo_version: 6.0.33
codeception_image_version: 7.4-cli_20220605.0
wordpress_image_version: 6.1.1-php7.4 # We use image with PHP 7.4 and install required WordPress version via CLI # We use image with PHP 7.4 and install required WordPress version via CLI
wordpress_version: 6.6.2
mysql_command: --max_allowed_packet=100M
mysql_image: mysql:5.5
codeception_image_version: 7.2-cli_20220605.0
requires:
- build
- build_premium:
@ -1143,36 +697,6 @@ workflows:
name: acceptance_with_premium_latest
requires:
- build_premium
- integration_tests:
<<: *slack-fail-post-step
name: integration_tests_wordpress_beta
use_wordpress_beta: true
mysql_image: mysql:8.3
mysql_command: --default-authentication-plugin=mysql_native_password
requires:
- build
- integration_tests:
<<: *slack-fail-post-step
name: integration_tests_woocommerce_beta
use_woocommerce_beta: true
mysql_image: mysql:8.3
mysql_command: --default-authentication-plugin=mysql_native_password
requires:
- build
- acceptance_tests:
<<: *slack-fail-post-step
name: acceptance_tests_wordpress_beta
enable_hpos: 1
use_wordpress_beta: true
requires:
- build
- acceptance_tests:
<<: *slack-fail-post-step
name: acceptance_tests_woocommerce_beta
enable_hpos: 1
use_woocommerce_beta: true
requires:
- build
- unit_tests:
<<: *slack-fail-post-step
name: unit_with_premium_latest
@ -1183,29 +707,3 @@ workflows:
name: integration_with_premium_latest
requires:
- build_premium
- acceptance_tests:
<<: *slack-fail-post-step
name: acceptance_with_premium_oldest
woo_core_version: 9.5.2
woo_subscriptions_version: 7.0.0
woo_memberships_version: 1.25.2
automate_woo_version: 6.0.33
codeception_image_version: 7.4-cli_20220605.0
wordpress_image_version: 6.1.1-php7.4 # We use image with PHP 7.4 and install required WordPress version via CLI
wordpress_version: 6.6.2
requires:
- build_premium
- integration_tests:
<<: *slack-fail-post-step
name: integration_with_premium_oldest
woo_core_version: 9.5.2
woo_subscriptions_version: 7.0.0
woo_memberships_version: 1.25.2
automate_woo_version: 6.0.33
codeception_image_version: 7.4-cli_20220605.0
wordpress_image_version: 6.1.1-php7.4 # We use image with PHP 7.4 and install required WordPress version via CLI
wordpress_version: 6.6.2
mysql_command: --max_allowed_packet=100M
mysql_image: mysql:5.5
requires:
- build_premium

View File

@ -1,23 +0,0 @@
import * as fs from 'fs/promises';
import * as os from 'os';
// Get logical CPU count for a container on CircleCI. Adapted from:
// https://circleci.canny.io/cloud-feature-requests/p/have-nproc-accurately-reporter-number-of-cpus-available-to-container
async function cgroupCpuCount() {
const quotaS = await fs.readFile('/sys/fs/cgroup/cpu/cpu.cfs_quota_us');
const periodS = await fs.readFile('/sys/fs/cgroup/cpu/cpu.cfs_period_us');
const quota = parseInt(quotaS);
const period = parseInt(periodS);
return quota / period;
}
async function cpuCount() {
try {
return await cgroupCpuCount();
} catch {
return os.cpus().length;
}
}
cpuCount().then(console.log);

View File

@ -10,12 +10,6 @@ indent_size = 2
ij_smart_tabs = false
max_line_length = off
[packages/php/email-editor/**]
indent_style = tab
[packages/js/email-editor/**.{js,jsx,ts,tsx,scss}]
indent_style = tab
[*.php]
ij_php_align_key_value_pairs = false
ij_php_align_multiline_chained_methods = false
@ -61,5 +55,3 @@ ij_php_space_after_for_semicolon = true
ij_php_space_after_colon_in_return_type = true
ij_php_space_before_else_keyword = true
ij_php_for_statement_new_line_after_left_paren = true
ij_php_class_brace_style = end_of_line
ij_php_comma_after_last_array_element = true

View File

@ -39,15 +39,3 @@ e66c76133ec3ef667e382203426d91a4b4aa5174
# Prettier autoformatting
ab27eaee2df740c0add4331a7f8c115a87ecfa2b
# Move email editor to JS packages folder
912282f57ccc839491ff951ec5cf7aa10c14f429
# Switch email editor js packages to WP coding style
b2fb96f8793b63db629d5237010d87332330c51e
# Email editor Prettier autoformatting
8c604453b1d82e3a2c731241e1c96ea8b32ec716
# Move email editor components out of the engine folder
1c3ea9cd0a5fc8848a64d840e2fa16a6c7d8c1fe

View File

@ -21,9 +21,3 @@ _N/A_
## After-merge notes
_N/A_
## Tasks
- [ ] I followed [best practices](https://codex.wordpress.org/I18n_for_WordPress_Developers) for translations
- [ ] I added sufficient test coverage
- [ ] I embraced TypeScript by either creating new files in TypeScript or converting existing JavaScript files when making changes

View File

@ -1,212 +0,0 @@
name: Email Editor Package Tests
on:
push:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['7.4', '8.2']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Cache Composer vendor dependencies for MailPoet
id: composer-mailpoet-cache
uses: actions/cache@v4
with:
path: mailpoet/vendor
key: ${{ runner.os }}-composer-mailpoet-${{ matrix.php-version }}-${{ hashFiles('mailpoet/composer.lock') }}-${{ hashFiles('mailpoet/composer.json') }}
- name: Cache Composer vendor-prefixed dependencies for MailPoet
id: vendor-prefixed-cache
uses: actions/cache@v4
with:
path: mailpoet/vendor-prefixed
key: ${{ runner.os }}-vendor-prefixed-${{ matrix.php-version }}-${{ hashFiles('mailpoet/prefixer/composer.lock') }}-${{ hashFiles('mailpoet/prefixer/composer.json') }}
- name: Cache Composer vendor for test environment
id: composer-tests-env-cache
uses: actions/cache@v4
with:
path: tests_env/vendor
key: ${{ runner.os }}-composer-mailpoet-${{ matrix.php-version }}-${{ hashFiles('tests_env/composer.lock') }}-${{ hashFiles('tests_env/composer.json') }}
- name: Cache Composer dependencies for Email Editor
id: composer-email-editor-cache
uses: actions/cache@v4
with:
path: packages/php/email-editor/vendor
key: ${{ runner.os }}-composer-email-editor-${{ matrix.php-version }}-${{ hashFiles('packages/php/email-editor/composer.lock') }}-${{ hashFiles('packages/php/email-editor/composer.json') }}
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: gd
- name: Install tools
run: |
COMPOSER_DEV_MODE=1 php tools/install.php
touch .env
working-directory: mailpoet
# Install Test Environment dependencies only if the cache was not hit
- name: Install test environment dependencies
if: steps.composer-tests-env-cache.outputs.cache-hit != 'true'
run: ../mailpoet/tools/vendor/composer.phar install
working-directory: tests_env
# Install MailPoet dependencies only if the cache was not hit
- name: Install mailpoet dependencies
if: |
steps.composer-mailpoet-cache.outputs.cache-hit != 'true' || steps.vendor-prefixed-cache.outputs.cache-hit != 'true'
run: ./tools/vendor/composer.phar install
working-directory: mailpoet
# Install Email Editor dependencies only if the cache was not hit
- name: Install email-editor dependencies
if: steps.composer-email-editor-cache.outputs.cache-hit != 'true'
run: ../../../mailpoet/tools/vendor/composer.phar install
working-directory: packages/php/email-editor
# Dump Email Editor autoload
# This is needed to refresh classmap autoload when the composer cache is hit
- name: Dump email-editor autoload
run: ../../../mailpoet/tools/vendor/composer.phar dump-autoload
working-directory: packages/php/email-editor
# Dump MailPoet autoload
# This is needed to refresh classmap autoload when the composer cache is hit
- name: Dump MailPoet autoload
run: ./tools/vendor/composer.phar dump-autoload
working-directory: mailpoet
# Run Email Editor unit tests
- name: Run email-editor package unit tests
run: ../../../tests_env/vendor/bin/codecept build && ../../../mailpoet/tools/vendor/composer.phar unit-test
working-directory: packages/php/email-editor
- name: Run email-editor package integration tests
run: ../../../mailpoet/tools/vendor/composer.phar integration-test
working-directory: packages/php/email-editor
code-style:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- name: Install tools
run: |
COMPOSER_DEV_MODE=1 php tools/install.php
touch .env
working-directory: mailpoet
- name: Install composer dependencies
run: ../../tools/vendor/composer.phar install
working-directory: mailpoet/tasks/code_sniffer
- name: Run code style check
run: ../../../mailpoet/tools/vendor/composer.phar code-style
working-directory: packages/php/email-editor
phpstan-static-analysis:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['7.4', '8.2']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Cache Composer vendor dependencies for MailPoet
id: composer-mailpoet-cache
uses: actions/cache@v4
with:
path: mailpoet/vendor
key: ${{ runner.os }}-composer-mailpoet-${{ matrix.php-version }}-${{ hashFiles('mailpoet/composer.lock') }}-${{ hashFiles('mailpoet/composer.json') }}
- name: Cache Composer vendor-prefixed dependencies for MailPoet
id: vendor-prefixed-cache
uses: actions/cache@v4
with:
path: mailpoet/vendor-prefixed
key: ${{ runner.os }}-vendor-prefixed-${{ matrix.php-version }}-${{ hashFiles('mailpoet/prefixer/composer.lock') }}-${{ hashFiles('mailpoet/prefixer/composer.json') }}
- name: Cache Composer vendor for test environment
id: composer-tests-env-cache
uses: actions/cache@v4
with:
path: tests_env/vendor
key: ${{ runner.os }}-composer-mailpoet-${{ matrix.php-version }}-${{ hashFiles('tests_env/composer.lock') }}-${{ hashFiles('tests_env/composer.json') }}
- name: Cache Composer dependencies for Email Editor
id: composer-email-editor-cache
uses: actions/cache@v4
with:
path: packages/php/email-editor/vendor
key: ${{ runner.os }}-composer-email-editor-${{ matrix.php-version }}-${{ hashFiles('packages/php/email-editor/composer.lock') }}-${{ hashFiles('packages/php/email-editor/composer.json') }}
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: gd
- name: Install tools
run: |
COMPOSER_DEV_MODE=1 php tools/install.php
touch .env
working-directory: mailpoet
# Install Test Environment dependencies only if the cache was not hit
- name: Install test environment dependencies
if: steps.composer-tests-env-cache.outputs.cache-hit != 'true'
run: ../mailpoet/tools/vendor/composer.phar install
working-directory: tests_env
# Install MailPoet dependencies only if the cache was not hit
- name: Install mailpoet dependencies
if: |
steps.composer-mailpoet-cache.outputs.cache-hit != 'true' || steps.vendor-prefixed-cache.outputs.cache-hit != 'true'
run: ./tools/vendor/composer.phar install
working-directory: mailpoet
# Install Email Editor dependencies only if the cache was not hit
- name: Install email-editor dependencies
if: steps.composer-email-editor-cache.outputs.cache-hit != 'true'
run: ../../../mailpoet/tools/vendor/composer.phar install
working-directory: packages/php/email-editor
- name: Install composer dependencies
run: ../../tools/vendor/composer.phar install
working-directory: mailpoet/tasks/phpstan
# Dump Email Editor autoload
# This is needed to refresh classmap autoload when the composer cache is hit
- name: Dump email-editor autoload
run: ../../../mailpoet/tools/vendor/composer.phar dump-autoload
working-directory: packages/php/email-editor
# Dump MailPoet autoload
# This is needed to refresh classmap autoload when the composer cache is hit
- name: Dump MailPoet autoload
run: ./tools/vendor/composer.phar dump-autoload
working-directory: mailpoet
- name: Run code phpstan
run: ../../../mailpoet/tools/vendor/composer.phar phpstan -- --php-version=${{ matrix.php-version == '7.4' && '70400' || '80200' }}
working-directory: packages/php/email-editor

View File

@ -1,10 +0,0 @@
<?php
require_once __DIR__ . '/helpers.php';
$repository = 'woocommerce/automatewoo';
$downloadCommand = 'download:automate-woo-zip';
$configParameterName = 'automate_woo_version';
$versionsFilenameSuffix = 'automate_woo_version.txt';
replacePrivatePluginVersion($repository, $downloadCommand, $configParameterName, $versionsFilenameSuffix);

View File

@ -1,10 +0,0 @@
<?php
require_once __DIR__ . '/helpers.php';
$repository = 'woocommerce/woocommerce-memberships';
$downloadCommand = 'download:woo-commerce-memberships-zip';
$configParameterName = 'woo_memberships_version';
$versionsFilenameSuffix = 'woocommerce_memberships_version.txt';
replacePrivatePluginVersion($repository, $downloadCommand, $configParameterName, $versionsFilenameSuffix);

View File

@ -1,10 +0,0 @@
<?php
require_once __DIR__ . '/helpers.php';
$repository = 'woocommerce/woocommerce-subscriptions';
$downloadCommand = 'download:woo-commerce-subscriptions-zip';
$configParameterName = 'woo_subscriptions_version';
$versionsFilenameSuffix = 'woocommerce_subscriptions_version.txt';
replacePrivatePluginVersion($repository, $downloadCommand, $configParameterName, $versionsFilenameSuffix);

View File

@ -1,45 +0,0 @@
<?php
require_once __DIR__ . '/helpers.php';
$downloadCommand = 'download:woo-commerce-zip';
$configParameterName = 'woo_core_version';
$versionsFilenameSuffix = 'woocommerce_version.txt';
/**
* We get the official WooCommerce versions from the WordPress API.
*/
function getWooCommerceVersions(): array {
$url = "https://api.wordpress.org/plugins/info/1.0/woocommerce.json";
$response = file_get_contents($url);
$data = json_decode($response, true);
if (!isset($data['versions'])) {
die("Failed to fetch WooCommerce versions.");
}
return array_keys($data['versions']);
}
$allVersions = getWooCommerceVersions();
$stableVersions = filterStableVersions($allVersions);
[$latestVersion, $previousVersion] = getLatestAndPreviousMinorMajorVersions($stableVersions);
echo "Latest WooCommerce version: $latestVersion\n";
echo "Previous WooCommerce version: $previousVersion\n";
if ($latestVersion) {
echo "Replacing the latest version in the config file...\n";
replaceLatestVersion($latestVersion, $downloadCommand);
} else {
echo "No latest version found.\n";
}
if ($previousVersion) {
echo "Replacing the previous version in the config file...\n";
replacePreviousVersion($previousVersion, $configParameterName);
} else {
echo "No previous version found.\n";
}
saveVersionsToFiles($latestVersion, $previousVersion, $versionsFilenameSuffix);

View File

@ -1,123 +0,0 @@
<?php
require_once __DIR__ . '/helpers.php';
/**
* We try to get the current available official Docker images for WordPress.
*/
function getWordpressVersions(int $page = 1, int $pageSize = 100): array {
$url = "https://registry.hub.docker.com/v2/repositories/library/wordpress/tags?page_size={$pageSize}&page={$page}";
$response = file_get_contents($url);
$data = json_decode($response, true);
return array_column($data['results'], 'name');
}
/**
* We prefer the latest patch versions of WordPress with specified PHP versions.
* For example: 6.5.4-php8.3
*/
function filterVersions(array $versions): array {
return array_filter($versions, fn($version) => preg_match('/^\d+\.\d+\.\d+-php\d+\.\d+$/', $version));
}
/**
* We sort the versions by WordPress version and PHP version.
* The expected output is:
* - 6.5.4-php8.3
* - 6.5.4-php8.2
* - 6.5.3-php8.3
* - 6.5.3-php8.2
*/
function sortVersions(&$versions) {
usort($versions, function($a, $b) {
[$wpA, $phpA] = explode('-php', $a);
[$wpB, $phpB] = explode('-php', $b);
$wpCompare = version_compare($wpB, $wpA);
return $wpCompare !== 0 ? $wpCompare : version_compare($phpB, $phpA);
});
}
/**
* This function group docker tags by the WordPress version and returns the latest with the higher PHP version
* abd the previous with the lower PHP version.
*/
function getLatestAndPreviousVersions(array $sortedVersions): array {
$uniqueVersions = [];
foreach ($sortedVersions as $version) {
[$wpVersion] = explode('-php', $version);
$majorMinorVersion = preg_replace('/\.\d+$/', '', $wpVersion);
$uniqueVersions[$majorMinorVersion][] = $version;
}
krsort($uniqueVersions);
$latestVersionGroup = reset($uniqueVersions);
$previousVersionGroup = next($uniqueVersions);
$latestVersion = $latestVersionGroup === false ? null : reset($latestVersionGroup);
$previousVersion = $previousVersionGroup === false ? null : end($previousVersionGroup);
return [$latestVersion, $previousVersion];
}
/**
* We specify the latest WordPress version only in the docker-compose file for the tests.
*/
function replaceLatestWordPressVersion(string $latestVersion): void {
replaceVersionInFile(
__DIR__ . './../../../tests_env/docker/docker-compose.yml',
'/(wordpress:\${WORDPRESS_IMAGE_VERSION:-\s*)\d+\.\d+\.?\d*-php\d+\.\d+(})/',
'${1}' . $latestVersion . '${2}'
);
}
/**
* We use the previous WordPress version only in the CircleCI config file.
*/
function replacePreviousWordPressVersion(string $previousVersion): void {
replaceVersionInFile(
__DIR__ . './../../../.circleci/config.yml',
'/(wordpress_version: )\d+\.\d+\.?\d*/',
'${1}' . $previousVersion
);
}
$allVersions = [];
$page = 1;
$maxPages = 8;
$latestVersion = null;
$previousVersion = null;
echo "Fetching WordPress versions...\n";
// We fetch the versions until we find the latest and previous versions. But there is a limit of 4 pages.
while (($latestVersion === null || $previousVersion === null) && $page <= $maxPages) {
echo "Fetching page $page...\n";
$versions = getWordpressVersions($page);
$allVersions = array_merge($allVersions, $versions);
$allVersions = filterVersions($allVersions);
sortVersions($allVersions);
[$latestVersion, $previousVersion] = getLatestAndPreviousVersions($allVersions);
$page++;
}
echo "Latest version: $latestVersion\n";
echo "Previous version: $previousVersion\n";
if ($latestVersion) {
echo "Replacing the latest version in the docker file...\n";
replaceLatestWordPressVersion($latestVersion);
} else {
echo "No latest version found.\n";
}
if ($previousVersion) {
echo "Replacing the previous version in the config file...\n";
// We install previous WordPress version via CLI so we need a version without PHP in the name.
$previousVersion = preg_replace('/-php\d+\.\d+$/', '', $previousVersion);
replacePreviousWordPressVersion($previousVersion);
} else {
echo "No previous version found.\n";
}
saveVersionsToFiles($latestVersion, $previousVersion, 'wordpress_version.txt');

View File

@ -1,156 +0,0 @@
<?php
/**
* Function replacing versions in a file by the regex pattern.
*/
function replaceVersionInFile(string $filePath, string $pattern, string $replacement): void {
$content = file_get_contents($filePath);
if ($content === false) {
die("Failed to read the file at $filePath.");
}
$updatedContent = preg_replace($pattern, $replacement, $content);
if ($updatedContent === null || $updatedContent === $content) {
echo "Nothing to update in $filePath\n";
return;
}
if (file_put_contents($filePath, $updatedContent) === false) {
die("Failed to write the updated file at $filePath.");
}
}
/**
* Function to filter stable versions from a list of versions.
*/
function filterStableVersions(array $versions): array {
return array_filter($versions, function($version) {
// Only include stable versions (exclude versions with -rc, -beta, -alpha, etc.)
return preg_match('/^\d+\.\d+\.\d+$/', $version);
});
}
/**
* Function to get the latest and previous minor/major versions from a list of versions.
*/
function getLatestAndPreviousMinorMajorVersions(array $versions): array {
usort($versions, 'version_compare');
$currentVersion = end($versions);
$previousVersion = null;
foreach (array_reverse($versions) as $version) {
if (version_compare($version, $currentVersion, '<') && getMinorMajorVersion($version) !== getMinorMajorVersion($currentVersion)) {
$previousVersion = $version;
break;
}
}
return [$currentVersion, $previousVersion];
}
function getMinorMajorVersion(string $version): string {
$parts = explode('.', $version);
return $parts[0] . '.' . $parts[1];
}
/**
* Function to fetch tags from a GitHub repository.
*/
function fetchGitHubTags(string $repo, string $token, int $page = 1, int $limit = 50): array {
$url = "https://api.github.com/repos/$repo/tags?per_page=$limit&page=$page";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0'); // GitHub API requires a user agent
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: token $token"
]);
$response = curl_exec($ch);
curl_close($ch);
if ($response === false) {
die("Failed to fetch tags from GitHub.");
}
$data = json_decode($response, true);
if (isset($data['message']) && $data['message'] == 'Not Found') {
die("Repository not found or access denied.");
}
return array_column($data, 'name');
}
/**
* Function saving versions to a temporary files.
* File containing latest version is prefixed with 'latest_' and previous version is prefixed with 'previous_'.
*/
function saveVersionsToFiles(?string $latestVersion, ?string $previousVersion, string $fileNameSuffix): void {
file_put_contents("/tmp/latest_{$fileNameSuffix}", $latestVersion);
file_put_contents("/tmp/previous_{$fileNameSuffix}", $previousVersion);
}
function replaceLatestVersion(string $latestVersion, string $downloadCommand): void {
replaceVersionInFile(
__DIR__ . '/../../../.circleci/config.yml',
'/(.\/do ' . $downloadCommand . ' )\d+\.\d+\.\d+/',
'${1}' . $latestVersion
);
}
function replacePreviousVersion(string $previousVersion, string $configParameterName): void {
replaceVersionInFile(
__DIR__ . '/../../../.circleci/config.yml',
'/(' . $configParameterName . ': )\d+\.\d+\.\d+/',
'${1}' . $previousVersion
);
}
/**
* Function replacing the latest and previous versions of a private plugin in the config file.
* The function fetches the tags from the GitHub repository, filters stable versions,
* gets the latest and previous minor/major versions, and replaces the versions in the CircleCI config file.
*/
function replacePrivatePluginVersion(
string $repository,
string $downloadCommand,
string $configParameterName,
string $versionsFilename
): void {
// Read the GitHub token from environment variable
$token = getenv('GH_TOKEN');
if (!$token) {
die("GitHub token not found. Make sure it's set in the environment variable 'GH_TOKEN'.");
}
$page = 1;
$latestVersion = null;
$previousVersion = null;
$allVersions = [];
while (($latestVersion === null || $previousVersion === null) && $page < 10) {
$allVersions = array_merge($allVersions, fetchGitHubTags($repository, $token, $page));
$stableVersions = filterStableVersions($allVersions);
[$latestVersion, $previousVersion] = getLatestAndPreviousMinorMajorVersions($stableVersions);
$page++;
}
echo "Latest version: $latestVersion\n";
echo "Previous version: $previousVersion\n";
if ($latestVersion) {
echo "Replacing the latest version in the config file...\n";
replaceLatestVersion($latestVersion, $downloadCommand);
} else {
echo "No latest version found.\n";
}
if ($previousVersion) {
echo "Replacing the previous version in the config file...\n";
replacePreviousVersion($previousVersion, $configParameterName);
} else {
echo "No previous version found.\n";
}
saveVersionsToFiles($latestVersion, $previousVersion, $versionsFilename);
}

View File

@ -1,182 +0,0 @@
name: Check new versions of plugins and WordPress
on:
schedule:
- cron: '0 6 * * 1' # At 06:00 on Monday
workflow_dispatch: # Allows manual triggering
jobs:
check-versions:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3' # Specify the PHP version you want to use
# Updating used WordPress
- name: Check WordPress Docker Versions
run: php .github/workflows/scripts/check_wordpress_versions.php
- name: Check for WordPress changes
id: check_wp_changes
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
if [ "$(git status --porcelain)" != "" ]; then
echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
echo "WORDPRESS_CHANGES=true" >> $GITHUB_ENV
fi
- name: Get WordPress versions from files
id: get_wp_versions
run: |
echo "WORDPRESS_LATEST_VERSION=$(cat /tmp/latest_wordpress_version.txt)" >> $GITHUB_ENV
echo "WORDPRESS_PREVIOUS_VERSION=$(cat /tmp/previous_wordpress_version.txt)" >> $GITHUB_ENV
- name: Commit WordPress changes
if: env.WORDPRESS_CHANGES == 'true'
run: |
git add .
git commit -m $'Update used WordPress images in Circle CI\n\n - latest version: ${{ env.WORDPRESS_LATEST_VERSION }}\n - previous version: ${{ env.WORDPRESS_PREVIOUS_VERSION }}'
# Updating used WooCommerce plugin
- name: Check WooCommerce Versions
run: php .github/workflows/scripts/check_woocommerce_versions.php
- name: Check for WooCommerce changes
id: check_wc_changes
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
if [ "$(git status --porcelain)" != "" ]; then
echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
echo "WOOCOMMERCE_CHANGES=true" >> $GITHUB_ENV
fi
- name: Get WooCommerce versions from files
id: get_wc_versions
run: |
echo "WOOCOMMERCE_LATEST_VERSION=$(cat /tmp/latest_woocommerce_version.txt)" >> $GITHUB_ENV
echo "WOOCOMMERCE_PREVIOUS_VERSION=$(cat /tmp/previous_woocommerce_version.txt)" >> $GITHUB_ENV
- name: Commit WooCommerce changes
if: env.WOOCOMMERCE_CHANGES == 'true'
run: |
git add .
git commit -m $'Update used WooCommerce plugin in Circle CI\n\n - latest version: ${{ env.WOOCOMMERCE_LATEST_VERSION }}\n - previous version: ${{ env.WOOCOMMERCE_PREVIOUS_VERSION }}'
# Updating used Automate Woo plugin
- name: Check Automate Woo Versions
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: php .github/workflows/scripts/check_automate_woo_versions.php
- name: Check for Automate Woo changes
id: check_aw_changes
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
if [ "$(git status --porcelain)" != "" ]; then
echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
echo "AUTOMATE_WOO_CHANGES=true" >> $GITHUB_ENV
fi
- name: Get Automate Woo versions from files
id: get_aw_versions
run: |
echo "AUTOMATE_WOO_LATEST_VERSION=$(cat /tmp/latest_automate_woo_version.txt)" >> $GITHUB_ENV
echo "AUTOMATE_WOO_PREVIOUS_VERSION=$(cat /tmp/previous_automate_woo_version.txt)" >> $GITHUB_ENV
- name: Commit Automate Woo changes
if: env.AUTOMATE_WOO_CHANGES == 'true'
run: |
git add .
git commit -m $'Update used Automate Woo plugin in Circle CI\n\n - latest version: ${{ env.AUTOMATE_WOO_LATEST_VERSION }}\n - previous version: ${{ env.AUTOMATE_WOO_PREVIOUS_VERSION }}'
# Updating used WooCommerce Subscriptions plugin
- name: Check WooCommerce Subscriptions Versions
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: php .github/workflows/scripts/check_woocommerce_subscriptions_versions.php
- name: Check for WooCommerce Subscriptions changes
id: check_ws_changes
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
if [ "$(git status --porcelain)" != "" ]; then
echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
echo "SUBSCRIPTIONS_CHANGES=true" >> $GITHUB_ENV
fi
- name: Get WooCommerce Subscriptions versions from files
id: get_ws_versions
run: |
echo "WOOCOMMERCE_SUBSCRIPTIONS_LATEST_VERSION=$(cat /tmp/latest_woocommerce_subscriptions_version.txt)" >> $GITHUB_ENV
echo "WOOCOMMERCE_SUBSCRIPTIONS_PREVIOUS_VERSION=$(cat /tmp/previous_woocommerce_subscriptions_version.txt)" >> $GITHUB_ENV
- name: Commit WooCommerce Subscriptions changes
if: env.SUBSCRIPTIONS_CHANGES == 'true'
run: |
git add .
git commit -m $'Update used WooCommerce Subscriptions plugin in Circle CI\n\n - latest version: ${{ env.WOOCOMMERCE_SUBSCRIPTIONS_LATEST_VERSION }}\n - previous version: ${{ env.WOOCOMMERCE_SUBSCRIPTIONS_PREVIOUS_VERSION }}'
# Updating used WooCommerce Memberships plugin
- name: Check WooCommerce Memberships Versions
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: php .github/workflows/scripts/check_woocommerce_memberships_versions.php
- name: Check for WooCommerce Memberships changes
id: check_wm_changes
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
if [ "$(git status --porcelain)" != "" ]; then
echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
echo "MEMBERSHIPS_CHANGES=true" >> $GITHUB_ENV
fi
- name: Get WooCommerce Memberships versions from files
id: get_wm_versions
run: |
echo "WOOCOMMERCE_MEMBERSHIPS_LATEST_VERSION=$(cat /tmp/latest_woocommerce_memberships_version.txt)" >> $GITHUB_ENV
echo "WOOCOMMERCE_MEMBERSHIPS_PREVIOUS_VERSION=$(cat /tmp/previous_woocommerce_memberships_version.txt)" >> $GITHUB_ENV
- name: Commit WooCommerce Memberships changes
if: env.MEMBERSHIPS_CHANGES == 'true'
run: |
git add .
git commit -m $'Update used WooCommerce Memberships plugin in Circle CI\n\n - latest version: ${{ env.WOOCOMMERCE_MEMBERSHIPS_LATEST_VERSION }}\n - previous version: ${{ env.WOOCOMMERCE_MEMBERSHIPS_PREVIOUS_VERSION }}'
# Push all changes at the end if any changes were detected
#
# For local testing with act tool add following:
# env:
# GH_PAT: ${{ secrets.GH_TOKEN }}
# run: |
# git remote set-url origin https://${GH_PAT}@github.com/mailpoet/mailpoet
# git push -f origin HEAD:refs/heads/update-plugins-and-wordpress-test
- name: Push changes
if: env.CHANGES_DETECTED == 'true'
run: |
git push -f origin HEAD:refs/heads/update-plugins-and-wordpress
# Create a pull request if there are changes
- name: Create Pull Request
if: env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GH_TOKEN }}
branch: update-plugins-and-wordpress
title: Update WordPress and plugins in CI jobs
base: trunk
labels: automated, check-versions
body: |
1. If all checks passed, you can merge this PR.
2. If the build failed, please investigate the failure and either address the issues or delegate the job. Then, make sure these changes are merged.

View File

@ -1,27 +0,0 @@
name: Add link to WordPress Playground preview
on:
pull_request:
types: [opened, reopened, synchronize]
jobs:
add-wp-playground-link:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Check and append description
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER="${{ github.event.pull_request.number }}"
BRANCH_NAME="${{ github.head_ref }}"
DESCRIPTION="$(gh pr view $PR_NUMBER --json body -q .body)"
HEADING="## Preview"
CONTENT="$(printf "${HEADING}\n\n[Preview in WordPress Playground](https://account.mailpoet.com/playground/new/branch:${BRANCH_NAME})\n\n_The latest successful build from \`${BRANCH_NAME}\` will be used. If none is available, the link won't work._")"
if [[ "$DESCRIPTION" != *"$HEADING"* ]]; then
gh pr edit $PR_NUMBER --body "$(printf "${DESCRIPTION}\n\n${CONTENT}")"
fi

5
.gitignore vendored
View File

@ -7,7 +7,4 @@ docker-compose.override.yml
node_modules
npm-debug.log
mailpoet-premium
tsconfig.tsbuildinfo
/wordpress
packages/php/*/vendor
tests_env/vendor
wordpress

View File

@ -1,41 +0,0 @@
#!/usr/bin/env bash
. "$(dirname "$0")/../mailpoet/.env"
export MP_GIT_HOOKS_ENABLE="${MP_GIT_HOOKS_ENABLE:-true}"
export MP_GIT_HOOKS_ESLINT="${MP_GIT_HOOKS_ESLINT:-true}"
export MP_GIT_HOOKS_STYLELINT="${MP_GIT_HOOKS_STYLELINT:-true}"
export MP_GIT_HOOKS_PHPLINT="${MP_GIT_HOOKS_PHPLINT:-true}"
export MP_GIT_HOOKS_CODE_SNIFFER="${MP_GIT_HOOKS_CODE_SNIFFER:-true}"
export MP_GIT_HOOKS_PHPSTAN="${MP_GIT_HOOKS_PHPSTAN:-true}"
export MP_GIT_HOOKS_INSTALL_JS="${MP_GIT_HOOKS_INSTALL_JS:-false}"
export MP_GIT_HOOKS_INSTALL_PHP="${MP_GIT_HOOKS_INSTALL_PHP:-false}"
fileChanged() {
local filePattern="$1"
local changedFiles="$2"
if echo "$changedFiles" | grep -qE "$filePattern"; then
return 0
else
return 1
fi
}
installIfUpdates() {
local changedFiles="$(git diff-tree -r --name-only --no-commit-id HEAD@{1} HEAD)"
if [ "$MP_GIT_HOOKS_INSTALL_JS" = "true" ] && fileChanged "pnpm-lock.yaml" "$changedFiles"; then
echo "Change detected in pnpm-lock.yaml, running do install:js"
pushd mailpoet
./do install:js
popd
fi
if [ "$MP_GIT_HOOKS_INSTALL_PHP" = "true" ] && fileChanged "mailpoet/composer.lock" "$changedFiles"; then
echo "Change detected in mailpoet/composer.lock, running do install:php"
pushd mailpoet
./do install:php
popd
fi
}

View File

@ -1,8 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
. "$(dirname "$0")/common.sh"
[ "$MP_GIT_HOOKS_ENABLE" != "true" ] && exit 0
installIfUpdates
./do cleanup:cached-files

View File

@ -1,7 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
. "$(dirname "$0")/common.sh"
[ "$MP_GIT_HOOKS_ENABLE" != "true" ] && exit 0
installIfUpdates

View File

@ -1,7 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
. "$(dirname "$0")/common.sh"
[ "$MP_GIT_HOOKS_ENABLE" != "true" ] && exit 0
installIfUpdates

View File

@ -1,8 +1,5 @@
#!/bin/sh
. "$(dirname "$0")/common.sh"
[ "$MP_GIT_HOOKS_ENABLE" != "true" ] && exit 0
. "$(dirname "$0")/_/husky.sh"
npx lint-staged -c mailpoet/package.json --cwd mailpoet
npx lint-staged -c package.json
npx lint-staged -c packages/js/email-editor/package.json --cwd packages/js/email-editor
npx lint-staged -c packages/php/email-editor/.lintstagedrc.json --cwd packages/php/email-editor

2
.npmrc
View File

@ -1,2 +1,2 @@
auto-install-peers=false
save-workspace-protocol=rolling
strict-peer-dependencies=false

2
.nvmrc
View File

@ -1 +1 @@
v19.7.0
v17.9.1

View File

@ -1,22 +0,0 @@
function readPackage(pkg) {
// Resolve @wordpress/* dependencies of @woocommerce packages to those used by MailPoet.
// This avoids their duplication and downgrading due to @woocommerce pinning them to wp-6.0.
// This should be removed once we adopt similar pinning strategy and use dependency extraction.
// See: https://github.com/woocommerce/woocommerce/pull/37034
if (pkg.name?.startsWith('@woocommerce/')) {
pkg.dependencies = Object.fromEntries(
Object.entries(pkg.dependencies).map(([name, version]) =>
name.startsWith('@wordpress/') || name.startsWith('@types/wordpress__')
? [name, '*']
: [name, version],
),
);
}
return pkg;
}
module.exports = {
hooks: {
readPackage,
},
};

View File

@ -1,4 +1,5 @@
*.hbs
*.scss
.mp_svn
_generated
_output
@ -12,18 +13,15 @@ vendor-prefixed
/dev/data
/mailpoet/assets/dist
/mailpoet/assets/js/lib
/mailpoet/assets/js/src/newsletter-editor/behaviors/tinymce-icons.js
/mailpoet/assets/js/src/newsletter_editor/behaviors/tinymce_icons.js
/mailpoet/generated
/mailpoet/lang
/mailpoet/lib/Newsletter/Renderer/Template.html
/mailpoet/lib-3rd-party
/mailpoet/plugin_repository
/mailpoet/temp
/mailpoet/tests/javascript-newsletter-editor/testBundles
/mailpoet/tests/javascript_newsletter_editor/testBundles
/mailpoet/tests/plugins
/mailpoet/tools/wpscan-semgrep-rules
/mailpoet/views
/mailpoet-premium
/wordpress
/packages/php/email-editor
/packages/js/email-editor

View File

@ -1,30 +0,0 @@
clone:
git:
image: woodpeckerci/plugin-git
settings:
depth: 1
steps:
build:
image: node:current-bookworm-slim
commands:
- apt update
- apt install php php-symfony bash -y
- npm install pnpm
- cd mailpoet
- bash build.sh
- mkdir ../output
- mv mailpoet.zip ../output
- cd ..
release:
image: woodpeckerci/plugin-gitea-release:latest
settings:
base_url: https://git.cavemanon.xyz
api_key:
from_secret: releasesmithapikey
files: "output/"
prerelease: false
title: "${CI_COMMIT_TAG}"
when:
- event: tag

View File

@ -1,51 +1,56 @@
# Contributing
There is a `./do` command that helps with the development process. See [README](README.md) for more details.
## PHP Code
- Two spaces indentation.
- Space between keyword and left bracket (`if ()`, `for ()`, `switch ()`...).
- `CamelCase` for classes.
- `camelCase` for methods.
- `snake_case` for variables and class properties.
- Space between keyword (if, for, switch...) and left bracket
- CamelCase for classes.
- camelCase for methods.
- snake_case for variables and class properties.
- Composition over Inheritance.
- Comments are a code smell. If you need to use a comment - see if same idea can be achieved by more clearly expressing code.
- Require other classes with `use` at the beginning of the class file.
- Require other classes with 'use' at the beginning of the class file.
- Do not specify 'public' if method is public, it's implicit.
- Always use guard clauses.
- Ensure compatibility with PHP 7.4 and newer versions.
- Ensure compatibility with PHP 7.1 and newer versions.
- Cover your code in tests.
## SCSS Code
- `kebab-case` for file names.
- Components files are prefixed with underscore, to indicate, that they aren't compiled separately (`_new-component.scss`).
- 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
- Default to TypeScript for new 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:
- 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. */`
- For PHP we do the same with the exception `// phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps` which for now doesnt require an explanation
- for PHP we do the same with the exception `// phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps` which for now doesnt require an explanation
## Git flow
- Do not commit to trunk.
- Open a short-living feature branch.
- Use good commit messages as explained here https://chris.beams.io/posts/git-commit. Include Jira ticket in the commit message.
- Open a pull request.
- Add Jira issue reference in the title of the Pull Request.
- Work on the pull request.
- Use the `./do qa` command to check your code style before pushing.
- Create a pull request when finished. Include Jira ticket in the title of the pull request.
- Use good commit messages as explained here https://chris.beams.io/posts/git-commit
- Wait for review from another developer.
## Feature flags
## Issues creation
We use feature flags to control the visibility of new features. This allows us to work on new features in smaller chunks before they are released to all customers.
- 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.
- Feature flags can be enabled on the experimental page: `/admin.php?page=mailpoet-experimental`.
- New feature flags can be added in the class `FeaturesController`.
## 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/trunk/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/trunk/mailpoet/lib/Entities).

120
README.md
View File

@ -1,41 +1,20 @@
### Table of Contents
1. [MailPoet](#mailpoet)
2. [Initial setup](#initial-setup)
1. [Additional dependencies](#additional-dependencies)
3. [Xdebug](#xdebug)
1. [PhpStorm setup](#phpstorm-setup)
2. [Xdebug develop mode](#xdebug-develop-mode)
3. [Xdebug for integration tests](#xdebug-for-integration-tests)
4. [Local development](#local-development)
1. [NFS volume sharing for Mac](#nfs-volume-sharing-for-mac)
2. [Husky hooks](#husky-hooks)
5. [Docker](#docker)
1. [Commands](#commands)
2. [Available PHP versions](#available-php-versions)
3. [Disabling the Tracy panel](#disabling-the-tracy-panel)
4. [Running individual tests](#running-individual-tests)
6. [TODO](#todo)
## MailPoet
# MailPoet
The **MailPoet** plugin monorepo.
If you have **any questions or need help or support**, please see the [Support](SUPPORT.md) document.
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
## 🔌 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.
### Additional dependencies
## Additional dependencies
Even though it's possible to run everything using Docker, in the development workflow,
Even though it possible to run everything using Docker, in the development workflow,
it may be faster and more convenient to run some tasks outside the container. Therefore,
the following tools are recommended:
@ -43,9 +22,7 @@ the following tools are recommended:
2. **Node.js**, as specified by `.nvmrc`. For automatic management use [nvm](https://github.com/nvm-sh/nvm), [FNM](https://github.com/Schniz/fnm), or [Volta](https://github.com/volta-cli/volta).
3. **pnpm**, as specified in `package.json`. For automatic setup enable [Corepack](https://nodejs.org/docs/latest-v17.x/api/corepack.html) using `corepack enable`.
## Xdebug
### PhpStorm setup
## 🔍 PHPStorm setup for XDebug
In `Languages & Preferences > PHP > Servers` set path mappings:
@ -62,7 +39,7 @@ To use XDebug inside the **cron**, you need to pass a URL argument `&XDEBUG_TRIG
[in the cron request](https://github.com/mailpoet/mailpoet/blob/bf7bd6d2d9090ed6ec7b8b575bb7d6b08e663a52/lib/Cron/CronHelper.php#L155-L166).
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
[Xdebug develop mode](https://xdebug.org/docs/develop) is disabled by default because it causes performance issues due to conflicts with the DI container.
@ -73,7 +50,7 @@ environment:
XDEBUG_MODE: debug, develop
```
### Xdebug for integration tests
## Xdebug for integration tests
- In Languages & Preferences > PHP > Servers create a new sever named `MailPoetTest`, set the host to `localhost` and port to `80` and set following path mappings:
@ -85,12 +62,10 @@ mailpoet/vendor/bin/codecept -> /project/vendor/bin/codecept
mailpoet/vendor/bin/wp -> /usr/local/bin/wp
```
- Add `XDEBUG_TRIGGER: 1` environment to `tests_env/docker/docker-compose.yml` -> codeception service to start triggering Xdebug
- Add `XDEBUG_TRIGGER: 1` environment to `mailpoet/tests/docker/docker-compose.yml` -> codeception service to start triggering Xdebug
- Make PHPStorm listen to connections by clicking on the phone icon
## Local development
### NFS volume sharing for Mac
## 💾 NFS volume sharing for Mac
NFS volumes can bring more stability and performance on Docker for Mac. To setup NFS volume sharing run:
@ -103,14 +78,14 @@ Then create a Docker Compose override file with NFS settings and restart contain
```shell
cp docker-compose.override.macos-sample.yml docker-compose.override.yml
docker compose down -v --remove-orphans
docker compose up -d
docker-compose down -v --remove-orphans
docker-compose up -d
```
**NOTE:** If you are on MacOS Catalina or newer, make sure to put the repository
outside your `Documents` folder, otherwise you may run into [file permission issues](https://objekt.click/2019/11/docker-the-problem-with-macos-catalina/).
### Husky hooks
# 🐶 Husky
We use [Husky](https://github.com/typicode/husky) to run automated checks in pre-commit hooks.
@ -125,9 +100,7 @@ export NVM_DIR="$HOME/.nvm"
Without it, you may experience errors in some Git clients.
## Docker
### Commands
## 🕹 Commands
The `./do` script define aliases for most of the commands you will need while working on plugins:
@ -149,70 +122,29 @@ Options:
You can access this help in your command line running `./do` without parameters.
### Available PHP versions
## ✉️ Adding new templates to the plugin
To switch the environment to a different PHP version:
[Read the article.](https://mailpoet.atlassian.net/wiki/spaces/MAILPOET/pages/629374977/Adding+new+templates+to+the+plugin)
1. Check https://github.com/mailpoet/mailpoet/tree/trunk/dev for a list of available PHP versions. Each directory starting with `php` corresponds to a available version.
2. Configure the `wordpress` service in `docker-compose.override.yml` to build from the desired PHP version Dockerfile (replace {PHP_VERSION} with the name of the directory that corresponds to the version that you want to use):
## 🚥 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:
```yaml
wordpress:
build:
context: .
dockerfile: dev/{PHP_VERSION}/Dockerfile
dockerfile: dev/php74/Dockerfile # OR dev/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 the default PHP version remove what was added in 2) and, run `docker compose build wordpress` for application container and `docker compose build test_wordpress` for tests container,
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`.
### Disabling the Tracy panel
## ✅ TODO
To disable the Tracy panel, add the following to `docker-compose.override.yml`:
```yaml
services:
wordpress:
environment:
MAILPOET_DISABLE_TRACY_PANEL: 1
```
### Running individual tests
It's recommended to run tests in Docker. Free plugin tests can be run using --test flag (`./do --test`). However, to run a premium test, you need to ssh into test container (`./do ssh --test`) and run tests there.
#### Integration test in the free plugin
```shell
./do --test test:integration --skip-deps --file=tests/integration/WP/EmojiTest.php
```
#### Acceptance test in the free plugin
```shell
./do --test test:acceptance --skip-deps --file=tests/acceptance/Misc/MailpoetMenuCest.php
```
#### Unit/integration test in the premium plugin
```shell
./do ssh --test # to enter the container
cd ../mailpoet-premium # switch to premium plugin directory
./do test:unit --file=tests/unit/Config/EnvTest.php
```
#### Acceptance test in the premium plugin
```shell
cd ./mailpoet-premium # switch to premium plugin directory on your local machine
./do test:acceptance --skip-deps --file tests/acceptance/PremiumCheckCest.php
```
## TODO
- [ ] Install WooCommerce
- [ ] Install Members
- [ ] Install other useful plugins by default
- install woo commerce, members and other useful plugins by default

View File

@ -1,5 +1,23 @@
# Support
# Getting Support
Please visit our [Support](https://www.mailpoet.com/support/) page. We have a team of Happiness Engineers ready to help you.
Welcome to MailPoet!
This isn't the right place to get support for using MailPoet,
but the following resources are available below,
thanks for understanding.
For feature requests, please use our [tracker](https://feedback.mailpoet.com).
- [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.
Questions posed to the issue tracker will be closed.
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
was used to reproduce the issue.
You may also submit a failing test case as a pull request.

View File

@ -1,8 +0,0 @@
export default {
presets: [
['@babel/preset-react', { runtime: 'automatic' }],
'@babel/preset-env',
'@babel/preset-typescript',
],
plugins: [['@babel/plugin-transform-runtime']],
};

View File

@ -21,7 +21,7 @@ mkdir -p wordpress/wp-content/plugins/mailpoet-premium
mkdir -p dev/data/mailhog
for plugin in "mailpoet" "mailpoet-premium"; do
docker compose run --rm wordpress /bin/sh -c "
docker-compose run --rm wordpress /bin/sh -c "
[ -d /var/www/html/wp-content/plugins/$plugin ] &&
cd /var/www/html/wp-content/plugins/$plugin &&
./do install &&
@ -29,7 +29,7 @@ for plugin in "mailpoet" "mailpoet-premium"; do
"
done
docker compose run --rm wordpress /bin/sh -c "
docker-compose run --rm wordpress /bin/sh -c "
cd /var/www/templates &&
mkdir assets classes exported
"

View File

@ -29,6 +29,6 @@ cat <<EOT
NFS volume sharing is set up. Recreate your containers and volumes using:
cp docker-compose.override.macos-sample.yml docker-compose.override.yml
docker compose down -v --remove-orphans
docker compose up -d
docker-compose down -v --remove-orphans
docker-compose up -d
EOT

View File

@ -10,7 +10,7 @@ RUN apt-get update \
&& pecl install xdebug-2.9.8 && \
\
# Install NodeJS, enable Corepack
curl -sL https://deb.nodesource.com/setup_19.x | bash - && \
curl -sL https://deb.nodesource.com/setup_17.x | bash - && \
apt-get install -y nodejs build-essential && \
corepack enable && \
\

View File

@ -8,7 +8,7 @@ RUN apt-get update \
&& apt-get install -y git zlib1g-dev libzip-dev zip wget gnupg msmtp libpng-dev gettext subversion \
&& \
# Install NodeJS, enable Corepack
curl -sL https://deb.nodesource.com/setup_19.x | bash - && \
curl -sL https://deb.nodesource.com/setup_17.x | bash - && \
apt-get install -y nodejs build-essential && \
corepack enable && \
\
@ -27,7 +27,7 @@ RUN printf "account default\nhost smtp\nport 1025" > /etc/msmtprc
# xdebug build an config
ENV XDEBUGINI_PATH=/usr/local/etc/php/conf.d/xdebug.ini
RUN git clone -b "3.3.1" --depth 1 https://github.com/xdebug/xdebug.git /usr/src/php/ext/xdebug \
RUN git clone -b "3.0.2" --depth 1 https://github.com/xdebug/xdebug.git /usr/src/php/ext/xdebug \
&& docker-php-ext-configure xdebug --enable-xdebug-dev \
&& docker-php-ext-install xdebug \
&& mkdir /tmp/debug

View File

@ -8,7 +8,7 @@ RUN apt-get update \
&& apt-get install -y git zlib1g-dev libzip-dev zip wget gnupg msmtp libpng-dev gettext subversion \
&& \
# Install NodeJS, enable Corepack
curl -sL https://deb.nodesource.com/setup_19.x | bash - && \
curl -sL https://deb.nodesource.com/setup_17.x | bash - && \
apt-get install -y nodejs build-essential && \
corepack enable && \
\
@ -27,7 +27,7 @@ RUN printf "account default\nhost smtp\nport 1025" > /etc/msmtprc
# xdebug build an config
ENV XDEBUGINI_PATH=/usr/local/etc/php/conf.d/xdebug.ini
RUN git clone -b "3.3.1" --depth 1 https://github.com/xdebug/xdebug.git /usr/src/php/ext/xdebug \
RUN git clone -b "3.1.1" --depth 1 https://github.com/xdebug/xdebug.git /usr/src/php/ext/xdebug \
&& docker-php-ext-configure xdebug --enable-xdebug-dev \
&& docker-php-ext-install xdebug \
&& mkdir /tmp/debug

View File

@ -1,46 +0,0 @@
FROM wordpress:php8.2-apache
ARG UID=1000
ARG GID=1000
# additinal extensions
RUN apt-get update \
&& apt-get install -y git zlib1g-dev libzip-dev zip wget gnupg msmtp libpng-dev gettext subversion \
&& \
# Install NodeJS, enable Corepack
curl -sL https://deb.nodesource.com/setup_19.x | bash - && \
apt-get install -y nodejs build-essential && \
corepack enable && \
\
# Install WP-CLI
curl -o /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \
chmod +x /usr/local/bin/wp && \
\
# Clean up
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY dev/php.ini /usr/local/etc/php/conf.d/php_user.ini
# msmtp config
RUN printf "account default\nhost smtp\nport 1025" > /etc/msmtprc
# xdebug build an config
ENV XDEBUGINI_PATH=/usr/local/etc/php/conf.d/xdebug.ini
RUN git clone -b "3.3.1" --depth 1 https://github.com/xdebug/xdebug.git /usr/src/php/ext/xdebug \
&& docker-php-ext-configure xdebug --enable-xdebug-dev \
&& docker-php-ext-install xdebug \
&& mkdir /tmp/debug
COPY dev/xdebug.ini /tmp/xdebug.ini
RUN cat /tmp/xdebug.ini >> $XDEBUGINI_PATH
# php extensions
RUN docker-php-ext-install pdo_mysql
RUN docker-php-ext-install mysqli
# allow .htaccess files (between <Directory /var/www/> and </Directory>, which is WordPress installation)
RUN sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
# ensure existing content in /var/www/html respects UID and GID, give Node permissions for Corepack
RUN chown -R ${UID}:${GID} /var/www/html && \
mkdir -p /.node && chown -R ${UID}:${GID} /.node

16
do
View File

@ -3,8 +3,8 @@
function syntax {
cat << EOF
./do setup Setup the dev environment.
./do start Start the docker containers (docker compose up -d).
./do stop Stop the docker containers (docker compose stop).
./do start Start the docker containers (docker-compose up -d).
./do stop Stop the docker containers (docker-compose stop).
./do ssh [--test] Run an interactive bash shell inside the plugin directory.
./do run [--test] <command> Run a custom bash command in the wordpress container.
./do acceptance [--premium] Run acceptance tests.
@ -21,7 +21,7 @@ EOF
function ssh_and_run {
params=("$@")
params=("${params[@]:1}")
docker compose exec $1 bash -c "${params[@]}"
docker-compose exec $1 bash -c "${params[@]}"
}
if [ "$1" = "" -o "$1" = "--help" ]; then
@ -31,10 +31,10 @@ elif [ "$1" = "setup" ]; then
./dev/initial-setup.sh
elif [ "$1" = "start" ]; then
docker compose up -d
docker-compose up -d
elif [ "$1" = "stop" ]; then
docker compose stop
docker-compose stop
elif [ "$1" = "run" ]; then
params=("$@")
@ -54,9 +54,9 @@ elif [ "$1" = "ssh" ]; then
fi
if [ "$2" = "--test" ] || [ "$3" = "--test" ]; then
docker compose exec --workdir $dir test_wordpress bash
docker-compose exec --workdir $dir test_wordpress bash
else
docker compose exec --workdir $dir wordpress bash
docker-compose exec --workdir $dir wordpress bash
fi
elif [ "$1" = "acceptance" ]; then
@ -65,7 +65,7 @@ elif [ "$1" = "acceptance" ]; then
else
cd mailpoet
fi
COMPOSE_HTTP_TIMEOUT=200 docker compose run codeception_acceptance -e KEEP_DEPS=1 --steps --debug -vvv
COMPOSE_HTTP_TIMEOUT=200 docker-compose run codeception_acceptance -e KEEP_DEPS=1 --steps --debug -vvv
cd ..
elif [ "$1" = "build" ]; then

View File

@ -27,21 +27,15 @@ Class `\MailPoet\API\API` becomes available once MailPoet plugin is loaded by Wo
- [Add List (addList)](api_methods/AddList.md)
- [Add Subscriber (addSubscriber)](api_methods/AddSubscriber.md)
- [Update Subscriber (updateSubscriber)](api_methods/UpdateSubscriber.md)
- [Add Subscriber Field (addSubscriberField)](api_methods/AddSubscriberField.md)
- [Delete List (deleteList)](api_methods/DeleteList.md)
- [Get Lists (getLists)](api_methods/GetLists.md)
- [Get Subscriber (getSubscriber)](api_methods/GetSubscriber.md)
- [Get Subscribers (getSubscribers)](api_methods/GetSubscribers.md)
- [Get Subscribers Count (getSubscribersCount)](api_methods/GetSubscribersCount.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 Lists (subscribeToLists)](api_methods/SubscribeToLists.md)
- [Unsubscribe globally](api_methods/UnsubscribeGlobally.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)
- [Update List (updateList)](api_methods/UpdateList.md)
### Usage examples

View File

@ -1,6 +1,6 @@
[back to list](../Readme.md)
# Add List
# Add Subscriber
## `array addList(array $list)`

View File

@ -1,27 +0,0 @@
[back to list](../Readme.md)
# Delete List
## `bool deleteList(string $list_id)`
This method provides functionality for deleting a list that is of the type 'default'.
It returns a boolean value.
## Error handling
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 |
| ---- | --------------------------------------------------------------- |
| 5 | List does not exist |
| 18 | List id is empty |
| 20 | List cannot be deleted because its used for an automatic email |
| 21 | List cannot be deleted because its used for a form |
| 22 | The list couldnt be deleted from the database |
| 23 | Only lists of the type 'default' can be deleted |

View File

@ -31,12 +31,10 @@ This method throws an `\Exception` in the event a subscriber with a given email
| 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 in not null in case that list is in trash and contains UTC time in 'Y-m-d H:i:s' format. |
| last_subscribed_at | string\|null | - | UTC time of last confirmed subscription in 'Y-m-d H:i:s' format. |
| unconfirmed_data | string\|null | 65K chars | May contain serialized subscriber data in case when there are pending changes waiting for a confirmation from a subscriber |
| source | string\|null | - | Possible values: `form`,`imported`,`administrator`,`api`,`wordpress_user`,`woocommerce_user`,`woocommerce_checkout`,`unknown`) |
| count_confirmations | string | 11 chars | Counter for confirmation emails |
| subscriptions | array | - | List of subcriber subscriptions |
| tags | array | - | List of subcriber tags |
| cf\_{custom_field['id']} | string | 65K chars | A custom subscriber field value (see [Get Subscriber Fields](GetSubscriberFields.md) |
### Subscriber's subscription
@ -50,17 +48,6 @@ This method throws an `\Exception` in the event a subscriber with a given email
| created_at | string | - | 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 |
### Subscriber's tag
| Property | Type | Limits | Description |
| ------------- | ------ | -------- | ----------------------------------------------- |
| id | string | 11 chars | Id of relation |
| subscriber_id | string | 11 chars | Id of subscriber |
| tag_id | string | 11 chars | Id of a list |
| name | string | - | Name of a tag |
| created_at | string | - | 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 |
### Response Example
```php
@ -100,24 +87,6 @@ This method throws an `\Exception` in the event a subscriber with a given email
'updated_at' => '2019-05-14 08:43:08',
],
],
'tags' => [
0 => [
'id' => '2',
'subscriber_id' => '10',
'tag_id' => '1',
'name' => 'Alpha',
'created_at' => '2019-05-17 05:24:37',
'updated_at' => '2019-05-17 05:24:37',
],
1 => [
'id' => '4',
'subscriber_id' => '10',
'tag_id' => '5',
'name' => 'Beta',
'created_at' => '2020-03-07 15:21:37',
'updated_at' => '2020-03-07 15:21:37',
],
],
'cf_1' => 'US',
'cf_2' => 'New York',
];

View File

@ -1,25 +0,0 @@
[back to list](../Readme.md)
# Get Subscribers
## `array getSubscribers(array $filter = [], int $limit = 50, int $offset = 0)`
This method returns a list of subscribers. To see the subscriber data structure, please check [getSubscriber()](GetSubscriber.md) documentation.
## Arguments
| Argument | Type | Default | Description |
| ------------------ | ----- | ------- | --------------------------------------- |
| $filter (optional) | array | empty | Filters to retrieve subscribers |
| $limit (optional) | int | 50 | The number of results that are returned |
| $offset (optional) | int | 0 | From where to start returning data |
### Filter
Filter argument supports following array keys.
| Key | Type | Description |
| ------------ | ------------ | ----------------------------------------------------------------------------------------------------------------- |
| status | string | Specific status of subscribers. One of values: `unconfirmed`, `subscribed`, `unsubscribed`, `bounced`, `inactive` |
| listId | int | List id or dynamic segment id |
| minUpdatedAt | DateTime\int | DateTime object or timestamp of the minimal last update of subscribers |

View File

@ -1,17 +0,0 @@
[back to list](../Readme.md)
# Get Subscribers Count
## `int getSubscribersCount(array $filter = [])`
This method returns the count of subscribers by a filter.
## Arguments
| Argument | Type | Default | Description |
| ------------------ | ----- | ------- | -------------------------------------------- |
| $filter (optional) | array | empty | Filters to retrieve the count of subscribers |
### Filter
To see supported filters, please check [getSubscribers()](GetSubscribers.md) documentation.

View File

@ -1,29 +0,0 @@
[back to list](../Readme.md)
# Unsubscribe from all lists and change subscriber status
## `array unsubscribe(string $subscriber_id)`
This method removes a subscriber from all lists and updates its status to 'unsubscribed'.
It returns a subscriber. See [Get Subscriber](GetSubscriber.md) for returned data structure.
## Arguments
### string `$subscriber_id` (required)
An id or email of an existing subscriber. An `\Exception` is thrown when an id or email doesn't match any subscriber.
## Error handling
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 |
| ---- | -------------------------------------------- |
| 4 | Invalid subscriber that does not exist |
| 24 | Subscriber already has 'unsubscribed' status |

View File

@ -1,39 +0,0 @@
[back to list](../Readme.md)
# Update List
## `array updateList(array $list)`
This method provides functionality for updating a list name or description. Only lists of type 'default' are supported.
It returns the updated 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 |
| ---------------------- | ------------ | --------- | -------------------------------------------------------------------------- |
| id (required) | string | 11 chars | A id of the list. |
| name (required) | string | 90 chars | A name of the list. |
| description (optional) | string\|null | 250 chars | A description of the list. This will reset the list description when empty |
## Error handling
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 |
| ---- | ----------------------------------------------- |
| 5 | The list was not found by id |
| 14 | Missing list name |
| 15 | Trying to use a list name that is already used |
| 18 | Missing list id |
| 19 | The list couldnt be updated in the database |
| 23 | Only lists of the type 'default' can be updated |

View File

@ -1,33 +0,0 @@
[back to list](../Readme.md)
# Update Subscriber
## `array updateSubscriber($subscriberIdOrEmail, array $subscriber): array`
This method allows a subscriber to be updated.
The argument `$subscriber` is similar to [Add Subscriber](AddSubscriber.md) method, but the subscriber is updated instead of created.
It returns the updated subscriber. See [Get Subscriber](GetSubscriber.md) for a subscriber data structure.
If the subscriber is a WordPress user, the method does not allow updating `email`, `first_name` and `last_name`. It needs to be updated in the `wp_users` and MailPoet will synchronise the new values.
## Arguments
| Argument | Type | Description |
| -------------------- | ------------- | ----------------------------------------- |
| $subscriberIdOrEmail | string or int | An id or email of an existing subscriber. |
| $subscriber | array | Subscriber data that will be updated |
## Error handling
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 |
| ---- | -------------------------------------------------- |
| 4 | Updating a subscriber that does not exist. |
| 13 | The subscriber couldnt be updated in the database |

View File

@ -1,3 +1,5 @@
version: '3.8'
services:
# for M1 Macs
db:

View File

@ -1,3 +1,5 @@
version: '3.8'
services:
dashboard:
container_name: mp-dashboard
@ -13,7 +15,6 @@ services:
volumes:
- my-datavolume:/var/lib/mysql
- ./dev/database/create_test_db.sh:/docker-entrypoint-initdb.d/10-create_test_db.sh
command: --sql_mode=STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,ANSI,ONLY_FULL_GROUP_BY
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
@ -25,12 +26,13 @@ services:
container_name: mp-wp
build:
context: .
dockerfile: dev/php82/Dockerfile
dockerfile: dev/php81/Dockerfile
args:
UID: ${UID:-1000}
GID: ${GID:-1000}
ports:
- '8002:80'
- '8083:8083' # Storybook port number, see package.json
depends_on:
- db
- smtp
@ -44,17 +46,14 @@ services:
PHP_IDE_CONFIG: 'serverName=Mailpoet'
COMPOSER_HOME: '/tmp/.composer'
NPM_CONFIG_CACHE: '/tmp/.npm'
XDG_CACHE_HOME: '/tmp/.cache'
MAILPOET_DEV_SITE: 1
volumes:
- './wordpress:/var/www/html'
- './tsconfig.base.json:/var/www/html/wp-content/plugins/tsconfig.base.json:ro'
- './.npmrc:/var/www/html/wp-content/plugins/.npmrc'
- './package.json:/var/www/html/wp-content/plugins/package.json'
- './pnpm-lock.yaml:/var/www/html/wp-content/plugins/pnpm-lock.yaml'
- './pnpm-workspace.yaml:/var/www/html/wp-content/plugins/pnpm-workspace.yaml'
- './patches:/var/www/html/wp-content/plugins/patches'
- './tests_env:/var/www/html/wp-content/plugins/tests_env'
- './mailpoet:/var/www/html/wp-content/plugins/mailpoet'
- './mailpoet-premium:/var/www/html/wp-content/plugins/mailpoet-premium'
- './packages:/var/www/html/wp-content/plugins/packages'

View File

@ -1,10 +1,11 @@
{
"presets": [
"@babel/preset-typescript",
["@babel/preset-react", { "runtime": "automatic" }],
"@babel/preset-env",
["@babel/preset-typescript", { "allowDeclareFields": true }]
"@babel/preset-env"
],
"plugins": [
"babel-plugin-typescript-to-proptypes",
[
"@babel/plugin-transform-runtime",
{

View File

@ -37,20 +37,3 @@ WP_GITHUB_TOKEN=
# Jira username/email and a token from https://id.atlassian.com/manage/api-tokens:
WP_JIRA_USER=
WP_JIRA_TOKEN=
# k6 performance test suite
# Get following secrets from the Secret Store, look for "MailPoet: plugin .env":
WP_TEST_PERFORMANCE_DATA_URL=
WP_TEST_PERFORMANCE_PW=
K6_CLOUD_TOKEN=
K6_CLOUD_ID=
# git hooks configuration (must be 'true' to enable)
MP_GIT_HOOKS_ENABLE=true
MP_GIT_HOOKS_ESLINT=true
MP_GIT_HOOKS_STYLELINT=true
MP_GIT_HOOKS_PHPLINT=true
MP_GIT_HOOKS_CODE_SNIFFER=true
MP_GIT_HOOKS_PHPSTAN=true
MP_GIT_HOOKS_INSTALL_JS=false
MP_GIT_HOOKS_INSTALL_PHP=false

3
mailpoet/.eslintignore Normal file
View File

@ -0,0 +1,3 @@
**/vendor/**
**/vendor-prefixed/**
**/testBundles/**

5
mailpoet/.gitignore vendored
View File

@ -11,7 +11,7 @@ tests/plugins
/views/cache/**
temp
mailpoet.zip
tests/javascript-newsletter-editor/testBundles
tests/javascript_newsletter_editor/testBundles
assets/dist
.vagrant
lang
@ -26,4 +26,5 @@ tasks/code_sniffer/vendor
tasks/phpstan/vendor
tasks/phpstan/_phpstan-wp-source.neon
/tools/vendor
assets/js/src/newsletter-editor/behaviors/tinymce-icons.js
/storybook-static
assets/js/src/newsletter_editor/behaviors/tinymce_icons.js

View File

@ -1,2 +1 @@
# we can set this back to "true" once @woocommerce/components have less restrictive definitions
engine-strict=false
engine-strict=true

1
mailpoet/.nvmrc Normal file
View File

@ -0,0 +1 @@
v17.9.1

View File

@ -0,0 +1,48 @@
const path = require('path');
const modulesDir = path.join(__dirname, '../node_modules');
console.log('NODE', modulesDir);
// Workaround for Emotion 11
// https://github.com/storybookjs/storybook/pull/13300#issuecomment-783268111
const updateEmotionAliases = (config) => ({
...config,
resolve: {
...config.resolve,
alias: {
...config.resolve.alias,
'@emotion/core': path.join(modulesDir, '@emotion/react'),
'@emotion/styled': path.join(modulesDir, '@emotion/styled'),
'@emotion/styled-base': path.join(modulesDir, '@emotion/styled'),
'emotion-theming': path.join(modulesDir, '@emotion/react'),
},
},
});
module.exports = {
core: {
builder: 'webpack5',
},
stories: ['../assets/js/src/**/_stories/*.tsx'],
webpackFinal: (config) => {
config.resolve.modules = ['node_modules', '../assets/js/src'];
return updateEmotionAliases(config);
},
managerWebpack: updateEmotionAliases,
addons: [
'@storybook/addon-actions',
'@storybook/addon-links',
'storybook-addon-performance/register',
{
name: '@storybook/addon-storysource',
options: {
rule: {
test: [/_stories\/.*\.tsx?$/],
include: [path.resolve(__dirname, '../assets/js/src')],
},
loaderOptions: {
parser: 'typescript',
},
},
},
],
};

View File

@ -0,0 +1,14 @@
import React from 'react';
import { addDecorator } from '@storybook/react';
import { withPerformance } from 'storybook-addon-performance';
import '../assets/css/src/storybook/wordpress-5.8.2.css';
import '../assets/dist/css/mailpoet-plugin.css';
import '../assets/dist/css/mailpoet-form-editor.css';
addDecorator(withPerformance);
addDecorator((story) => (
<div className="wp-core-ui" id="wpbody">
<div id="mailpoet-modal"></div>
{story()}
</div>
));

View File

@ -3,39 +3,104 @@
'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,
},
}

View File

@ -1,28 +1,24 @@
### Table of Contents
# MailPoet
1. [MailPoet](#mailpoet)
2. [Setup](#setup)
1. [Requirements](#requirements)
2. [Installation](#installation)
3. [Frameworks and libraries](#frameworks-and-libraries)
3. [Workflow Commands](#workflow-commands)
4. [Coding and Testing](#coding-and-testing)
1. [DI](#di)
2. [PHP-Scoper](#php-scoper)
3. [i18n](#i18n)
4. [Acceptance testing](#acceptance-testing)
The **MailPoet** plugin.
## MailPoet
To use the official Docker-based development environment, see details
in [the readme file](../README.md) in the root of this repository. If
you'd like to use the plugin code directly, you can follow the instructions
below.
- For help with product, visit [SUPPORT](../SUPPORT.md).
- To use the Docker-based development environment, see [monorepo README](../README.md).
- To use plugin code directly, follow instructions below.
## Contents
- [Setup](#setup)
- [Frameworks and libraries](#frameworks-and-libraries)
- [Workflow Commands](#workflow-commands)
- [Coding and Testing](#coding-and-testing)
## Setup
### Requirements
- PHP >= 7.4
- PHP >= 7.3 (only for the development environment, to run the plugin PHP >= 7.2 is required)
- NodeJS
- WordPress
@ -50,8 +46,9 @@ cp .env.sample .env
./do compile:all
```
### Frameworks and libraries
## Frameworks and libraries
- [Paris ORM](https://github.com/j4mie/paris).
- [Symfony/dependency-injection](https://github.com/symfony/dependency-injection) ([docs for 3.4](https://symfony.com/doc/3.4/components/dependency_injection.html)).
- [PHP-Scoper](https://github.com/humbug/php-scoper) for moving dependencies into MP namespace
- [Twig](https://twig.symfony.com/) and [Handlebars](https://handlebarsjs.com/) are used for templates rendering.
@ -68,13 +65,6 @@ cp .env.sample .env
## Workflow Commands
There are two different `./do` commands. One is in the free MailPoet directory and runs the commands on the local computer. The second is in the repository root and runs the commands in a Docker container.
Running `./do` commands in the repository root will run the command in the Docker container.
It is recommended to run the assets commands directly in the free MailPoet directory. That means installing the dependencies locally. Running the js and css compilation commands within the container is possible, but it is slower.
On the other hand, the tests should be run in the container. The container has all the necessary dependencies installed and configured. And there is a database running. See more details in the README.md in the repository root.
```bash
$ ./do install # install PHP and JS dependencies
$ ./do update # update PHP and JS dependencies
@ -100,6 +90,7 @@ $ ./do test:debug-unit # alias for ./do test:unit --debug
$ ./do test:debug-integration # alias for ./do test:integration --debug
$ ./do test:failed-unit # run the last failing unit test.
$ ./do test:failed-integration # run the last failing integration test.
$ ./do test:coverage # run tests and output coverage information.
$ ./do test:javascript # run the JS tests.
$ ./do test:acceptance [--file=...] [--skip-deps]
# run acceptances tests into a docker environment.
@ -127,6 +118,34 @@ $ ./do container:dump # Generates DI container cache.
$ ./do generate:data [<generatorName>] [<threads>] # Generates random usage data (Note: requires WooCommerce active) e.g. ./do generate:data past_revenues 4
```
## Storybook
We use [Storybook.js](https://storybook.js.org/) to showcase our React components, which can be used throughout the plugin.
### Usage
Currently, we don't have Storybook published publicly, so developers need to run or build it locally.
To run it locally (on `http://localhost:8083`) while watching the changes (recommended when developing new component), run
```bash
./do storybook:watch
```
To build the static version, which can be accessed via browser, run
```bash
./do storybook:build
```
which will create a `storybook-static` folder with all necessary files. Don't forget to rebuild it when new components are added.
### Building new components
- All stories should be located in `_stories` folder inside the component folder they belong to.
- Run `./do storybook:watch` so all changes are automatically reflected in `http://localhost:8083`.
- Examples are available in `assets/js/src/storybook_demo/_stories` folder.
## Coding and Testing
### DI
@ -142,72 +161,35 @@ Dependencies handled by PHP-Scoper are configured in extra configuration files `
### i18n
We use functions `__()`, `_n()`, `_x()`, and `_nx()` with domain `mailpoet` to translate strings. Please follow [best practices](https://codex.wordpress.org/I18n_for_WordPress_Developers).
We use functions `__()`, `_n()` and `_x()` with domain `mailpoet` to translate strings.
#### Comments for translators
When the translation string can be ambiguous, add [a translators comment](https://codex.wordpress.org/I18n_for_WordPress_Developers#Descriptions) for clarification. Don't use `_x()` or `_xn()` for clarification.
```php
// translators:
$customErrorMessage = sprintf(
// translators: %1$s is the link, %2$s is the error message.
__('Please see %1$s for more information. %2$s.', 'mailpoet'),
'https://kb.mailpoet.com',
$errorMessage
);
```
#### In PHP code
**in PHP code**
```php
__('text to translate', 'mailpoet');
_n('single text', 'plural text', $number, 'mailpoet');
_x('text to translate', 'context', 'mailpoet');
_xn('single text', 'plural text', $number, 'context', 'mailpoet');
_x('text to translate', 'context for translators', 'mailpoet');
```
#### In JavaScript/TypeScript code
**in Twig views**
```ts
import { __, _n, _x, _xn } from '@wordpress/i18n';
__('text to translate', 'mailpoet');
_n('single text', 'plural text', number, 'mailpoet');
_x('text to translate', 'context', 'mailpoet');
_nx('single text', 'plural text', number, 'context', 'mailpoet');
```html
<%= __('text to translate') %> <%= _n('single text', 'plural text', $number) %>
<%= _x('text to translate', 'context for translators') %>
```
To replace placeholders in translated strings, use `sprintf`:
The domain `mailpoet` will be added automatically by the Twig functions.
```ts
import { sprintf } from '@wordpress/i18n';
**in Javascript code**
sprintf(__('Hello %s', 'mailpoet'), 'John');
First add the string to the translations block in the Twig view:
```html
<% block translations %> <%= localize({ 'key': __('string to translate'), ... })
%> <% endblock %>
```
To replace React elements use `createInterpolateElement`:
```tsx
import { __ } from '@wordpress/i18n';
import { createInterpolateElement } from '@wordpress/element';
import { CustomComponent } from '../custom-component.js';
const translatedString = createInterpolateElement(
__(
'This is a <span>string</span> with a <a>link</a> and a self-closing <custom_component/>.',
),
{
span: <span class="special-text" />,
a: <a href="https://make.wordpress.org" />,
custom_component: <CustomComponent />,
},
);
```
For more information, see the [@wordpress/i18n](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/)
and the [createInterpolateElement](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-element/#createinterpolateelement)
guides.
Then use `MailPoet.I18n.t('key')` to get the translated string on your Javascript code.
### Acceptance testing
@ -221,7 +203,7 @@ If you want to run only a single test use the parameter `--file`:
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.
To delete all the docker runtime for acceptance tests use the command `./do delete:docker`.
To delete all the docker runtime for acceptance tests use the command `./do d:d`.
When debugging you can add `$i->pause();` in to your test which pauses the execution.

View File

@ -1,4 +1,4 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
<?php
// phpcs:disable PSR1.Classes.ClassDeclaration
// phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
@ -23,35 +23,16 @@ class RoboFile extends \Robo\Tasks {
return $this->taskExecStack()
->stopOnFail()
->exec('./tools/vendor/composer.phar install')
->exec('cd ../packages/php/email-editor && ../../../mailpoet/tools/vendor/composer.phar install && cd -')
->exec('cd .. && pnpm install --frozen-lockfile --prefer-offline')
->addCode([$this, 'cleanupCachedFiles'])
->run();
}
public function installPhp() {
return $this->taskExecStack()
->stopOnFail()
->exec('./tools/vendor/composer.phar install')
->exec('cd ../packages/php/email-editor && ../../../mailpoet/tools/vendor/composer.phar install && cd -')
->addCode([$this, 'cleanupCachedFiles'])
->run();
}
public function installJs() {
return $this->taskExecStack()
->stopOnFail()
->exec('cd .. && pnpm install --frozen-lockfile --prefer-offline')
->run();
}
public function cleanupCachedFiles() {
$this->say('Cleaning up generated folder.');
$this->_exec('rm -rf ' . __DIR__ . '/generated/*');
$this->say('Cleaning up PHPStan cache.');
$this->_exec('rm -rf ' . __DIR__ . '/temp/*');
$this->say('Cleaning up old testing plugins.');
$this->_exec('rm -rf ' . __DIR__ . '/tests/plugins/*');
}
public function update() {
@ -62,115 +43,6 @@ class RoboFile extends \Robo\Tasks {
->run();
}
public function updateJsPackages($opts = ['ticket' => '', 'latest' => true, 'checks' => false, 'dedupe' => false]) {
$ticket = $opts['ticket'];
$latest = $opts['latest'];
$runChecks = $opts['checks'];
$runDedupe = $opts['dedupe'];
if (empty($ticket)) {
$this->say('Please specify a ticket with --ticket=<ticket>');
exit(1);
}
$outdatedPackagesOutput = $this->taskExec('pnpm outdated --no-table')
->printOutput(false)
->run()
->getMessage();
$lines = explode("\n", $outdatedPackagesOutput);
// The package names themselves are every third line
$outdatedPackages = array_filter($lines, function($key) {
return $key % 3 === 0;
}, ARRAY_FILTER_USE_KEY);
$excludePackages = [
'react-router-dom', // MAILPOET-3911
'codemirror', // MAILPOET-5483
'babel-loader', // MAILPOET-5491
'stylelint', // MAILPOET-5462
'backbone', // Will remove with new email editor
'backbone.marionette', // Will remove with new email editor
'history',
'fork-ts-checker-webpack-plugin',
];
$majorVersionChanges = [];
foreach ($outdatedPackages as $packageName) {
// @wordpress packages should be handled all together
if (strpos($packageName, '@wordpress') !== false) {
continue;
}
// @types should be kept in sync with the major version of the dependency and will be easiest to update after
if (strpos($packageName, '@types') !== false) {
continue;
}
$packageName = str_replace(' (dev)', '', $packageName);
if (in_array($packageName, $excludePackages)) {
$this->say("Skipping $packageName");
continue;
}
$this->say("Updating $packageName");
$oldVersion = $this->getCurrentJsPackageVersion($packageName);
$oldMajorVersion = explode('.', $oldVersion)[0];
if ($latest) {
$this->taskExec("pnpm up -L $packageName")->run();
} else {
$this->taskExec("pnpm up $packageName")->run();
}
if ($this->taskExec("pnpm list $packageName")->run()->wasSuccessful()) {
$newVersion = $this->getCurrentJsPackageVersion($packageName);
if ($newVersion === $oldVersion) {
continue;
}
if ($runChecks) {
$collection = $this->collectionBuilder();
$collection
->addCode([$this, 'installJs'])
->addCode([$this, 'compileJs'])
->addCode([$this, 'compileCss'])
->addCode([$this, 'qaFrontendAssets'])
->run();
}
$newMajorVersion = explode('.', $newVersion)[0];
$this->taskExecStack()
->exec("git add ./package.json")
->exec('git add ../pnpm-lock.yaml')
->exec("git commit --no-verify -m \"Update $packageName from $oldVersion to $newVersion\" -m \"\" -m \"$ticket\"")
->run();
if ($oldMajorVersion !== $newMajorVersion) {
$majorVersionChanges[] = $packageName;
}
} else {
$this->say("Update of $packageName failed. Exiting.");
exit(1);
}
}
if ($runDedupe) {
$this->taskExecStack()
->exec('pnpm dedupe')
->exec('git add ../pnpm-lock.yaml')
->exec("git commit --no-verify -m \"Update lock file after pnpm dedupe\" -m \"\" -m \"$ticket\"")
->run();
}
if (count($majorVersionChanges) > 0) {
$this->say(sprintf("The following packages changed major version: %s. Check to see if any of them need to have their @types updated.", implode(', ', $majorVersionChanges)));
}
}
public function watchCss() {
$cssFiles = $this->rsearch('assets/css/src/', ['scss']);
$this->taskWatch()
@ -189,7 +61,7 @@ class RoboFile extends \Robo\Tasks {
$this->_exec('./node_modules/webpack/bin/webpack.js --watch');
}
public function compileAll($opts = ['env' => null, 'skip-tests' => false, 'only-tests' => false]) {
public function compileAll($opts = ['env' => null]) {
$collection = $this->collectionBuilder();
$collection->addCode(function() use ($opts) {
return call_user_func([$this, 'compileJs'], $opts);
@ -200,17 +72,15 @@ class RoboFile extends \Robo\Tasks {
return $collection->run();
}
public function compileJs($opts = ['env' => null, 'skip-tests' => false, 'only-tests' => false]) {
public function compileJs($opts = ['env' => null]) {
if (!is_dir('assets/dist/js')) {
mkdir('assets/dist/js', 0777, true);
}
if (!$opts['only-tests']) {
$this->_exec('rm -rf ' . __DIR__ . '/assets/dist/js/*');
}
$this->_exec('rm -rf ' . __DIR__ . '/assets/dist/js/*');
$env = ($opts['env']) ?
sprintf('./node_modules/.bin/cross-env NODE_ENV="%s"', $opts['env']) :
null;
return $this->_exec($env . ' ./node_modules/webpack/bin/webpack.js --env BUILD_TESTS=' . ($opts['skip-tests'] ? 'skip' : 'build') . ' --env BUILD_ONLY_TESTS=' . ($opts['only-tests'] ? 'true' : 'false'));
return $this->_exec($env . ' ./node_modules/webpack/bin/webpack.js');
}
public function compileCss($opts = ['env' => null]) {
@ -220,11 +90,9 @@ class RoboFile extends \Robo\Tasks {
// Clean up folder from previous files
array_map('unlink', glob("assets/dist/css/*.*"));
$compilationResult = $this->taskExecStack()
->exec('pnpm run stylelint-check -- "assets/css/src/**/*.scss"')
->exec('pnpm run scss' . ($opts['env'] === 'production' ? ' --no-source-map' : ''))
->exec('pnpm run autoprefixer')
->run();
$this->_exec('pnpm run stylelint -- "assets/css/src/**/*.scss"');
$this->_exec('pnpm run scss');
$compilationResult = $this->_exec('pnpm run autoprefixer');
// Create manifest file
$manifest = [];
@ -247,46 +115,11 @@ class RoboFile extends \Robo\Tasks {
}
public function translationsBuild() {
$exclude = implode(',', [
'.mp_svn',
'assets/css',
'assets/img',
'assets/js',
'generated',
'lang',
'lib-3rd-party',
'mailpoet-premium',
'node_modules',
'plugin_repository',
'prefixer',
'tasks',
'temp',
'tests',
'tools',
'vendor',
'vendor-prefixed',
'RoboFile.php',
]);
$headers = escapeshellarg(
json_encode([
'Report-Msgid-Bugs-To' => 'http://support.mailpoet.com/',
'Last-Translator' => 'MailPoet i18n (https://www.transifex.com/organization/wysija)',
'Language-Team' => 'MailPoet i18n <https://www.transifex.com/organization/wysija>',
'Plural-Forms' => 'nplurals=2; plural=(n != 1);',
])
);
$this->collectionBuilder()
->taskExec('mkdir -p ' . __DIR__ . '/lang')
// HTML, HBS
->taskExec("php -d memory_limit=-1 tasks/makepot/makepot-views.php . > lang/mailpoet.pot")
// PHP, JS/TS
->taskExec("php -d memory_limit=-1 vendor/wp-cli/wp-cli/php/boot-fs.php i18n make-pot --merge --slug=mailpoet --domain=mailpoet --exclude=$exclude --headers=$headers . lang/mailpoet.pot")
->run();
->taskExec(
'php -d memory_limit=-1 tasks/makepot/grunt-makepot.php wp-plugin . lang/mailpoet.pot mailpoet .mp_svn,assets,lang,node_modules,plugin_repository,tasks,tests,vendor'
)->run();
}
public function translationsGetPotFileFromBuild() {
@ -332,7 +165,7 @@ class RoboFile extends \Robo\Tasks {
}
public function testUnit(array $opts = ['file' => null, 'xml' => false, 'multisite' => false, 'debug' => false]) {
$command = '../tests_env/vendor/bin/codecept run unit';
$command = 'vendor/bin/codecept run unit';
if ($opts['file']) {
$command .= ' -f ' . $opts['file'];
@ -349,15 +182,15 @@ class RoboFile extends \Robo\Tasks {
return $this->_exec($command);
}
public function testIntegration(array $opts = ['file' => null, 'group' => null, 'skip-group' => null, 'xml' => false, 'multisite' => false, 'debug' => false, 'skip-deps' => false, 'skip-plugins' => false, 'disable-hpos' => false, 'enable-hpos-sync' => false, 'enable-hpos' => false, 'stop-on-fail' => false, 'wordpress-version' => null]) {
public function testIntegration(array $opts = ['file' => null, 'group' => null, 'skip-group' => null, 'xml' => false, 'multisite' => false, 'debug' => false, 'skip-deps' => false, 'skip-plugins' => false]) {
return $this->runTestsInContainer(array_merge($opts, ['test_type' => 'integration']));
}
public function testMultisiteIntegration($opts = ['file' => null, 'group' => null, 'skip-group' => null, 'xml' => false, 'multisite' => true, 'skip-deps' => false, 'skip-plugins' => false, 'disable-hpos' => false, 'enable-hpos-sync' => false, 'enable-hpos' => false]) {
public function testMultisiteIntegration($opts = ['file' => null, 'group' => null, 'skip-group' => null, 'xml' => false, 'multisite' => true, 'skip-deps' => false, 'skip-plugins' => false]) {
return $this->runTestsInContainer(array_merge($opts, ['test_type' => 'integration']));
}
public function testWooIntegration(array $opts = ['file' => null, 'xml' => false, 'multisite' => false, 'debug' => false, 'disable-hpos' => false, 'enable-hpos-sync' => false, 'enable-hpos' => false]) {
public function testWooIntegration(array $opts = ['file' => null, 'xml' => false, 'multisite' => false, 'debug' => false]) {
return $this->runTestsInContainer(array_merge($opts, ['test_type' => 'integration', 'group' => 'woo', 'skip-deps' => true, 'skip-plugins' => false]));
}
@ -365,12 +198,26 @@ class RoboFile extends \Robo\Tasks {
return $this->runTestsInContainer(array_merge($opts, ['test_type' => 'integration', 'skip-group' => 'woo', 'skip-deps' => true, 'skip-plugins' => true]));
}
public function testCoverage($opts = ['file' => null, 'xml' => false]) {
$command = join(' ', [
'vendor/bin/codecept run -s acceptance',
(($opts['file']) ? $opts['file'] : ''),
'--coverage',
($opts['xml']) ? '--coverage-xml' : '--coverage-html',
]);
if ($opts['xml']) {
$command .= ' --xml';
}
return $this->execWithXDebug($command);
}
public function testNewsletterEditor($xmlOutputFile = null) {
$command = join(' ', [
'./node_modules/.bin/mocha',
'-r tests/javascript-newsletter-editor/mocha-test-helper.js',
'-r tests/javascript-newsletter-editor/mocha-chai.mjs',
'tests/javascript-newsletter-editor/testBundles/**/*.js',
'-r tests/javascript_newsletter_editor/mochaTestHelper.js',
'tests/javascript_newsletter_editor/testBundles/**/*.js',
'--exit',
]);
@ -381,12 +228,16 @@ class RoboFile extends \Robo\Tasks {
);
}
return $this->taskExec($command)
return $this->collectionBuilder()
->addCode(function () {
$this->compileJs();
})
->taskExec($command)
->run();
}
public function testJavascript($xmlOutputFile = null) {
$command = './node_modules/.bin/mocha --recursive --require tests/javascript/mocha-env.mjs tests/javascript --extension spec.ts';
$command = './node_modules/.bin/mocha --recursive --require tests/javascript/babel_register.js tests/javascript --extension spec.js --extension spec.ts';
if (!empty($xmlOutputFile)) {
$command .= sprintf(
@ -406,121 +257,28 @@ class RoboFile extends \Robo\Tasks {
return $this->testIntegration($opts);
}
public function testAcceptance($opts = ['file' => null, 'skip-deps' => false, 'group' => null, 'timeout' => null, 'disable-hpos' => false, 'enable-hpos-sync' => false, 'enable-hpos' => false, 'wordpress-version' => null, 'skip-plugins' => false]) {
public function testAcceptance($opts = ['file' => null, 'skip-deps' => false, 'timeout' => null]) {
return $this->runTestsInContainer($opts);
}
public function testPerformance($path = null, $opts = ['url' => null, 'us' => null, 'pw' => null, 'head' => false, 'scenario' => null]) {
$dir = __DIR__;
if ((getenv('K6_CLOUD_TOKEN')) === false) {
return $this->collectionBuilder()
->addCode([$this, 'testPerformanceSetup'])
->taskExec("php $dir/tools/k6.php")
->arg('run')
->option('env', 'K6_BROWSER_ENABLED=1')
->option('env', 'URL=' . $opts['url'])
->option('env', 'US=' . $opts['us'])
->option('env', 'PW=' . $opts['pw'])
->option('env', 'K6_BROWSER_HEADLESS=' . ($opts['head'] ? 'false' : 'true'))
->option('env', 'K6_BROWSER_TIMEOUT=' . getenv('K6_BROWSER_TIMEOUT'))
->option('env', 'SCENARIO=' . $opts['scenario'])
->arg($path ?? "$dir/tests/performance/scenarios.js")
->dir($dir)->run();
} else {
return $this->collectionBuilder()
->addCode([$this, 'testPerformanceSetup'])
->taskExec("php $dir/tools/k6.php")
->arg('run')
->option('env', 'K6_BROWSER_ENABLED=1')
->option('env', 'URL=' . $opts['url'])
->option('env', 'US=' . $opts['us'])
->option('env', 'PW=' . $opts['pw'])
->option('env', 'HEADLESS=' . ($opts['head'] ? 'false' : 'true'))
->option('env', 'SCENARIO=' . $opts['scenario'])
->option('env', 'K6_CLOUD_TOKEN=' . getenv('K6_CLOUD_TOKEN'))
->option('env', 'K6_CLOUD_ID=' . getenv('K6_CLOUD_ID'))
->option('env', 'K6_BROWSER_TIMEOUT=' . getenv('K6_BROWSER_TIMEOUT'))
->option('env', 'K6_PROJECT_NAME=' . $opts['scenario'])
->option('out', 'cloud')
->arg($path ?? "$dir/tests/performance/scenarios.js")
->dir($dir)->run();
}
}
public function testPerformanceSetup() {
// get data file URL
$url = getenv('WP_TEST_PERFORMANCE_DATA_URL');
if (!$url) {
$this->yell("Please set 'WP_TEST_PERFORMANCE_DATA_URL'. You'll find it in the secret store.", 40, 'red');
exit(1);
}
// download data
$dataFile = __DIR__ . '/tests/performance/_data/data.sql';
if (!file_exists($dataFile)) {
$this->say('Downloading data file...');
if (!is_dir(dirname($dataFile))) {
mkdir(dirname($dataFile), 0777, true);
}
$source = gzopen($url, 'rb');
$destination = fopen($dataFile, 'wb');
while (!gzeof($source)) {
fwrite($destination, gzread($source, 4096));
}
fclose($destination);
gzclose($source);
$this->say("Data file downloaded to: $dataFile");
} else {
$this->say("Data file already exists: $dataFile");
}
// import data & run WordPress setup
$this->say('Importing data and running a WordPress setup...');
$this->taskExec('COMPOSE_HTTP_TIMEOUT=200 docker compose run --rm -it setup')
->dir(__DIR__ . '/tests/performance')
->run();
$this->say('Data imported, WordPress set up.');
}
public function testPerformanceClean() {
$this->taskExec('COMPOSE_HTTP_TIMEOUT=200 docker compose down --remove-orphans -v')
->dir(__DIR__ . '/tests/performance')
->run();
}
public function testAcceptanceMultisite($opts = ['file' => null, 'skip-deps' => false, 'group' => null, 'timeout' => null, 'disable-hpos' => false, 'enable-hpos-sync' => false, 'enable-hpos' => false]) {
public function testAcceptanceMultisite($opts = ['file' => null, 'skip-deps' => false, 'timeout' => null]) {
return $this->runTestsInContainer(array_merge($opts, ['multisite' => true]));
}
/**
* Deletes docker stuff related to tests including docker images.
*/
public function deleteDocker() {
return $this->taskExec(
'docker compose down -v --remove-orphans --rmi all'
)->dir(__DIR__ . '/../tests_env/docker')->run();
}
/**
* Deletes docker containers and volumes used in tests
*/
public function resetTestDocker() {
return $this
->taskExec(
'docker compose down -v --remove-orphans'
)->dir(__DIR__ . '/../tests_env/docker')
->addCode([$this, 'cleanupCachedFiles'])
->run();
'docker-compose down -v --remove-orphans --rmi all'
)->dir(__DIR__ . '/tests/docker')->run();
}
public function testFailedUnit() {
$this->_exec('../tests_env/vendor/bin/codecept build');
return $this->_exec('../tests_env/vendor/bin/codecept run unit -g failed');
$this->_exec('vendor/bin/codecept build');
return $this->_exec('vendor/bin/codecept run unit -g failed');
}
public function testFailedIntegration() {
$this->_exec('../tests_env/vendor/bin/codecept build');
return $this->_exec('../tests_env/vendor/bin/codecept run integration -g failed');
$this->_exec('vendor/bin/codecept build');
return $this->_exec('vendor/bin/codecept run integration -g failed');
}
public function containerDump() {
@ -579,30 +337,6 @@ class RoboFile extends \Robo\Tasks {
$this->say("Validator metadata generated to: $validatorMetadataDir");
}
/**
* Creates a new migration file. Use `migrations:new db` for a db level migration or `migrations:new app` for app level migration.
* @param $level string - db or app
*/
public function migrationsNew($level) {
$generator = new \MailPoet\Migrator\Repository();
$level = strtolower($level);
$result = $generator->create($level);
$path = realpath($result['path']);
$this->output->writeln('MAILPOET DATABASE MIGRATIONS');
$this->output->writeln("============================\n");
$this->output->writeln("New migration created ✔\n");
$this->output->writeln(" Name: {$result['name']}");
$this->output->writeln(" Path: $path");
}
public function migrationsStatus() {
return $this->taskExec('vendor/bin/wp mailpoet:migrations:status');
}
public function migrationsRun() {
return $this->taskExec('vendor/bin/wp mailpoet:migrations:run');
}
public function qa() {
$collection = $this->collectionBuilder();
$collection->addCode([$this, 'qaPhp']);
@ -650,9 +384,9 @@ class RoboFile extends \Robo\Tasks {
'lib/',
'lib-3rd-party/',
'vendor/composer',
'vendor/dragonmantank',
'vendor/mtdowling',
'vendor/soundasleep',
'vendor-prefixed/',
'vendor-prefixed/soundasleep',
'mailpoet.php',
]);
// The list of files and folders to exclude is coming from build.sh
@ -666,11 +400,11 @@ class RoboFile extends \Robo\Tasks {
'vendor-prefixed/cerdic/css-tidy/COPYING',
'vendor-prefixed/cerdic/css-tidy/NEWS',
'vendor-prefixed/cerdic/css-tidy/testing',
'vendor/dragonmantank/cron-expression/tests',
'vendor/mtdowling/cron-expression/tests',
'vendor/phpmailer/phpmailer/test',
'vendor-prefixed/psr/log/Psr/Log/Test',
'vendor-prefixed/sabberworm/php-css-parser/tests',
'vendor-prefixed/soundasleep/html2text/tests',
'vendor/soundasleep/html2text/tests',
'vendor-prefixed/swiftmailer/swiftmailer/tests',
'vendor-prefixed/symfony/service-contracts/Tests',
'vendor-prefixed/symfony/translation/Tests',
@ -686,21 +420,11 @@ class RoboFile extends \Robo\Tasks {
}
public function qaLintJavascript() {
$collection = $this->collectionBuilder();
return $collection->taskExecStack()
->stopOnFail()
->exec('pnpm run check-types && pnpm run lint')
->exec('cd .. && cd packages/js/email-editor && pnpm run check-types && pnpm run lint:js')
->run();
return $this->_exec('pnpm run check-types && pnpm run lint');
}
public function qaLintCss() {
$collection = $this->collectionBuilder();
return $collection->taskExecStack()
->stopOnFail()
->exec('pnpm run stylelint-check -- "assets/css/src/**/*.scss"')
->exec('cd .. && cd packages/js/email-editor && pnpm run lint:css')
->run();
return $this->_exec('pnpm run stylelint-check -- "assets/css/src/**/*.scss"');
}
public function qaCodeSniffer(array $filesToCheck, $opts = ['severity' => 'all']) {
@ -708,10 +432,9 @@ class RoboFile extends \Robo\Tasks {
$task = implode(' ', [
'php -d memory_limit=-1',
'./tasks/code_sniffer/vendor/bin/phpcs',
"--parallel=" . $this->getParallelism(),
'--extensions=php',
$severityFlag,
'--standard=tasks/code_sniffer/MailPoet/free-ruleset.xml',
'--standard=tasks/code_sniffer/MailPoet',
'-s',
]);
@ -729,9 +452,7 @@ class RoboFile extends \Robo\Tasks {
'tasks/code_sniffer/vendor',
'tasks/phpstan/vendor',
'tasks/makepot',
'tasks/minimal-plugin-standard/vendor',
'tools/vendor',
'tools/wpscan-semgrep-rules',
'temp',
'tests/_data',
'tests/_output',
@ -748,29 +469,67 @@ class RoboFile extends \Robo\Tasks {
$stringFilesToCheck = !empty($filesToCheck) ? implode(' ', $filesToCheck) : '.';
return $this->taskExec($task)
return $this
->taskExec($task)
->arg('--ignore=' . implode(',', $ignorePatterns))
->rawArg($stringFilesToCheck)
->run();
}
public function qaFixFile($filePath) {
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
if ($extension === 'php') {
if (substr($filePath, -4) === '.php') {
// fix PHPCS rules
return $this->collectionBuilder()
->taskExec(
'./tasks/code_sniffer/vendor/bin/phpcbf ' .
'--standard=tasks/code_sniffer/MailPoet/free-ruleset.xml ' .
'--runtime-set testVersion 7.4-8.2 ' .
'--standard=./tasks/code_sniffer/MailPoet ' .
'--runtime-set testVersion 7.2-8.0 ' .
$filePath . ' -n'
)
->run();
}
if (in_array($extension, ['js', 'jsx', 'ts', 'tsx'], true)) {
// fix ESLint rules
if (substr($filePath, -4) === '.jsx') {
// fix ESLint using ES6 rules
return $this->collectionBuilder()
->taskExec("pnpm eslint --max-warnings 0 --fix $filePath")
->taskExec(
'npx ../eslint-config/node_modules/.bin/eslint -c ../eslint-config/.eslintrc.es6.json ' .
'--max-warnings 0 ' .
'--fix ' .
$filePath
)
->run();
}
if (substr($filePath, -4) === '.tsx' || substr($filePath, -3) === '.ts') {
// fix ESLint using TS rules
return $this->collectionBuilder()
->taskExec(
'npx ../eslint-config/node_modules/.bin/eslint -c ../eslint-config/.eslintrc.ts.json ' .
'--max-warnings 0 ' .
'--fix ' .
$filePath
)
->run();
}
if (substr($filePath, -8) === '.spec.js') {
// fix ESLint using tests rules
return $this->collectionBuilder()
->taskExec(
'npx ../eslint-config/node_modules/.bin/eslint -c ../eslint-config/.eslintrc.tests_newsletter_editor.json ' .
'--max-warnings 0 ' .
'--fix ' .
$filePath
)
->run();
}
if (substr($filePath, -3) === '.js') {
// fix ESLint using ES5 rules
return $this->collectionBuilder()
->taskExec(
'npx ../eslint-config/node_modules/.bin/eslint -c ../eslint-config/.eslintrc.es5.json ' .
'--max-warnings 0 ' .
'--fix ' .
$filePath
)
->run();
}
}
@ -787,7 +546,7 @@ class RoboFile extends \Robo\Tasks {
}
// make sure Codeception support files are present to avoid invalid errors when running PHPStan
$this->_exec('../tests_env/vendor/bin/codecept build');
$this->_exec('vendor/bin/codecept build');
// PHPStan must be run out of main plugin directory to avoid its autoloading
// from vendor/autoload.php where some dev dependencies cause conflicts.
@ -807,12 +566,12 @@ class RoboFile extends \Robo\Tasks {
->run();
}
public function qaSemgrep() {
return $this->_exec('./tools/semgrep.sh lib/ lib-3rd-party/');
public function storybookBuild() {
return $this->_exec('pnpm run build-storybook');
}
public function qaQitSecurity() {
return $this->_exec('./vendor/bin/qit run:security mailpoet --zip=mailpoet.zip --wait');
public function storybookWatch() {
return $this->_exec('pnpm run storybook');
}
public function svnCheckout() {
@ -973,9 +732,6 @@ class RoboFile extends \Robo\Tasks {
->addCode(function () use ($version) {
$this->releaseCreatePullRequest($version);
})
->addCode(function () use ($version) {
$this->releaseRerunCircleWorkflow(\MailPoetTasks\Release\CircleCiController::PROJECT_PREMIUM);
})
->addCode(function () use ($version) {
$this->translationsPrepareLanguagePacks($version);
})
@ -1091,9 +847,6 @@ class RoboFile extends \Robo\Tasks {
->addCode(function () {
return $this->releaseDownloadZip();
})
->addCode(function () use ($version) {
return $this->releaseVerifyDownloadedZip($version);
})
->addCode(function () {
return $this->translationsGetPotFileFromBuild();
})
@ -1115,26 +868,9 @@ class RoboFile extends \Robo\Tasks {
->addCode(function () use ($version) {
return $this->releasePublishSlack($version);
})
->addCode(function () {
return $this->releaseMergePullRequest(\MailPoetTasks\Release\GitHubController::RELEASE_SOURCE_BRANCH);
})
->addCode(function () {
return $this->releaseDeleteDownloadedZip();
})
->run();
}
public function releaseMergePullRequest(string $branch) {
try {
$this->createGitHubController()
->mergePullRequest(\MailPoetTasks\Release\CircleCiController::PROJECT_MAILPOET, $branch);
} catch (\Exception $e) {
$this->yell($e->getMessage(), 40, 'red');
exit(1);
}
$this->say("Pull request for branch: '{$branch}' was successfully merged");
}
/**
* This command displays how many pull request each person did recently
*/
@ -1279,29 +1015,6 @@ class RoboFile extends \Robo\Tasks {
$this->say("IMPORTANT NOTES \n" . ($outputs[2] ?: 'none'));
}
public function releaseVerifyDownloadedZip($version) {
$this->say('Verifying ZIP file');
$zip = new ZipArchive();
$versionFound = false;
if ($zip->open(self::ZIP_BUILD_PATH) === true) {
$fileContent = $zip->getFromName('mailpoet/readme.txt');
if ($fileContent !== false) {
$versionFound = strpos($fileContent, 'Stable tag: ' . $version);
}
$zip->close();
} else {
$this->yell('ZIP file could not be opened!', 40, 'red');
exit(1);
}
if (!$versionFound) {
$this->yell('ZIP file does not contain required version: "' . $version . '" in readme.txt! ', 40, 'red');
exit(1);
}
$this->say('ZIP file contains required version: "' . $version . '" in readme.txt.');
}
public function releaseDownloadZip() {
$circleciController = $this->createCircleCiController();
$path = $circleciController->downloadLatestBuild(self::ZIP_BUILD_PATH);
@ -1309,12 +1022,6 @@ class RoboFile extends \Robo\Tasks {
$this->say(sprintf('Release ZIP file size: %.2F MB', filesize($path) / pow(1024, 2)));
}
public function releaseDeleteDownloadedZip() {
$this->say('Delete downloaded ZIP: ' . self::ZIP_BUILD_PATH);
$this->taskExec('rm -f ' . self::ZIP_BUILD_PATH)->run();
$this->say('ZIP file was deleted');
}
public function releasePublishGithub($version = null) {
$jiraController = $this->createJiraController();
$version = $jiraController->getVersion($version);
@ -1342,16 +1049,9 @@ class RoboFile extends \Robo\Tasks {
$this->say("Release '$version[name]' info was published on Slack.");
}
public function releaseRerunCircleWorkflow(string $project = null) {
$circleciController = $this->createCircleCiController();
$result = $circleciController->rerunLatestWorkflow($project);
// Sometimes can be useful to know which Circle project workflow was restarted
$project = $project ? " for the project '{$project}'" : '';
if (!$result) {
$this->yell("Circle Workflow{$project} was not restarted", 40, 'red');
} else {
$this->say("Circle Workflow{$project} was started from the beginning");
}
public function downloadWooCommerceBlocksZip($tag = null) {
$this->createWpOrgDownloader('woo-gutenberg-products-block')
->downloadPluginZip('woo-gutenberg-products-block.zip', __DIR__ . '/tests/plugins/', $tag);
}
public function downloadWooCommerceMembershipsZip($tag = null) {
@ -1372,15 +1072,6 @@ class RoboFile extends \Robo\Tasks {
->downloadReleaseZip('woocommerce-subscriptions.zip', __DIR__ . '/tests/plugins/', $tag);
}
public function downloadAutomateWooZip($tag = null) {
if (!getenv('WP_GITHUB_USERNAME') && !getenv('WP_GITHUB_TOKEN')) {
$this->yell("Skipping download of Automate Woo", 40, 'red');
exit(0); // Exit with 0 since it is a valid state for some environments
}
$this->createGithubClient('woocommerce/automatewoo')
->downloadReleaseZip('automatewoo.zip', __DIR__ . '/tests/plugins/', $tag);
}
public function downloadWooCommerceZip($tag = null) {
$this->createWpOrgDownloader('woocommerce')
->downloadPluginZip('woocommerce.zip', __DIR__ . '/tests/plugins/', $tag);
@ -1412,60 +1103,6 @@ class RoboFile extends \Robo\Tasks {
$generator->run($generatorName);
}
public function automationAddStep() {
require_once __DIR__ . '/tasks/automation/AddStep.php';
$yes = ['y', 'yes', 'Y', 'Yes', 'YES'];
$type = in_array($this->ask("Is this step a trigger? [y]"), $yes) ? 'trigger' : 'action';
$isPremium = in_array($this->ask("Is this $type a premium feature? [y]"), $yes);
$vendor = $this->ask("Who is the vendor of the $type? (default: mailpoet)") ?? 'mailpoet';
do {
$id = $this->ask("What is the id of the $type?");
} while (!$id);
do {
$name = $this->ask("What is the name of the $type?");
} while (!$name);
do {
$description = $this->ask("Describe the $type?");
} while (!$description);
do {
$keywords = array_map(
function(string $keyword): string {
return trim($keyword);
},
explode(',', $this->ask("Add some keywords (commaseparated)"))
);
} while (!$keywords);
$subtitle = 'Trigger';
if ($type === 'action') {
do {
$subtitle = $this->ask("What is the subtitle?");
} while (!$subtitle);
}
$premiumNotice = '';
if ($isPremium) {
do {
$premiumNotice = $this->ask("What is the text message for the premium modal?");
} while (!$premiumNotice);
}
$generator = new \MailPoetTasks\Automation\AddStep(
$type,
$isPremium,
$vendor,
$id,
$name,
$description,
$subtitle,
$keywords,
$premiumNotice
);
$generator->create();
$this->yell(ucfirst("$type created."));
}
public function twigGenerateCache() {
$templatePath = __DIR__ . '/views/'; // \MailPoet\Config\Env::$viewsPath . '/'
@ -1475,16 +1112,12 @@ class RoboFile extends \Robo\Tasks {
new TwigFileSystem($templatePath)
);
$twig = $renderer->getTwig();
foreach ($this->rsearch($templatePath, ['html', 'hbs', 'txt']) as $template) {
foreach ($this->rsearch($templatePath, ['html','hbs','txt']) as $template) {
$path = substr($template, strlen($templatePath));
$twig->load($path);
}
}
public function emailCreateTemplates() {
return $this->taskExec('vendor/bin/wp mailpoet:email-editor:create-templates');
}
protected function rsearch($folder, $extensions = []) {
$dir = new RecursiveDirectoryIterator($folder);
$iterator = new RecursiveIteratorIterator($dir);
@ -1611,9 +1244,9 @@ class RoboFile extends \Robo\Tasks {
}
$annotationReaderProvider = new \MailPoet\Doctrine\Annotations\AnnotationReaderProvider();
$configuration = (new \MailPoet\Doctrine\ConfigurationFactory($annotationReaderProvider, true))->createConfiguration();
$platformClass = \MailPoetVendor\Doctrine\DBAL\Platforms\MySQLPlatform::class;
$platformClass = \MailPoet\Doctrine\ConnectionFactory::PLATFORM_CLASS;
return \MailPoetVendor\Doctrine\ORM\EntityManager::create([
'driverClass' => \MailPoet\Doctrine\ConnectionFactory::DRIVER_CLASS,
'driver' => \MailPoet\Doctrine\ConnectionFactory::DRIVER,
'platform' => new $platformClass,
], $configuration);
}
@ -1621,13 +1254,10 @@ class RoboFile extends \Robo\Tasks {
private function runTestsInContainer(array $opts) {
$testType = $opts['test_type'] ?? 'acceptance';
$this->doctrineGenerateCache();
return $this->taskExec(
'COMPOSE_HTTP_TIMEOUT=200 docker compose run ' .
(isset($opts['wordpress-version']) && $opts['wordpress-version'] ? '-e WORDPRESS_VERSION=' . $opts['wordpress-version'] . ' ' : '') .
'COMPOSE_HTTP_TIMEOUT=200 docker-compose run ' .
(isset($opts['skip-deps']) && $opts['skip-deps'] ? '-e SKIP_DEPS=1 ' : '') .
(isset($opts['disable-hpos']) && $opts['disable-hpos'] ? '-e DISABLE_HPOS=1 ' : '') .
(isset($opts['enable-hpos-sync']) && $opts['enable-hpos-sync'] ? '-e ENABLE_HPOS_SYNC=1 ' : '') .
(isset($opts['enable-hpos']) && $opts['enable-hpos'] ? '-e ENABLE_HPOS=1 ' : '') .
(isset($opts['skip-plugins']) && $opts['skip-plugins'] ? '-e SKIP_PLUGINS=1 ' : '') .
(isset($opts['timeout']) && $opts['timeout'] ? '-e WAIT_TIMEOUT=' . (int)$opts['timeout'] . ' ' : '') .
(isset($opts['multisite']) && $opts['multisite'] ? '-e MULTISITE=1 ' : '-e MULTISITE=0 ') .
@ -1635,21 +1265,7 @@ class RoboFile extends \Robo\Tasks {
(isset($opts['xml']) && $opts['xml'] ? '--xml ' : '') .
(isset($opts['group']) && $opts['group'] ? '--group ' . $opts['group'] . ' ' : '') .
(isset($opts['skip-group']) && $opts['skip-group'] ? '--skip-group ' . $opts['skip-group'] . ' ' : '') .
(isset($opts['stop-on-fail']) && $opts['stop-on-fail'] ? '-f ' : '') .
(isset($opts['file']) && $opts['file'] ? $opts['file'] : '')
)->dir(__DIR__ . '/../tests_env/docker')->run();
}
private function getParallelism(int $multiplier = 1, int $min = 4, int $max = 32): int {
$path = __DIR__ . '/../.circleci/nproc.js';
$nproc = (int)$this->taskExec("node $path")->printOutput(false)->run()->stopOnFail()->getMessage();
return max(min($nproc * $multiplier, $max), $min);
}
private function getCurrentJsPackageVersion(string $packageName) {
$versionInfo = $this->taskExec("pnpm list $packageName")->printOutput(false)->run()->getMessage();
$lines = explode("\n", $versionInfo);
$lastLine = end($lines);
return explode(' ', $lastLine)[1];
'-f ' . (isset($opts['file']) && $opts['file'] ? $opts['file'] : '')
)->dir(__DIR__ . '/tests/docker')->run();
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -3,8 +3,7 @@ Style for Members plugin
*/
.members-tab-title {
.mailpoet-icon-logo {
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNTIuMDIgMTU2LjQiPjxwYXRoIGQ9Ik0zNy43MSw4OS4xYzMuNSwwLDUuOS0uOCw3LjItMi4zYTgsOCwwLDAsMCwyLTUuNFYzNS43bDE3LDQ1LjFhMTIuNjgsMTIuNjgsMCwwLDAsMy43LDUuNGMxLjYsMS4zLDQsMiw3LjIsMmExMi41NCwxMi41NCwwLDAsMCw1LjktMS40LDguNDEsOC40MSwwLDAsMCwzLjktNWwxOC4xLTUwVjgxYTguNTMsOC41MywwLDAsMCwyLjEsNi4xYzEuNCwxLjQsMy43LDIuMiw2LjksMi4yLDMuNSwwLDUuOS0uOCw3LjItMi4zYTgsOCwwLDAsMCwyLTUuNFY4LjdhNy40OCw3LjQ4LDAsMCwwLTMuMy02LjZjLTIuMS0xLjQtNS0yLjEtOC42LTIuMWExOS4zLDE5LjMsMCwwLDAtOS40LDIsMTEuNjMsMTEuNjMsMCwwLDAtNS4xLDYuOEw3NC45MSw2Ny4xLDU0LjQxLDguNGExMi40LDEyLjQsMCwwLDAtNC41LTYuMmMtMi4xLTEuNS01LTIuMi04LjgtMi4yYTE2LjUxLDE2LjUxLDAsMCwwLTguOSwyLjFjLTIuMywxLjUtMy41LDMuOS0zLjUsNy4yVjgwLjhjMCwyLjguNyw0LjgsMiw2LjJDMzIuMjEsODguNCwzNC40MSw4OS4xLDM3LjcxLDg5LjFaIiAvPjxwYXRoIGQ9Ik0xNDksMTE2LjZsLTIuNC0xLjlhNy40LDcuNCwwLDAsMC05LjQuMywxOS42NSwxOS42NSwwLDAsMS0xMi41LDQuNmgtMjEuNEEzNy4wOCwzNy4wOCwwLDAsMCw3NywxMzAuNWwtMS4xLDEuMi0xLjEtMS4xYTM3LjI1LDM3LjI1LDAsMCwwLTI2LjMtMTAuOUgyN2ExOS41OSwxOS41OSwwLDAsMS0xMi40LTQuNiw3LjI4LDcuMjgsMCwwLDAtOS40LS4zbC0yLjQsMS45QTcuNDMsNy40MywwLDAsMCwwLDEyMi4yYTcuMTQsNy4xNCwwLDAsMCwyLjQsNS43QTM3LjI4LDM3LjI4LDAsMCwwLDI3LDEzNy40aDIxLjZhMTkuNTksMTkuNTksMCwwLDEsMTguOSwxNC40di4yYy4xLjcsMS4yLDQuNCw4LjUsNC40czguNC0zLjcsOC41LTQuNHYtLjJhMTkuNTksMTkuNTksMCwwLDEsMTguOS0xNC40SDEyNWEzNy4yOCwzNy4yOCwwLDAsMCwyNC42LTkuNSw3LjQyLDcuNDIsMCwwLDAsMi40LTUuN0E3Ljg2LDcuODYsMCwwLDAsMTQ5LDExNi42WiIgLz48L3N2Zz4=')
no-repeat center;
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNTIuMDIgMTU2LjQiPjxwYXRoIGQ9Ik0zNy43MSw4OS4xYzMuNSwwLDUuOS0uOCw3LjItMi4zYTgsOCwwLDAsMCwyLTUuNFYzNS43bDE3LDQ1LjFhMTIuNjgsMTIuNjgsMCwwLDAsMy43LDUuNGMxLjYsMS4zLDQsMiw3LjIsMmExMi41NCwxMi41NCwwLDAsMCw1LjktMS40LDguNDEsOC40MSwwLDAsMCwzLjktNWwxOC4xLTUwVjgxYTguNTMsOC41MywwLDAsMCwyLjEsNi4xYzEuNCwxLjQsMy43LDIuMiw2LjksMi4yLDMuNSwwLDUuOS0uOCw3LjItMi4zYTgsOCwwLDAsMCwyLTUuNFY4LjdhNy40OCw3LjQ4LDAsMCwwLTMuMy02LjZjLTIuMS0xLjQtNS0yLjEtOC42LTIuMWExOS4zLDE5LjMsMCwwLDAtOS40LDIsMTEuNjMsMTEuNjMsMCwwLDAtNS4xLDYuOEw3NC45MSw2Ny4xLDU0LjQxLDguNGExMi40LDEyLjQsMCwwLDAtNC41LTYuMmMtMi4xLTEuNS01LTIuMi04LjgtMi4yYTE2LjUxLDE2LjUxLDAsMCwwLTguOSwyLjFjLTIuMywxLjUtMy41LDMuOS0zLjUsNy4yVjgwLjhjMCwyLjguNyw0LjgsMiw2LjJDMzIuMjEsODguNCwzNC40MSw4OS4xLDM3LjcxLDg5LjFaIiAvPjxwYXRoIGQ9Ik0xNDksMTE2LjZsLTIuNC0xLjlhNy40LDcuNCwwLDAsMC05LjQuMywxOS42NSwxOS42NSwwLDAsMS0xMi41LDQuNmgtMjEuNEEzNy4wOCwzNy4wOCwwLDAsMCw3NywxMzAuNWwtMS4xLDEuMi0xLjEtMS4xYTM3LjI1LDM3LjI1LDAsMCwwLTI2LjMtMTAuOUgyN2ExOS41OSwxOS41OSwwLDAsMS0xMi40LTQuNiw3LjI4LDcuMjgsMCwwLDAtOS40LS4zbC0yLjQsMS45QTcuNDMsNy40MywwLDAsMCwwLDEyMi4yYTcuMTQsNy4xNCwwLDAsMCwyLjQsNS43QTM3LjI4LDM3LjI4LDAsMCwwLDI3LDEzNy40aDIxLjZhMTkuNTksMTkuNTksMCwwLDEsMTguOSwxNC40di4yYy4xLjcsMS4yLDQuNCw4LjUsNC40czguNC0zLjcsOC41LTQuNHYtLjJhMTkuNTksMTkuNTksMCwwLDEsMTguOS0xNC40SDEyNWEzNy4yOCwzNy4yOCwwLDAsMCwyNC42LTkuNSw3LjQyLDcuNDIsMCwwLDAsMi40LTUuN0E3Ljg2LDcuODYsMCwwLDAsMTQ5LDExNi42WiIgLz48L3N2Zz4=') no-repeat center;
background-size: contain;
display: inline-block;
height: 20px;
@ -14,18 +13,15 @@ Style for Members plugin
}
&:not([aria-selected='true']) .mailpoet-icon-logo {
filter: invert(24%) sepia(95%) saturate(1872%) hue-rotate(179deg)
brightness(93%) contrast(101%);
filter: invert(24%) sepia(95%) saturate(1872%) hue-rotate(179deg) brightness(93%) contrast(101%);
}
> a:hover .mailpoet-icon-logo,
> a:active .mailpoet-icon-logo {
filter: invert(49%) sepia(50%) saturate(3683%) hue-rotate(163deg)
brightness(94%) contrast(101%);
filter: invert(49%) sepia(50%) saturate(3683%) hue-rotate(163deg) brightness(94%) contrast(101%);
}
&[aria-selected='true'] a .mailpoet-icon-logo {
filter: invert(33%) sepia(0%) saturate(7%) hue-rotate(205deg)
brightness(94%) contrast(87%);
filter: invert(33%) sepia(0%) saturate(7%) hue-rotate(205deg) brightness(94%) contrast(87%);
}
}

View File

@ -1,50 +0,0 @@
@import '../components-automation/colors';
.mailpoet-automation-analytics-title {
color: $color-gutenberg-grey-700;
font-size: 16px;
a {
color: inherit;
text-decoration: none;
}
strong {
color: $color-gutenberg-grey-900;
}
.mailpoet-automation-status {
display: inline-block;
margin-left: 8px;
}
}
.mailpoet-analytics-header {
align-items: end;
display: flex;
justify-content: space-between;
margin-bottom: 24px;
.woocommerce-filters-filter {
width: 100%;
* {
box-sizing: border-box;
}
}
.components-dropdown {
.components-button.is-primary {
box-shadow: none;
}
}
}
.admin_page_mailpoet-automation-analytics {
.woocommerce-filters-date__content .components-popover__content {
padding: 0;
* {
box-sizing: border-box;
}
}
}

View File

@ -1,21 +0,0 @@
@import '../components-automation/colors';
.mailpoet-automation-analytics-overview {
background: $color-white;
margin-bottom: 24px;
h1 {
border: 1px solid $color-grey;
border-bottom: none;
padding: 16px 24px;
}
.woocommerce-summary {
background: $color-white;
margin: 0;
}
.woocommerce-summary__item {
background: $color-white;
}
}

View File

@ -1,29 +0,0 @@
.mailpoet-automation-analytics {
.woocommerce-table__header,
.woocommerce-table__item,
.woocommerce-table__empty-item {
text-wrap: balance;
@include respond-to(medium-screen) {
padding: 8px 12px;
}
}
.woocommerce-table__header.is-sortable {
padding: 2px;
button {
padding: 4px 20px;
width: 100%;
@include respond-to(medium-screen) {
padding: 4px 8px;
}
}
}
.woocommerce-table__header,
.woocommerce-table__header button {
font-weight: bold;
}
}

Some files were not shown because too many files have changed in this diff Show More