diff --git a/.env.example b/.env.example index e63441941..b279c54e7 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,9 @@ WORDPRESS_ROOT_DIR="/Users/tim/Local Sites/convertkit-github/app/public" # Local site URL WORDPRESS_URL=http://kit.local +# Local site DB SQL dump file +WORDPRESS_DB_SQL_DUMP_FILE="tests/Support/Data/dump.sql" + # Kit credentials CONVERTKIT_OAUTH_ACCESS_TOKEN= CONVERTKIT_OAUTH_REFRESH_TOKEN= diff --git a/.github/workflows/tests-backward-compat.yml b/.github/workflows/tests-backward-compat.yml new file mode 100644 index 000000000..2f73012b6 --- /dev/null +++ b/.github/workflows/tests-backward-compat.yml @@ -0,0 +1,329 @@ +name: Run Backward Compat. Tests + +# When to run tests. +on: + pull_request: + types: + - opened + - synchronize + push: + branches: + - main + +jobs: + tests: + # Name. + name: ${{ matrix.test-groups }} / WordPress ${{ matrix.wp-versions }} / PHP ${{ matrix.php-versions }} + + # Virtual Environment to use. + # @see: https://github.com/actions/virtual-environments + runs-on: ubuntu-latest + + # Environment Variables. + # Accessible by using ${{ env.NAME }} + # Use ${{ secrets.NAME }} to include any GitHub Secrets in ${{ env.NAME }} + env: + ROOT_DIR: /var/www/html + PLUGIN_DIR: /var/www/html/wp-content/plugins/convertkit + DB_NAME: test + DB_USER: root + DB_PASS: root + DB_HOST: localhost + WORDPRESS_DB_SQL_DUMP_FILE: tests/Support/Data/dump-6.2.8.sql # Used to populate the test site database for WordPress 6.2.8 + INSTALL_PLUGINS: "autoptimize debloat litespeed-cache rocket-lazy-load sg-cachepress" # Don't include this repository's Plugin here. + INSTALL_PLUGINS_URLS: "http://cktestplugins.wpengine.com/wp-content/uploads/2024/11/disable-doing-it-wrong-notices.zip https://downloads.wordpress.org/plugin/block-visibility.3.3.0.zip https://downloads.wordpress.org/plugin/jetpack-boost.3.5.0.zip" # URLs to specific third party Plugins or versions that support WordPress 6.2.8 + CONVERTKIT_API_KEY: ${{ secrets.CONVERTKIT_API_KEY }} # ConvertKit API Key, stored in the repository's Settings > Secrets + CONVERTKIT_API_SECRET: ${{ secrets.CONVERTKIT_API_SECRET }} # ConvertKit API Secret, stored in the repository's Settings > Secrets + CONVERTKIT_API_KEY_NO_DATA: ${{ secrets.CONVERTKIT_API_KEY_NO_DATA }} # ConvertKit API Key for ConvertKit account with no data, stored in the repository's Settings > Secrets + CONVERTKIT_API_SECRET_NO_DATA: ${{ secrets.CONVERTKIT_API_SECRET_NO_DATA }} # ConvertKit API Secret for ConvertKit account with no data, stored in the repository's Settings > Secrets + CONVERTKIT_OAUTH_CLIENT_ID: ${{ secrets.CONVERTKIT_OAUTH_CLIENT_ID }} + CONVERTKIT_OAUTH_REDIRECT_URI: ${{ secrets.CONVERTKIT_OAUTH_REDIRECT_URI }} + KIT_OAUTH_REDIRECT_URI: ${{ secrets.KIT_OAUTH_REDIRECT_URI }} + CONVERTKIT_API_SIGNED_SUBSCRIBER_ID: ${{ secrets.CONVERTKIT_API_SIGNED_SUBSCRIBER_ID }} # ConvertKit API Signed Subscriber ID, stored in the repository's Settings > Secrets + CONVERTKIT_API_SIGNED_SUBSCRIBER_ID_NO_ACCESS: ${{ secrets.CONVERTKIT_API_SIGNED_SUBSCRIBER_ID_NO_ACCESS }} # ConvertKit API Signed Subscriber ID with no access to Products, stored in the repository's Settings > Secrets + CONVERTKIT_API_RECAPTCHA_SITE_KEY: ${{ secrets.CONVERTKIT_API_RECAPTCHA_SITE_KEY }} # Google reCAPTCHA v3 Site Key, stored in the repository's Settings > Secrets + CONVERTKIT_API_RECAPTCHA_SECRET_KEY: ${{ secrets.CONVERTKIT_API_RECAPTCHA_SECRET_KEY }} # Google reCAPTCHA v3 Secret Key, stored in the repository's Settings > Secrets + + # Defines the WordPress and PHP Versions matrix to run tests on + # WordPress 6.2.8 and specific block tests are run to test apiVersion 2 blocks + strategy: + fail-fast: false + matrix: + wp-versions: [ '6.2.8' ] #[ '6.1.1', 'latest' ] + php-versions: [ '8.1' ] #[ '7.4', '8.0', '8.1' ] + + # Folder names within the 'tests' folder to run tests in parallel. + test-groups: [ + 'EndToEnd/broadcasts/blocks-shortcodes/PageBlockBroadcastsCest', + 'EndToEnd/forms/blocks-shortcodes/PageBlockFormBuilderCest', + 'EndToEnd/forms/blocks-shortcodes/PageBlockFormCest', + 'EndToEnd/forms/blocks-shortcodes/PageBlockFormTriggerCest', + 'EndToEnd/products/PageBlockFormatterProductLinkCest', + 'EndToEnd/products/PageBlockProductCest' + ] + + # Steps to install, configure and run tests + steps: + - name: Define Test Group Name + id: test-group + uses: mad9000/actions-find-and-replace-string@5 + with: + source: ${{ matrix.test-groups }} + find: '/' + replace: '-' + replaceAll: true + + # Checkout Plugin to /home/runner/work/convertkit-wordpress/convertkit-wordpress/convertkit + # We cannot checkout to ${{ env.PLUGIN_DIR }} as GitHub Actions require it be first placed in /home/runner/work/repo/repo + - name: Checkout Plugin + uses: actions/checkout@v4 + with: + path: /home/runner/work/convertkit-wordpress/convertkit-wordpress/convertkit + + - name: Start MySQL + run: sudo systemctl start mysql.service + + - name: Create MySQL Database + run: | + mysql -e 'CREATE DATABASE test;' -u${{ env.DB_USER }} -p${{ env.DB_PASS }} + mysql -e 'SHOW DATABASES;' -u${{ env.DB_USER }} -p${{ env.DB_PASS }} + + # WordPress won't be able to connect to the DB if we don't perform this step. + - name: Permit MySQL Password Auth for MySQL 8.0 + run: mysql -e "ALTER USER '${{ env.DB_USER }}'@'${{ env.DB_HOST }}' IDENTIFIED WITH mysql_native_password BY '${{ env.DB_PASS }}';" -u${{ env.DB_USER }} -p${{ env.DB_PASS }} + + # Some workflows checkout WordPress from GitHub, but that seems to bring a bunch of uncompiled files with it. + # Instead download from wordpress.org stable. + - name: Download and Extract WordPress + run: | + sudo chown -R runner:docker /var/www/html + ls -la /var/www/html + cd /var/www/html + wget https://wordpress.org/wordpress-${{ matrix.wp-versions }}.tar.gz + tar xfz wordpress-${{ matrix.wp-versions }}.tar.gz + mv wordpress/* . + rm -rf wordpress wordpress-${{ matrix.wp-versions }}.tar.gz + + # We install WP-CLI, as it provides useful commands to setup and install WordPress through the command line. + - name: Install WP-CLI + run: | + curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar + chmod +x wp-cli.phar + sudo mv wp-cli.phar /usr/local/bin/wp-cli + + - name: Setup wp-config.php + working-directory: ${{ env.ROOT_DIR }} + run: wp-cli config create --dbname=${{ env.DB_NAME }} --dbuser=${{ env.DB_USER }} --dbpass=${{ env.DB_PASS }} --dbhost=${{ env.DB_HOST }} --locale=en_DB + + - name: Install WordPress + working-directory: ${{ env.ROOT_DIR }} + run: wp-cli core install --url=127.0.0.1 --title=ConvertKit --admin_user=admin --admin_password=password --admin_email=wordpress@convertkit.local + + # env.INSTALL_PLUGINS is a list of Plugin slugs, space separated e.g. contact-form-7 woocommerce. + - name: Install Free Third Party WordPress Plugins + working-directory: ${{ env.ROOT_DIR }} + run: wp-cli plugin install ${{ env.INSTALL_PLUGINS }} + + # env.INSTALL_PLUGINS_URLS is a list of Plugin URLs, space separated, to install specific versions of third party Plugins. + - name: Install Free Third Party WordPress Specific Version Plugins + working-directory: ${{ env.ROOT_DIR }} + run: wp-cli plugin install ${{ env.INSTALL_PLUGINS_URLS }} + + # These should be stored as a separated list of URLs in the repository Settings > Secrets > Repository Secret > CONVERTKIT_PAID_PLUGIN_URLS. + # We cannot include the URLs in this file, as they're not Plugins we are permitted to distribute. + - name: Install Paid Third Party WordPress Plugins + working-directory: ${{ env.ROOT_DIR }} + run: wp-cli plugin install ${{ secrets.CONVERTKIT_PAID_PLUGIN_URLS }} + + # Move Plugin + - name: Move Plugin + run: mv /home/runner/work/convertkit-wordpress/convertkit-wordpress/convertkit ${{ env.PLUGIN_DIR }} + + # WP_DEBUG = true is required so all PHP errors are output and caught by tests (E_ALL). + - name: Enable WP_DEBUG + working-directory: ${{ env.ROOT_DIR }} + run: | + wp-cli config set WP_DEBUG true --raw + + # FS_METHOD = direct is required for WP_Filesystem to operate without suppressed PHP fopen() errors that trip up tests. + - name: Enable FS_METHOD + working-directory: ${{ env.ROOT_DIR }} + run: | + wp-cli config set FS_METHOD direct + + # DISALLOW_FILE_MODS = true is required to disable the block directory's "Available to Install" suggestions, which trips up + # tests that search and wait for block results. + - name: Enable DISALLOW_FILE_MODS + working-directory: ${{ env.ROOT_DIR }} + run: | + wp-cli config set DISALLOW_FILE_MODS true --raw + + # Prevent WordPress auto updating to the latest version, as we specifically want to use a specific version of WordPress. + - name: Disable WP_AUTO_UPDATE_CORE + working-directory: ${{ env.ROOT_DIR }} + run: | + wp-cli config set WP_AUTO_UPDATE_CORE false --raw + + # This step is deliberately after WordPress installation and configuration, as enabling PHP 8.x before using WP-CLI results + # in the workflow failing due to incompatibilities between WP-CLI and PHP 8.x. + # By installing PHP at this stage, we can still run our tests against e.g. PHP 8.x. + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: xdebug + + # Configure nginx to use the PHP version and WOrdPress installation at /var/www/html + - name: Configure nginx site + run: | + sudo rm -f /etc/nginx/sites-enabled/default + sudo tee /etc/nginx/sites-available/default > /dev/null << 'EOF' + + server { + listen 80 default_server; + listen [::]:80 default_server; + + root /var/www/html; + index index.php; + + server_name localhost; + + location / { + try_files $uri $uri/ /index.php?$args; + } + + location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass unix:/run/php/php${{ matrix.php-versions }}-fpm.sock; + + # Prevent 502 Bad Gateway error "upstream sent too big header while reading response header from upstream" + fastcgi_buffers 16 16k; + fastcgi_buffer_size 32k; + fastcgi_busy_buffers_size 64k; + } + + location ~ /\.ht { + deny all; + } + } + EOF + + sudo ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default || true + + - name: Test nginx + run: sudo nginx -t + + - name: Start nginx + run: sudo systemctl start nginx.service + + # Start chromedriver + - name: Start chromedriver + run: | + export DISPLAY=:99 + chromedriver --port=9515 --url-base=/wd/hub & + sudo Xvfb -ac :99 -screen 0 1920x1080x24 > /dev/null 2>&1 & # optional + + # Exchange API Keys and Secrets for OAuth Tokens. + - name: Exchange API Key and Secret for OAuth Tokens + id: get-oauth-tokens + run: | + response=$(curl -s -X POST "${{ secrets.CONVERTKIT_EXCHANGE_API_KEYS_ENDPOINT }}?api_key=${{ env.CONVERTKIT_API_KEY }}&api_secret=${{ env.CONVERTKIT_API_SECRET }}&client_id=${{ env.CONVERTKIT_OAUTH_CLIENT_ID }}&redirect_uri=${{ env.CONVERTKIT_OAUTH_REDIRECT_URI }}&tenant_name=github-actions-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}") + access_token=$(echo "$response" | jq -r '.oauth.access_token') + refresh_token=$(echo "$response" | jq -r '.oauth.refresh_token') + echo "CONVERTKIT_OAUTH_ACCESS_TOKEN=$access_token" >> $GITHUB_ENV + echo "CONVERTKIT_OAUTH_REFRESH_TOKEN=$refresh_token" >> $GITHUB_ENV + response=$(curl -s -X POST "${{ secrets.CONVERTKIT_EXCHANGE_API_KEYS_ENDPOINT }}?api_key=${{ env.CONVERTKIT_API_KEY_NO_DATA }}&api_secret=${{ env.CONVERTKIT_API_SECRET_NO_DATA }}&client_id=${{ env.CONVERTKIT_OAUTH_CLIENT_ID }}&redirect_uri=${{ env.CONVERTKIT_OAUTH_REDIRECT_URI }}&tenant_name=github-actions-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}") + access_token=$(echo "$response" | jq -r '.oauth.access_token') + refresh_token=$(echo "$response" | jq -r '.oauth.refresh_token') + echo "CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA=$access_token" >> $GITHUB_ENV + echo "CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA=$refresh_token" >> $GITHUB_ENV + + # Write any secrets, such as API keys, to the .env.testing file now. + - name: Define GitHub Secrets in .env.dist.testing + uses: DamianReeves/write-file-action@v1.3 + with: + path: ${{ env.PLUGIN_DIR }}/.env.testing + contents: | + WORDPRESS_DB_SQL_DUMP_FILE=${{ env.WORDPRESS_DB_SQL_DUMP_FILE }} + CONVERTKIT_API_KEY=${{ env.CONVERTKIT_API_KEY }} + CONVERTKIT_API_SECRET=${{ env.CONVERTKIT_API_SECRET }} + CONVERTKIT_API_KEY_NO_DATA=${{ env.CONVERTKIT_API_KEY_NO_DATA }} + CONVERTKIT_API_SECRET_NO_DATA=${{ env.CONVERTKIT_API_SECRET_NO_DATA }} + CONVERTKIT_OAUTH_ACCESS_TOKEN=${{ env.CONVERTKIT_OAUTH_ACCESS_TOKEN }} + CONVERTKIT_OAUTH_REFRESH_TOKEN=${{ env.CONVERTKIT_OAUTH_REFRESH_TOKEN }} + CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA=${{ env.CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA }} + CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA=${{ env.CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA }} + CONVERTKIT_OAUTH_CLIENT_ID=${{ env.CONVERTKIT_OAUTH_CLIENT_ID }} + CONVERTKIT_OAUTH_REDIRECT_URI=${{ env.CONVERTKIT_OAUTH_REDIRECT_URI }} + KIT_OAUTH_REDIRECT_URI=${{ env.KIT_OAUTH_REDIRECT_URI }} + CONVERTKIT_API_SIGNED_SUBSCRIBER_ID=${{ env.CONVERTKIT_API_SIGNED_SUBSCRIBER_ID }} + CONVERTKIT_API_SIGNED_SUBSCRIBER_ID_NO_ACCESS=${{ env.CONVERTKIT_API_SIGNED_SUBSCRIBER_ID_NO_ACCESS }} + CONVERTKIT_API_RECAPTCHA_SITE_KEY=${{ env.CONVERTKIT_API_RECAPTCHA_SITE_KEY }} + CONVERTKIT_API_RECAPTCHA_SECRET_KEY=${{ env.CONVERTKIT_API_RECAPTCHA_SECRET_KEY }} + + write-mode: overwrite + + # Installs wp-browser, Codeception, PHP CodeSniffer and anything else needed to run tests. + - name: Run Composer + working-directory: ${{ env.PLUGIN_DIR }} + run: composer update + + - name: Build PHP Autoloader + working-directory: ${{ env.PLUGIN_DIR }} + run: composer dump-autoload + + # This ensures that applicable files and folders can be written to by WordPress and cache Plugins. + - name: Set File and Folder Permissions + run: | + sudo chmod 767 ${{ env.ROOT_DIR }} + sudo chown www-data:www-data ${{ env.ROOT_DIR }} + + sudo chmod 767 ${{ env.ROOT_DIR }}/wp-config.php + sudo chown www-data:www-data ${{ env.ROOT_DIR }}/wp-config.php + + sudo chmod 767 ${{ env.ROOT_DIR }}/wp-content + sudo chown www-data:www-data ${{ env.ROOT_DIR }}/wp-content + + sudo chmod -R 767 ${{ env.ROOT_DIR }}/wp-content/uploads + sudo chown www-data:www-data ${{ env.ROOT_DIR }}/wp-content/uploads + + # This ensures the Plugin's log file can be written to. + # We don't recursively do this, as it'll prevent Codeception from writing to the /tests/_output directory. + - name: Set Permissions for Plugin Directory + run: | + sudo chmod g+w ${{ env.PLUGIN_DIR }} + sudo chown www-data:www-data ${{ env.PLUGIN_DIR }} + + # Build Codeception Tests. + - name: Build Tests + working-directory: ${{ env.PLUGIN_DIR }} + run: php vendor/bin/codecept build + + # Run Codeception Tests. + - name: Run tests/${{ matrix.test-groups }} + working-directory: ${{ env.PLUGIN_DIR }} + run: php vendor/bin/codecept run tests/${{ matrix.test-groups }} --fail-fast + + # Artifacts are data generated by this workflow that we want to access, such as log files, screenshots, HTML output. + # The if: failure() directive means that this will run when the workflow fails e.g. if a test fails, which is needed + # because we want to see why a test failed. + - name: Upload nginx Error Log to Artifact + if: failure() + uses: actions/upload-artifact@v4 + with: + name: nginx-error-log-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}.log + path: /var/log/nginx/error.log + + - name: Upload Test Results to Artifact + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }} + path: ${{ env.PLUGIN_DIR }}/tests/_output/ + + - name: Upload Plugin Log File to Artifact + if: failure() + uses: actions/upload-artifact@v4 + with: + name: log-${{ steps.test-group.outputs.value }}-${{ matrix.php-versions }}.txt + path: ${{ env.PLUGIN_DIR }}/log/log.txt \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ec742687a..d59ffb260 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,6 +30,7 @@ jobs: DB_USER: root DB_PASS: root DB_HOST: localhost + WORDPRESS_DB_SQL_DUMP_FILE: tests/Support/Data/dump.sql # Used to populate the test site database for WordPress 6.2.8 INSTALL_PLUGINS: "admin-menu-editor autoptimize beaver-builder-lite-version block-visibility contact-form-7 classic-editor custom-post-type-ui debloat elementor forminator jetpack-boost mailchimp-for-wp rocket-lazy-load woocommerce wordpress-seo wpforms-lite litespeed-cache wp-crontrol wp-super-cache w3-total-cache wp-fastest-cache wp-optimize sg-cachepress" # Don't include this repository's Plugin here. INSTALL_PLUGINS_URLS: "https://downloads.wordpress.org/plugin/convertkit-for-woocommerce.1.6.4.zip http://cktestplugins.wpengine.com/wp-content/uploads/2024/01/convertkit-action-filter-tests.zip http://cktestplugins.wpengine.com/wp-content/uploads/2024/11/disable-doing-it-wrong-notices.zip http://cktestplugins.wpengine.com/wp-content/uploads/2025/03/uncode-js_composer.7.8.zip http://cktestplugins.wpengine.com/wp-content/uploads/2025/03/uncode-core.zip" # URLs to specific third party Plugins INSTALL_THEMES_URLS: "http://cktestplugins.wpengine.com/wp-content/uploads/2025/03/uncode.zip http://cktestplugins.wpengine.com/wp-content/uploads/2025/04/Divi.zip" @@ -265,6 +266,7 @@ jobs: with: path: ${{ env.PLUGIN_DIR }}/.env.testing contents: | + WORDPRESS_DB_SQL_DUMP_FILE=${{ env.WORDPRESS_DB_SQL_DUMP_FILE }} CONVERTKIT_API_KEY=${{ env.CONVERTKIT_API_KEY }} CONVERTKIT_API_SECRET=${{ env.CONVERTKIT_API_SECRET }} CONVERTKIT_API_KEY_NO_DATA=${{ env.CONVERTKIT_API_KEY_NO_DATA }} diff --git a/includes/blocks/class-convertkit-block-form-builder-field-email.php b/includes/blocks/class-convertkit-block-form-builder-field-email.php index 5389d463c..883ce7cb9 100644 --- a/includes/blocks/class-convertkit-block-form-builder-field-email.php +++ b/includes/blocks/class-convertkit-block-form-builder-field-email.php @@ -32,6 +32,15 @@ class ConvertKit_Block_Form_Builder_Field_Email extends ConvertKit_Block_Form_Bu */ public $field_id = 'email'; + /** + * The type of field to render. + * + * @since 3.1.4 + * + * @var string + */ + public $field_type = 'email'; + /** * Whether the field is required. * diff --git a/readme.txt b/readme.txt index bd7c11447..6c7fba939 100755 --- a/readme.txt +++ b/readme.txt @@ -52,12 +52,14 @@ Start collecting email subscribers today, building your newsletter audience and Kit's Form Builder block, for the WordPress block editor, gives complete control to design and customize your own subscription and email marketing forms - directly in WordPress. With the Form Builder block, you can: + - Add fields such as first name, email address, and custom fields - Apply tags and sequences to subscribers for powerful segmentation - Enable reCAPTCHA protection (if configured in your Kit account) - Style forms using block editor controls, with full support for your block theme’s CSS and styling This makes the Form Builder block ideal for: + - Subscription forms to grow your email subscribers list - Contact forms that connect directly to Kit - Custom forms tailored to your email marketing strategy @@ -140,42 +142,204 @@ Full Plugin documentation can be found [here](https://help.kit.com/en/articles/2 == Frequently asked questions == -= Does this plugin require a paid service? = += Is Kit free for email newsletter creators? = + +Yes! Kit is 100% free for your first 10,000 email subscribers - [sign up today](https://kit.com?utm_source=wordpress&utm_term=en_US&utm_content=readme). This makes it perfect for new newsletter creators, bloggers starting email marketing, and small membership sites. You can grow your email subscribers list, send newsletters, create landing pages, and even sell membership products without paying anything until you exceed 10,000 newsletter subscribers. + += How do I create newsletter signup forms? = + +Create newsletter subscription forms directly in your Kit account using the visual form builder. Design inline forms, modal popups, slide-in forms, or sticky bars optimized for email subscriber conversion. Once created, the newsletter forms automatically sync with the WordPress plugin and become available for embedding on your site to grow your email subscribers list. + += Can I create landing pages for email subscriber growth? = + +Yes! Design professional landing pages in Kit's landing page editor, then display them on any WordPress page. Landing pages are optimized specifically for email newsletter signups and subscriber conversion. Choose from pre-built landing page templates or customize your own. The plugin seamlessly integrates Kit landing pages into WordPress for consistent subscriber experience. + += How does the membership site feature work? = + +The membership feature lets you restrict WordPress content to paying email subscribers or newsletter members. Create Products in Kit, then assign them to specific WordPress pages or posts. When email subscribers visit protected content, they're prompted to enter their email. If they've purchased the required Kit Product, they receive membership access via a magic link. No complex membership plugins needed. + += Can I segment my email subscribers for targeted newsletters? = + +Absolutely! Kit provides powerful subscriber segmentation using tags. Automatically tag email subscribers based on which newsletter form they used, which links they clicked, or which Kit Products they purchased. Send targeted email newsletters to specific subscriber segments for more relevant email marketing campaigns and higher newsletter engagement rates. + += Do I need coding knowledge to use this plugin? = + +No coding required! Create newsletter subscription forms, landing pages, and email campaigns using Kit's visual editors. Embed newsletter forms using native WordPress blocks or simple shortcodes. Build membership sites by selecting Products from dropdowns. Even custom newsletter forms in WordPress can be built using the drag-and-drop block editor without touching code. + += How do I refresh available forms, landing pages, and tags? = + +Click the refresh button next to Form, Landing Page, or Tag dropdowns when editing Pages or Posts. This syncs your latest newsletter forms and products from Kit to WordPress. You can also refresh all resources at once by navigating to Settings > Kit and clicking refresh. This ensures your newest email subscriber capture forms are always available. + += Can I automatically import email newsletters to WordPress? = + +Yes! Navigate to Settings > Kit > Broadcasts and enable automatic import. Your published email newsletters from Kit will automatically import as WordPress posts. Configure the import schedule, post author, category, and featured image settings. This keeps newsletter subscribers engaged by making your email content discoverable on your site. + += What integrations work with email subscriber capture? = + +The plugin integrates with Contact Form 7, Gravity Forms, WooCommerce, WishList Member, Elementor, and more. Automatically add form submissions as email subscribers, tag WooCommerce customers for email marketing, sync membership levels with newsletter subscriber tags, and use native Elementor widgets for newsletter forms. Additional integrations available through separate plugins. + += How do I display newsletter forms in specific locations? = + +You have multiple options for newsletter form placement. Set site-wide defaults that display newsletter signup forms automatically below posts and pages. Override defaults on individual posts to show specific email subscriber forms. Use blocks or shortcodes to embed newsletter forms within content at optimal conversion points. Add widget newsletter forms to sidebars and footers. + += Can I create free membership content for newsletter subscribers? = + +Yes! You can gate content requiring only email subscription without requiring payment. This is perfect for exclusive newsletter subscriber content, free member communities, or content upgrades. Use Tag-based restrictions where entering an email and receiving membership access via magic link is all that's required. Great for building email subscriber loyalty. + += Does this work with my WordPress theme? = + +The plugin works with any WordPress theme. Newsletter signup forms and landing pages are designed to adapt to your theme's styling. The Form Builder block uses your theme's CSS ensuring newsletter forms match your site perfectly. Inline forms blend seamlessly into content while modal, slide-in, and sticky bar newsletter forms maintain Kit's polished design. + += How do I track email subscriber growth and conversions? = + +Track subscriber growth, newsletter form conversion rates, email campaign performance, and landing page analytics directly in your Kit dashboard. Monitor which newsletter forms convert best, which landing pages drive the most email subscribers, and which email newsletters get the highest engagement from your subscriber list. All metrics update in real-time. + += What's the difference between inline and non-inline newsletter forms? = + +Inline newsletter forms embed directly within page content and scroll with the page. They're great for email subscriber capture within blog posts. Non-inline forms include modals (popups), slide-ins (appear from corners), and sticky bars (fixed to top/bottom). Non-inline newsletter forms demand attention and often achieve higher email subscriber conversion rates. + += Can I customize newsletter form colors and styling? = + +Yes! Customize newsletter subscription forms in Kit's form editor matching your brand colors, fonts, and style. Adjust form button colors, input field styling, and background colors. For inline forms, the Form Builder block in WordPress uses your theme's styling automatically, ensuring newsletter forms blend seamlessly with your site design. + += What happens when someone subscribes to my newsletter? = + +When visitors submit newsletter signup forms, they're added as email subscribers to your Kit account. You can configure forms to immediately add subscribers to your newsletter list, tag them for segmentation, or enroll them in automated email sequences. Subscribers receive welcome emails if configured, and you can start sending email newsletters immediately. + += How do membership magic links work for subscribers? = + +When an email subscriber tries accessing protected membership content, they enter their email address. If they've purchased the required Kit Product or have the necessary tag, they receive an email containing a one-time code and magic link. Clicking the magic link or entering the code grants immediate membership access without requiring passwords. + += Can I sell digital products through membership features? = + +Yes! Create Products in Kit and sell them directly to your email subscribers. Products can be one-time purchases, subscriptions, or tip jars. Assign Products to WordPress pages and posts as membership content. When newsletter subscribers purchase Products, they automatically get membership access to all associated content in WordPress. + += Do newsletter forms slow down my site? = + +No. Newsletter subscription forms load asynchronously and are optimized for performance. The plugin only loads necessary assets on pages actually displaying newsletter forms. Landing pages are hosted on Kit's servers ensuring fast load times. The lightweight codebase ensures email subscriber capture doesn't impact your WordPress site speed. + += How many newsletter forms can I create? = + +There's no limit to the number of newsletter subscription forms you can create in Kit. Design different forms for different audiences, pages, or lead magnets. Create targeted newsletter signup forms for each email subscriber segment. Test variations of forms to optimize conversion. All forms sync automatically with the WordPress plugin. + += Can I schedule email newsletters to subscribers? = + +Yes! Kit's email marketing platform lets you schedule newsletter campaigns to send at optimal times for your email subscribers. Write newsletters in advance and schedule them for future delivery. Set up recurring newsletters that send automatically on a schedule. Ensure global email subscribers receive newsletters at appropriate times for their timezone. + += What's the difference between tags and segments for email subscribers? = + +Tags are labels you apply to newsletter subscribers based on interests, behavior, or characteristics. Segments are filtered views of your subscriber list based on one or more tags. Use tags to organize email subscribers, then create segments to send targeted newsletters. For example, tag subscribers as "interested in courses" then create a segment for course email marketing. + += Can I migrate existing email subscribers to Kit? = + +Yes! Kit provides easy import tools for migrating email subscribers from other platforms. Import CSV files containing subscriber data. The plugin maintains subscriber tags and preferences during migration. You can continue sending newsletters immediately after importing. Kit's migration support team helps with large subscriber list transfers. + += How do I remove email subscribers from newsletters? = + +Email subscribers can unsubscribe from newsletters using links automatically included in every email campaign per email marketing regulations. You can also manually remove subscribers from Settings > Kit in WordPress or directly in your Kit account. Unsubscribed email addresses are automatically excluded from future newsletter sends. + += Does this work with WooCommerce for email marketing? = + +Yes! The separate Kit for WooCommerce plugin adds powerful integrations. Automatically subscribe WooCommerce customers to email newsletters at checkout. Tag purchasers for targeted email marketing campaigns. Track purchase data for each newsletter subscriber. Send abandoned cart emails to recover lost sales. Build email subscriber value over time. + += Can I create multiple membership tiers for subscribers? = + +Yes! Create different Kit Products for each membership tier. Assign Products to different WordPress content. Newsletter subscribers purchase the membership tier they want and automatically get access to corresponding content. Create Bronze, Silver, Gold membership tiers each with different Products and content access for paying email subscribers. + += How do landing pages improve email subscriber conversions? = + +Landing pages are specifically designed for single conversion goals like email newsletter signups. They remove navigation and distractions, focusing visitors entirely on becoming email subscribers. Use compelling headlines, clear value propositions, social proof, and strong calls-to-action. Kit's landing page templates are proven to convert visitors into newsletter subscribers at high rates. + += What email marketing automations can I create for subscribers? = + +Create welcome sequences greeting new email subscribers with your best content. Build product launch funnels promoting offers to newsletter segments. Set up abandoned cart sequences for WooCommerce email marketing. Create re-engagement campaigns for inactive newsletter subscribers. Configure behavioral automations tagging email subscribers based on email clicks and website activity. + += Can I embed past email newsletters on my WordPress site? = + +Yes! Use the Broadcasts block or `[convertkit_broadcasts]` shortcode to display past email newsletters on any page or post. This keeps newsletter content discoverable for website visitors, drives email subscriber signups from engaged readers, and extends the life of newsletter content beyond inboxes. + += How do I optimize newsletter forms for mobile email subscribers? = + +All newsletter subscription forms and landing pages are automatically mobile-responsive. Forms resize and reorganize for small screens ensuring easy email subscriber signup on phones and tablets. Test newsletter forms on mobile devices to verify the email subscriber experience. The Form Builder block uses responsive WordPress patterns for perfect mobile newsletter conversion. + += What's the best strategy for growing email subscribers? = + +Use multiple newsletter form types strategically. Place inline newsletter forms within valuable blog posts where visitors are most engaged. Add exit-intent modal newsletter forms to capture abandoning visitors as email subscribers. Include sticky bar newsletter forms for persistent visibility. Create dedicated landing pages for lead magnets. Test and optimize all newsletter forms for maximum email subscriber growth. + += Can I use newsletter forms with page builders? = + +Yes! The plugin includes integrations with popular page builders. Use native Elementor widgets for newsletter forms and email subscriber content. Divi modules available for newsletter integration. All page builders support the shortcode method for email subscriber forms. The WordPress block editor provides native blocks for newsletter capture. + += How do I create a newsletter lead magnet funnel? = + +Create a compelling lead magnet (ebook, checklist, course) that solves a specific problem for your target email subscribers. Design a dedicated landing page promoting the lead magnet optimized for newsletter signups. Create a newsletter subscription form offering the lead magnet in exchange for email addresses. Set up an automated email sequence delivering the lead magnet and nurturing new newsletter subscribers. + += What is the Form Builder block for email newsletter forms? = + +The Form Builder block is a native WordPress block that lets you create custom email newsletter subscription forms directly in the WordPress editor without any coding. Unlike embedding pre-designed forms from Kit, the Form Builder block gives you complete control to design newsletter signup forms that perfectly match your WordPress theme's styling and branding for optimal email subscriber conversion. + += How does the Form Builder block differ from regular newsletter forms? = + +Regular newsletter forms are designed in Kit and embedded in WordPress. The Form Builder block lets you build email subscription forms entirely within WordPress using the block editor. Form Builder newsletter forms automatically inherit your theme's typography, colors, and spacing. You can customize every aspect including form fields, button styling, colors, spacing, and layout while maintaining your site's design consistency for email subscribers. + += Can I customize Form Builder newsletter forms to match my brand? = + +Yes! The Form Builder block provides extensive customization options for email newsletter forms. Adjust typography including font families, sizes, and weights. Customize colors for form fields, buttons, labels, and backgrounds. Control spacing with margin and padding settings. Add custom CSS classes for advanced styling. Form Builder newsletter forms adapt to your WordPress theme while allowing granular control over email subscriber form appearance. + += What fields can I add to Form Builder newsletter subscription forms? = + +The Form Builder block supports multiple field types for email newsletter signups. Add email address fields (required), first name fields, last name fields, and custom fields you've created in Kit. All fields can be marked as required or optional. This flexibility lets you collect exactly the email subscriber information you need while keeping newsletter forms simple and conversion-focused. + += Does the Form Builder block work with Kit tags and sequences? = + +Absolutely! When building newsletter subscription forms with the Form Builder block, you can assign Kit tags to segment email subscribers automatically. Subscribe new email addresses to specific Kit sequences to trigger automated email marketing campaigns. Configure reCAPTCHA protection if enabled in your Kit account. Form Builder newsletter forms have full integration with Kit's email subscriber management features. + += How do I add a Form Builder newsletter form to my WordPress site? = + +In the WordPress block editor, click the plus icon to add a new block and search for "Kit Form Builder." Add the block to your page or post. Configure your email newsletter form by adding fields like email and first name. Customize the styling, colors, and spacing to match your theme. Optionally assign tags or sequences for email subscriber segmentation. Publish to start capturing email subscribers with your custom newsletter form. + += Can Form Builder newsletter forms collect custom subscriber data? = + +Yes! The Form Builder block supports custom fields you've created in your Kit account. Add custom fields to collect specific information from email newsletter subscribers like job titles, company names, interests, or any data relevant to your email marketing strategy. Custom field data syncs to Kit automatically, enabling advanced email subscriber segmentation and personalized newsletter campaigns. + += Are Form Builder newsletter forms mobile responsive? = + +All Form Builder newsletter subscription forms are automatically mobile responsive. Forms adapt to screen sizes ensuring email subscribers can easily sign up on phones and tablets. The Form Builder block uses responsive WordPress patterns and follows your theme's mobile styling. Test newsletter forms on multiple devices to verify the optimal email subscriber experience across all screen sizes. + += Can I use the Form Builder block for contact forms and email subscribers? = + +Yes! The Form Builder block works as both a newsletter subscription form and a contact form. Collect email addresses and additional information from visitors. When someone submits a Form Builder form, they're automatically added as email subscribers to your Kit account and can be tagged or added to sequences. This makes Form Builder perfect for dual-purpose forms capturing both contacts and newsletter subscribers. -No. You must first have an account on [kit.com](https://kit.com?utm_source=wordpress&utm_term=en_US&utm_content=readme), but you do not have to use a paid plan! += Does the Form Builder block support spam protection for newsletter signups? = -= How do I refresh my available Forms, Landing Pages and Tags? = +Yes! The Form Builder block integrates with Google reCAPTCHA v3 if configured in your Kit account at Settings > Kit > General. This protects your email newsletter forms from bot submissions and fake email subscribers. reCAPTCHA runs invisibly in the background, ensuring legitimate newsletter subscribers have a smooth signup experience while blocking spam from polluting your email list. -Either: += How does the Form Builder block handle newsletter form styling? = -- Navigate to the Plugin's Settings at `Settings > Kit` -- Click the refresh button displayed next to the Form, Landing Page or Tag fields when creating/editing Pages or Posts to ensure your latest email newsletter forms are available +The Form Builder block uses your WordPress theme's CSS by default, ensuring newsletter subscription forms match your site design automatically. You can then customize individual elements including button colors, input field borders, label typography, and spacing. The block provides intuitive controls in the WordPress editor sidebar letting you style email newsletter forms visually without touching code while maintaining theme consistency. -= How do I automatically import Kit Broadcasts to WordPress Posts? = += Can I view Form Builder newsletter form submissions in WordPress? = -To import your past (and future) email newsletters from Kit to WordPress: +Yes! Navigate to Settings > Kit > Form Entries to view all submissions from Form Builder newsletter forms. Search entries by email address, filter by submission date or API result, and export subscriber data as CSV files. This gives you a WordPress-based view of email newsletter signups alongside Kit's subscriber management, perfect for tracking email subscriber acquisition and form performance. -- Navigate to the Plugin's Settings at `Settings > Kit` -- Click the `Broadcasts` tab -- Tick the `Enable Automatic Import` option -- Configure other settings as necessary, and click `Save Changes` when done to streamline your email newsletter publishing += Where can I find complete plugin documentation? = -= Where can I find the Plugin's Documentation? = +Comprehensive documentation is available at: [Kit WordPress Plugin Setup Guide](https://help.kit.com/en/articles/2502591-how-to-set-up-the-kit-plugin-on-your-wordpress-website?utm_source=wordpress&utm_content=readme) -Full Plugin documentation can be found [here](https://help.kit.com/en/articles/2502591-how-to-set-up-the-kit-plugin-on-your-wordpress-website?utm_source=wordpress&utm_content=readme). +The documentation covers newsletter form setup, landing page configuration, membership site creation, email marketing automation, subscriber segmentation, and all plugin features for growing your email newsletter. == Screenshots == -1. Create and customize stunning email subscription forms and landing pages in Kit -2. Manage the WordPress plugin from a simple settings page in the WordPress admin -3. Append Kit forms to Pages, Posts or other Custom Post Types to grow your email subscribers -4. Configure a specific Kit form to display on a specific Page or Post -5. Display your Kit forms anywhere on your WordPress web site using the form block / shortcode -6. Configure a Kit landing page to be used in place of a WordPress Page, to capture email subscribers -7. Automatically import your past email newsletters to WordPress Posts -8. Create your paid membership site by assigning paid Kit Products to your existing WordPress content -9. Set up form and landing page automations in Kit -10. Track subscriber growth and email newsletter performance +1. Create stunning email newsletter subscription forms and landing pages in Kit's visual editor optimized for email subscriber conversion +2. Simple WordPress plugin settings page to manage email newsletter forms, landing pages, and membership features for subscribers +3. Automatically append newsletter signup forms to Posts, Pages, or Custom Post Types to capture email subscribers site-wide +4. Configure specific email newsletter forms on individual Pages and Posts for targeted subscriber growth and email marketing +5. Display newsletter subscription forms anywhere using blocks or shortcodes to maximize email subscriber capture opportunities +6. Replace WordPress pages with Kit landing pages optimized specifically for email newsletter subscriber acquisition +7. Automatically import published email newsletters from Kit to WordPress Posts keeping subscribers engaged with searchable newsletter content +8. Build paid membership sites by assigning Kit Products to WordPress content accessible only to paying email subscribers +9. Set up form automations and email marketing sequences in Kit to nurture newsletter subscribers and drive conversions +10. Track email subscriber growth, newsletter performance, landing page conversions, and membership site analytics in real-time == Changelog == diff --git a/resources/backend/css/gutenberg.css b/resources/backend/css/gutenberg.css index fb2df6d06..54391d2dd 100644 --- a/resources/backend/css/gutenberg.css +++ b/resources/backend/css/gutenberg.css @@ -36,9 +36,12 @@ top: 20px; right: 20px; line-height: 22px; + width: auto; } -.wp-block .convertkit-no-content .convertkit-progress-bar { +.wp-block .convertkit-no-content .convertkit-progress-bar, +.wp-block .convertkit-no-content .spinner { + float: none; margin: 0 auto; } diff --git a/resources/backend/js/gutenberg.js b/resources/backend/js/gutenberg.js index c003ac0dd..258fe1fff 100644 --- a/resources/backend/js/gutenberg.js +++ b/resources/backend/js/gutenberg.js @@ -599,9 +599,18 @@ function convertKitGutenbergRegisterBlock(block) { * @return {Object} Progress Bar. */ const loadingIndicator = function (props) { + // If the ProgressBar component is not available i.e. WordPress < 6.3, return a spinner. + if (typeof ProgressBar === 'undefined') { + return el('span', { + key: props.clientId + '-spinner', + className: 'spinner is-active convertkit-block-refreshing', + }); + } + return el(ProgressBar, { key: props.clientId + '-progress-bar', - className: 'convertkit-progress-bar', + className: + 'convertkit-progress-bar convertkit-block-refreshing', }); }; diff --git a/tests/EndToEnd.suite.yml b/tests/EndToEnd.suite.yml index 9bc855a5a..7cf15b9ed 100644 --- a/tests/EndToEnd.suite.yml +++ b/tests/EndToEnd.suite.yml @@ -67,7 +67,7 @@ modules: download.default_directory: '%WORDPRESS_ROOT_DIR%' lucatume\WPBrowser\Module\WPDb: dbUrl: '%WORDPRESS_DB_URL%' - dump: 'tests/Support/Data/dump.sql' + dump: '%WORDPRESS_DB_SQL_DUMP_FILE%' #import the dump before the tests; this means the test site database will be repopulated before the tests. populate: true # re-import the dump between tests; this means the test site database will be repopulated between the tests. diff --git a/tests/EndToEnd/broadcasts/blocks-shortcodes/PageBlockBroadcastsCest.php b/tests/EndToEnd/broadcasts/blocks-shortcodes/PageBlockBroadcastsCest.php index 4a761ec8c..81161668f 100644 --- a/tests/EndToEnd/broadcasts/blocks-shortcodes/PageBlockBroadcastsCest.php +++ b/tests/EndToEnd/broadcasts/blocks-shortcodes/PageBlockBroadcastsCest.php @@ -783,7 +783,18 @@ public function testBroadcastsBlockWithThemeColorParameters(EndToEndTester $I) $I->seeInSource('seeElementHasClasses( + $I, + '.convertkit-broadcasts', + [ + 'convertkit-broadcasts', + 'wp-block-convertkit-broadcasts', + 'has-text-color', + 'has-' . $textColor . '-color', + 'has-background', + 'has-' . $backgroundColor . '-background-color', + ] + ); } /** @@ -835,7 +846,16 @@ public function testBroadcastsBlockWithHexColorParameters(EndToEndTester $I) $I->seeInSource('seeElementHasClasses( + $I, + '.convertkit-broadcasts', + [ + 'convertkit-broadcasts', + 'wp-block-convertkit-broadcasts', + 'has-text-color', + 'has-background', + ] + ); } /** diff --git a/tests/EndToEnd/forms/blocks-shortcodes/PageBlockFormBuilderCest.php b/tests/EndToEnd/forms/blocks-shortcodes/PageBlockFormBuilderCest.php index 38ee77d1a..3cccaf73f 100644 --- a/tests/EndToEnd/forms/blocks-shortcodes/PageBlockFormBuilderCest.php +++ b/tests/EndToEnd/forms/blocks-shortcodes/PageBlockFormBuilderCest.php @@ -1283,10 +1283,10 @@ private function seeFormBuilderField(EndToEndTester $I, $fieldType, $fieldName, // Check field exists with correct attributes. switch ( $fieldType ) { case 'textarea': - $I->seeElementInDOM($container . ' textarea[name="convertkit[' . $fieldName . ']"][id="' . $fieldID . '"]' . $required ? '[required]' : ''); + $I->seeElementInDOM($container . ' textarea[name="convertkit[' . $fieldName . ']"][id="' . $fieldID . '"]' . ( $required ? '[required]' : '' ) ); break; default: - $I->seeElementInDOM($container . ' input[name="convertkit[' . $fieldName . ']"][type="' . $fieldType . '"][id="' . $fieldID . '"]' . $required ? '[required]' : ''); + $I->seeElementInDOM($container . ' input[name="convertkit[' . $fieldName . ']"][type="' . $fieldType . '"][id="' . $fieldID . '"]' . ( $required ? '[required]' : '' ) ); } // Check label exists with correct text. diff --git a/tests/EndToEnd/forms/blocks-shortcodes/PageBlockFormCest.php b/tests/EndToEnd/forms/blocks-shortcodes/PageBlockFormCest.php index 1940355cb..ef64e1f4f 100644 --- a/tests/EndToEnd/forms/blocks-shortcodes/PageBlockFormCest.php +++ b/tests/EndToEnd/forms/blocks-shortcodes/PageBlockFormCest.php @@ -1214,7 +1214,16 @@ public function testFormBlockWithThemeColorParameters(EndToEndTester $I) $I->seeFormOutput($I, $_ENV['CONVERTKIT_API_FORM_ID']); // Confirm that the chosen colors are applied as CSS styles. - $I->seeInSource('
seeInSource('style="background-color:' . $backgroundColor . '"'); + $I->seeElementHasClasses( + $I, + '.convertkit-form', + [ + 'convertkit-form', + 'wp-block-convertkit-form', + 'has-background', + ] + ); } /** @@ -1265,7 +1274,16 @@ public function testFormBlockWithHexColorParameters(EndToEndTester $I) $I->seeFormOutput($I, $_ENV['CONVERTKIT_API_FORM_ID']); // Confirm that the chosen colors are applied as CSS styles. - $I->seeInSource('
seeInSource('style="background-color:' . $backgroundColor . '"'); + $I->seeElementHasClasses( + $I, + '.convertkit-form', + [ + 'convertkit-form', + 'wp-block-convertkit-form', + 'has-background', + ] + ); } /** @@ -1315,7 +1333,15 @@ public function testFormBlockWithMarginAndPaddingParameters(EndToEndTester $I) $I->seeFormOutput($I, $_ENV['CONVERTKIT_API_FORM_ID']); // Confirm that the chosen margin and padding are applied as CSS styles. - $I->seeInSource('
seeInSource('style="padding-top:var(--wp--preset--spacing--30);margin-top:var(--wp--preset--spacing--30)"'); + $I->seeElementHasClasses( + $I, + '.convertkit-form', + [ + 'convertkit-form', + 'wp-block-convertkit-form', + ] + ); } /** @@ -1365,7 +1391,15 @@ public function testFormBlockWithAlignmentParameter(EndToEndTester $I) $I->seeFormOutput($I, $_ENV['CONVERTKIT_API_FORM_ID']); // Confirm that the chosen alignment is applied as a CSS class. - $I->seeInSource('
seeElementHasClasses( + $I, + '.convertkit-form', + [ + 'convertkit-form', + 'wp-block-convertkit-form', + 'alignright', + ] + ); } /** diff --git a/tests/EndToEnd/forms/blocks-shortcodes/PageBlockFormTriggerCest.php b/tests/EndToEnd/forms/blocks-shortcodes/PageBlockFormTriggerCest.php index d6f7b02e2..bb51a118f 100644 --- a/tests/EndToEnd/forms/blocks-shortcodes/PageBlockFormTriggerCest.php +++ b/tests/EndToEnd/forms/blocks-shortcodes/PageBlockFormTriggerCest.php @@ -311,7 +311,20 @@ public function testFormTriggerBlockWithThemeColorParameters(EndToEndTester $I) ); // Confirm that the chosen colors are applied as CSS styles. - $I->seeInSource('class="convertkit-formtrigger wp-block-button__link wp-element-button wp-block-convertkit-formtrigger has-text-color has-' . $textColor . '-color has-background has-' . $backgroundColor . '-background-color'); + $I->seeElementHasClasses( + $I, + 'a.convertkit-formtrigger', + [ + 'convertkit-formtrigger', + 'wp-block-button__link', + 'wp-element-button', + 'wp-block-convertkit-formtrigger', + 'has-text-color', + 'has-' . $textColor . '-color', + 'has-background', + 'has-' . $backgroundColor . '-background-color', + ] + ); } /** @@ -352,16 +365,17 @@ public function testFormTriggerBlockWithHexColorParameters(EndToEndTester $I) $I->checkNoWarningsAndNoticesOnScreen($I); // Confirm that the chosen colors are applied as CSS styles. - $I->seeInSource('class="convertkit-formtrigger wp-block-button__link wp-element-button wp-block-convertkit-formtrigger has-text-color has-background"'); - - // Confirm that the block displays. - $I->seeFormTriggerOutput( + $I->seeElementHasClasses( $I, - formURL: $_ENV['CONVERTKIT_API_FORM_FORMAT_MODAL_URL'], - text: 'Subscribe', - textColor: $textColor, - backgroundColor: $backgroundColor, - isBlock: true + 'a.convertkit-formtrigger', + [ + 'convertkit-formtrigger', + 'wp-block-button__link', + 'wp-element-button', + 'wp-block-convertkit-formtrigger', + 'has-text-color', + 'has-background', + ] ); } diff --git a/tests/EndToEnd/products/PageBlockProductCest.php b/tests/EndToEnd/products/PageBlockProductCest.php index a3b901a85..6653ae5a0 100644 --- a/tests/EndToEnd/products/PageBlockProductCest.php +++ b/tests/EndToEnd/products/PageBlockProductCest.php @@ -486,7 +486,20 @@ public function testProductBlockWithThemeColorParameters(EndToEndTester $I) ); // Confirm that the chosen colors are applied as CSS styles. - $I->seeInSource('class="convertkit-product wp-block-button__link wp-element-button wp-block-convertkit-product has-text-color has-' . $textColor . '-color has-background has-' . $backgroundColor . '-background-color'); + $I->seeElementHasClasses( + $I, + 'a.convertkit-product', + [ + 'convertkit-product', + 'wp-block-button__link', + 'wp-element-button', + 'wp-block-convertkit-product', + 'has-text-color', + 'has-' . $textColor . '-color', + 'has-background', + 'has-' . $backgroundColor . '-background-color', + ] + ); } /** diff --git a/tests/EndToEnd/restrict-content/post-types/RestrictContentProductThirdPartyThemeOrPageBuilderCest.php b/tests/EndToEnd/restrict-content/post-types/RestrictContentProductThirdPartyThemeOrPageBuilderCest.php index e53916605..82d82d808 100644 --- a/tests/EndToEnd/restrict-content/post-types/RestrictContentProductThirdPartyThemeOrPageBuilderCest.php +++ b/tests/EndToEnd/restrict-content/post-types/RestrictContentProductThirdPartyThemeOrPageBuilderCest.php @@ -33,14 +33,14 @@ public function _before(EndToEndTester $I) /** * Test that restricting content by a Product specified in the Page Settings works when - * creating and viewing a new WordPress Page using the Uncode theme with - * the Visual Composer Page Builder. + * creating and viewing a new WordPress Page using the Uncode theme both using + * the Visual Composer Page Builder and not using it. * * @since 2.7.7 * * @param EndToEndTester $I Tester. */ - public function testRestrictContentByProductWithUncodeThemeAndVisualComposer(EndToEndTester $I) + public function testRestrictContentByProductWithUncodeTheme(EndToEndTester $I) { // Activate theme and third party Plugins. $I->useTheme('uncode'); @@ -81,28 +81,7 @@ public function testRestrictContentByProductWithUncodeThemeAndVisualComposer(End checkNoWarningsAndNotices: false ); - // Deactivate theme and third party Plugins. - $I->deactivateThirdPartyPlugin($I, 'uncode-wpbakery-page-builder'); - $I->deactivateThirdPartyPlugin($I, 'uncode-core'); - $I->useTheme('twentytwentyfive'); - } - - /** - * Test that restricting content by a Product specified in the Page Settings works when - * creating and viewing a new WordPress Page using the Uncode theme without - * the Visual Composer Page Builder. - * - * @since 2.7.7 - * - * @param EndToEndTester $I Tester. - */ - public function testRestrictContentByProductWithUncodeTheme(EndToEndTester $I) - { - // Activate theme and third party Plugins. - $I->useTheme('uncode'); - $I->activateThirdPartyPlugin($I, 'uncode-core'); - - // Programmatically create a Page using the Visual Composer Page Builder. + // Programmatically create a Page without using the Visual Composer Page Builder. $pageID = $I->havePostInDatabase( [ 'post_type' => 'page', @@ -134,10 +113,6 @@ public function testRestrictContentByProductWithUncodeTheme(EndToEndTester $I) // Don't check for warnings and notices, as Uncode uses deprecated functions which WordPress 6.9 warn about. checkNoWarningsAndNotices: false ); - - // Deactivate theme and third party Plugins. - $I->deactivateThirdPartyPlugin($I, 'uncode-core'); - $I->useTheme('twentytwentyfive'); } /** diff --git a/tests/Support/Data/dump-6.2.8.sql b/tests/Support/Data/dump-6.2.8.sql new file mode 100644 index 000000000..c7162a88b --- /dev/null +++ b/tests/Support/Data/dump-6.2.8.sql @@ -0,0 +1,360 @@ +-- Adminer 4.8.1 MySQL 8.0.16 dump + +SET NAMES utf8; +SET time_zone = '+00:00'; +SET foreign_key_checks = 0; +SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; + +SET NAMES utf8mb4; + +DROP TABLE IF EXISTS `wp_commentmeta`; +CREATE TABLE `wp_commentmeta` ( + `meta_id` bigint unsigned NOT NULL AUTO_INCREMENT, + `comment_id` bigint unsigned NOT NULL DEFAULT '0', + `meta_key` varchar(255) COLLATE utf8mb4_unicode_520_ci DEFAULT NULL, + `meta_value` longtext COLLATE utf8mb4_unicode_520_ci, + PRIMARY KEY (`meta_id`), + KEY `comment_id` (`comment_id`), + KEY `meta_key` (`meta_key`(191)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + + +DROP TABLE IF EXISTS `wp_comments`; +CREATE TABLE `wp_comments` ( + `comment_ID` bigint unsigned NOT NULL AUTO_INCREMENT, + `comment_post_ID` bigint unsigned NOT NULL DEFAULT '0', + `comment_author` tinytext COLLATE utf8mb4_unicode_520_ci NOT NULL, + `comment_author_email` varchar(100) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `comment_author_url` varchar(200) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `comment_author_IP` varchar(100) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `comment_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `comment_date_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `comment_content` text COLLATE utf8mb4_unicode_520_ci NOT NULL, + `comment_karma` int NOT NULL DEFAULT '0', + `comment_approved` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '1', + `comment_agent` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `comment_type` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'comment', + `comment_parent` bigint unsigned NOT NULL DEFAULT '0', + `user_id` bigint unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`comment_ID`), + KEY `comment_post_ID` (`comment_post_ID`), + KEY `comment_approved_date_gmt` (`comment_approved`,`comment_date_gmt`), + KEY `comment_date_gmt` (`comment_date_gmt`), + KEY `comment_parent` (`comment_parent`), + KEY `comment_author_email` (`comment_author_email`(10)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +INSERT INTO `wp_comments` (`comment_ID`, `comment_post_ID`, `comment_author`, `comment_author_email`, `comment_author_url`, `comment_author_IP`, `comment_date`, `comment_date_gmt`, `comment_content`, `comment_karma`, `comment_approved`, `comment_agent`, `comment_type`, `comment_parent`, `user_id`) VALUES +(1, 1, 'A WordPress Commenter', 'wapuu@wordpress.example', 'https://wordpress.org/', '', '2026-01-06 08:19:29', '2026-01-06 08:19:29', 'Hi, this is a comment.\nTo get started with moderating, editing, and deleting comments, please visit the Comments screen in the dashboard.\nCommenter avatars come from Gravatar.', 0, '1', '', 'comment', 0, 0); + +DROP TABLE IF EXISTS `wp_links`; +CREATE TABLE `wp_links` ( + `link_id` bigint unsigned NOT NULL AUTO_INCREMENT, + `link_url` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `link_name` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `link_image` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `link_target` varchar(25) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `link_description` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `link_visible` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'Y', + `link_owner` bigint unsigned NOT NULL DEFAULT '1', + `link_rating` int NOT NULL DEFAULT '0', + `link_updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `link_rel` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `link_notes` mediumtext COLLATE utf8mb4_unicode_520_ci NOT NULL, + `link_rss` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + PRIMARY KEY (`link_id`), + KEY `link_visible` (`link_visible`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + + +DROP TABLE IF EXISTS `wp_options`; +CREATE TABLE `wp_options` ( + `option_id` bigint unsigned NOT NULL AUTO_INCREMENT, + `option_name` varchar(191) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `option_value` longtext COLLATE utf8mb4_unicode_520_ci NOT NULL, + `autoload` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'yes', + PRIMARY KEY (`option_id`), + UNIQUE KEY `option_name` (`option_name`), + KEY `autoload` (`autoload`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +INSERT INTO `wp_options` (`option_id`, `option_name`, `option_value`, `autoload`) VALUES +(1, 'siteurl', 'http://kit.local', 'yes'), +(2, 'home', 'http://kit.local', 'yes'), +(3, 'blogname', 'kit', 'yes'), +(4, 'blogdescription', '', 'yes'), +(5, 'users_can_register', '0', 'yes'), +(6, 'admin_email', 'dev-email@flywheel.local', 'yes'), +(7, 'start_of_week', '1', 'yes'), +(8, 'use_balanceTags', '0', 'yes'), +(9, 'use_smilies', '1', 'yes'), +(10, 'require_name_email', '1', 'yes'), +(11, 'comments_notify', '1', 'yes'), +(12, 'posts_per_rss', '10', 'yes'), +(13, 'rss_use_excerpt', '0', 'yes'), +(14, 'mailserver_url', 'mail.example.com', 'yes'), +(15, 'mailserver_login', 'login@example.com', 'yes'), +(16, 'mailserver_pass', 'password', 'yes'), +(17, 'mailserver_port', '110', 'yes'), +(18, 'default_category', '1', 'yes'), +(19, 'default_comment_status', 'open', 'yes'), +(20, 'default_ping_status', 'open', 'yes'), +(21, 'default_pingback_flag', '0', 'yes'), +(22, 'posts_per_page', '10', 'yes'), +(23, 'date_format', 'F j, Y', 'yes'), +(24, 'time_format', 'g:i a', 'yes'), +(25, 'links_updated_date_format', 'F j, Y g:i a', 'yes'), +(26, 'comment_moderation', '0', 'yes'), +(27, 'moderation_notify', '1', 'yes'), +(28, 'permalink_structure', '/%year%/%monthnum%/%day%/%postname%/', 'yes'), +(29, 'rewrite_rules', 'a:81:{s:11:\"^wp-json/?$\";s:22:\"index.php?rest_route=/\";s:14:\"^wp-json/(.*)?\";s:33:\"index.php?rest_route=/$matches[1]\";s:21:\"^index.php/wp-json/?$\";s:22:\"index.php?rest_route=/\";s:24:\"^index.php/wp-json/(.*)?\";s:33:\"index.php?rest_route=/$matches[1]\";s:17:\"^wp-sitemap\\.xml$\";s:23:\"index.php?sitemap=index\";s:17:\"^wp-sitemap\\.xsl$\";s:36:\"index.php?sitemap-stylesheet=sitemap\";s:23:\"^wp-sitemap-index\\.xsl$\";s:34:\"index.php?sitemap-stylesheet=index\";s:48:\"^wp-sitemap-([a-z]+?)-([a-z\\d_-]+?)-(\\d+?)\\.xml$\";s:75:\"index.php?sitemap=$matches[1]&sitemap-subtype=$matches[2]&paged=$matches[3]\";s:34:\"^wp-sitemap-([a-z]+?)-(\\d+?)\\.xml$\";s:47:\"index.php?sitemap=$matches[1]&paged=$matches[2]\";s:12:\"robots\\.txt$\";s:18:\"index.php?robots=1\";s:13:\"favicon\\.ico$\";s:19:\"index.php?favicon=1\";s:48:\".*wp-(atom|rdf|rss|rss2|feed|commentsrss2)\\.php$\";s:18:\"index.php?feed=old\";s:20:\".*wp-app\\.php(/.*)?$\";s:19:\"index.php?error=403\";s:18:\".*wp-register.php$\";s:23:\"index.php?register=true\";s:32:\"feed/(feed|rdf|rss|rss2|atom)/?$\";s:27:\"index.php?&feed=$matches[1]\";s:27:\"(feed|rdf|rss|rss2|atom)/?$\";s:27:\"index.php?&feed=$matches[1]\";s:8:\"embed/?$\";s:21:\"index.php?&embed=true\";s:20:\"page/?([0-9]{1,})/?$\";s:28:\"index.php?&paged=$matches[1]\";s:41:\"comments/feed/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?&feed=$matches[1]&withcomments=1\";s:36:\"comments/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?&feed=$matches[1]&withcomments=1\";s:17:\"comments/embed/?$\";s:21:\"index.php?&embed=true\";s:44:\"search/(.+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:40:\"index.php?s=$matches[1]&feed=$matches[2]\";s:39:\"search/(.+)/(feed|rdf|rss|rss2|atom)/?$\";s:40:\"index.php?s=$matches[1]&feed=$matches[2]\";s:20:\"search/(.+)/embed/?$\";s:34:\"index.php?s=$matches[1]&embed=true\";s:32:\"search/(.+)/page/?([0-9]{1,})/?$\";s:41:\"index.php?s=$matches[1]&paged=$matches[2]\";s:14:\"search/(.+)/?$\";s:23:\"index.php?s=$matches[1]\";s:47:\"author/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?author_name=$matches[1]&feed=$matches[2]\";s:42:\"author/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?author_name=$matches[1]&feed=$matches[2]\";s:23:\"author/([^/]+)/embed/?$\";s:44:\"index.php?author_name=$matches[1]&embed=true\";s:35:\"author/([^/]+)/page/?([0-9]{1,})/?$\";s:51:\"index.php?author_name=$matches[1]&paged=$matches[2]\";s:17:\"author/([^/]+)/?$\";s:33:\"index.php?author_name=$matches[1]\";s:69:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:80:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]\";s:64:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$\";s:80:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]\";s:45:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/embed/?$\";s:74:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&embed=true\";s:57:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$\";s:81:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4]\";s:39:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$\";s:63:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]\";s:56:\"([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:64:\"index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]\";s:51:\"([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$\";s:64:\"index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]\";s:32:\"([0-9]{4})/([0-9]{1,2})/embed/?$\";s:58:\"index.php?year=$matches[1]&monthnum=$matches[2]&embed=true\";s:44:\"([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$\";s:65:\"index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3]\";s:26:\"([0-9]{4})/([0-9]{1,2})/?$\";s:47:\"index.php?year=$matches[1]&monthnum=$matches[2]\";s:43:\"([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?year=$matches[1]&feed=$matches[2]\";s:38:\"([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?year=$matches[1]&feed=$matches[2]\";s:19:\"([0-9]{4})/embed/?$\";s:37:\"index.php?year=$matches[1]&embed=true\";s:31:\"([0-9]{4})/page/?([0-9]{1,})/?$\";s:44:\"index.php?year=$matches[1]&paged=$matches[2]\";s:13:\"([0-9]{4})/?$\";s:26:\"index.php?year=$matches[1]\";s:58:\"[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:68:\"[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:88:\"[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:83:\"[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:83:\"[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:64:\"[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/attachment/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:53:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/embed/?$\";s:91:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&embed=true\";s:57:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/trackback/?$\";s:85:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&tb=1\";s:77:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:97:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&feed=$matches[5]\";s:72:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:97:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&feed=$matches[5]\";s:65:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/page/?([0-9]{1,})/?$\";s:98:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&paged=$matches[5]\";s:72:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)/comment-page-([0-9]{1,})/?$\";s:98:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&cpage=$matches[5]\";s:61:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/([^/]+)(?:/([0-9]+))?/?$\";s:97:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&name=$matches[4]&page=$matches[5]\";s:47:\"[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:57:\"[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:77:\"[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:72:\"[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:72:\"[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:53:\"[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/[^/]+/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:64:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/comment-page-([0-9]{1,})/?$\";s:81:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&cpage=$matches[4]\";s:51:\"([0-9]{4})/([0-9]{1,2})/comment-page-([0-9]{1,})/?$\";s:65:\"index.php?year=$matches[1]&monthnum=$matches[2]&cpage=$matches[3]\";s:38:\"([0-9]{4})/comment-page-([0-9]{1,})/?$\";s:44:\"index.php?year=$matches[1]&cpage=$matches[2]\";s:27:\".?.+?/attachment/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:37:\".?.+?/attachment/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:57:\".?.+?/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\".?.+?/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\".?.+?/attachment/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:33:\".?.+?/attachment/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:16:\"(.?.+?)/embed/?$\";s:41:\"index.php?pagename=$matches[1]&embed=true\";s:20:\"(.?.+?)/trackback/?$\";s:35:\"index.php?pagename=$matches[1]&tb=1\";s:40:\"(.?.+?)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:47:\"index.php?pagename=$matches[1]&feed=$matches[2]\";s:35:\"(.?.+?)/(feed|rdf|rss|rss2|atom)/?$\";s:47:\"index.php?pagename=$matches[1]&feed=$matches[2]\";s:28:\"(.?.+?)/page/?([0-9]{1,})/?$\";s:48:\"index.php?pagename=$matches[1]&paged=$matches[2]\";s:35:\"(.?.+?)/comment-page-([0-9]{1,})/?$\";s:48:\"index.php?pagename=$matches[1]&cpage=$matches[2]\";s:24:\"(.?.+?)(?:/([0-9]+))?/?$\";s:47:\"index.php?pagename=$matches[1]&page=$matches[2]\";}', 'yes'), +(30, 'hack_file', '0', 'yes'), +(31, 'blog_charset', 'UTF-8', 'yes'), +(32, 'moderation_keys', '', 'no'), +(33, 'active_plugins', 'a:0:{}', 'yes'), +(34, 'category_base', '', 'yes'), +(35, 'ping_sites', 'http://rpc.pingomatic.com/', 'yes'), +(36, 'comment_max_links', '2', 'yes'), +(37, 'gmt_offset', '0', 'yes'), +(38, 'default_email_category', '1', 'yes'), +(39, 'recently_edited', '', 'no'), +(40, 'template', 'twentytwentythree', 'yes'), +(41, 'stylesheet', 'twentytwentythree', 'yes'), +(42, 'comment_registration', '0', 'yes'), +(43, 'html_type', 'text/html', 'yes'), +(44, 'use_trackback', '0', 'yes'), +(45, 'default_role', 'subscriber', 'yes'), +(46, 'db_version', '53496', 'yes'), +(47, 'uploads_use_yearmonth_folders', '1', 'yes'), +(48, 'upload_path', '', 'yes'), +(49, 'blog_public', '0', 'yes'), +(50, 'default_link_category', '2', 'yes'), +(51, 'show_on_front', 'posts', 'yes'), +(52, 'tag_base', '', 'yes'), +(53, 'show_avatars', '1', 'yes'), +(54, 'avatar_rating', 'G', 'yes'), +(55, 'upload_url_path', '', 'yes'), +(56, 'thumbnail_size_w', '150', 'yes'), +(57, 'thumbnail_size_h', '150', 'yes'), +(58, 'thumbnail_crop', '1', 'yes'), +(59, 'medium_size_w', '300', 'yes'), +(60, 'medium_size_h', '300', 'yes'), +(61, 'avatar_default', 'mystery', 'yes'), +(62, 'large_size_w', '1024', 'yes'), +(63, 'large_size_h', '1024', 'yes'), +(64, 'image_default_link_type', 'none', 'yes'), +(65, 'image_default_size', '', 'yes'), +(66, 'image_default_align', '', 'yes'), +(67, 'close_comments_for_old_posts', '0', 'yes'), +(68, 'close_comments_days_old', '14', 'yes'), +(69, 'thread_comments', '1', 'yes'), +(70, 'thread_comments_depth', '5', 'yes'), +(71, 'page_comments', '0', 'yes'), +(72, 'comments_per_page', '50', 'yes'), +(73, 'default_comments_page', 'newest', 'yes'), +(74, 'comment_order', 'asc', 'yes'), +(75, 'sticky_posts', 'a:0:{}', 'yes'), +(76, 'widget_categories', 'a:0:{}', 'yes'), +(77, 'widget_text', 'a:0:{}', 'yes'), +(78, 'widget_rss', 'a:0:{}', 'yes'), +(79, 'uninstall_plugins', 'a:0:{}', 'no'), +(80, 'timezone_string', '', 'yes'), +(81, 'page_for_posts', '0', 'yes'), +(82, 'page_on_front', '0', 'yes'), +(83, 'default_post_format', '0', 'yes'), +(84, 'link_manager_enabled', '0', 'yes'), +(85, 'finished_splitting_shared_terms', '1', 'yes'), +(86, 'site_icon', '0', 'yes'), +(87, 'medium_large_size_w', '768', 'yes'), +(88, 'medium_large_size_h', '0', 'yes'), +(89, 'wp_page_for_privacy_policy', '3', 'yes'), +(90, 'show_comments_cookies_opt_in', '1', 'yes'), +(91, 'admin_email_lifespan', '1783239569', 'yes'), +(92, 'disallowed_keys', '', 'no'), +(93, 'comment_previously_approved', '1', 'yes'), +(94, 'auto_plugin_theme_update_emails', 'a:0:{}', 'no'), +(95, 'auto_update_core_dev', 'enabled', 'yes'), +(96, 'auto_update_core_minor', 'enabled', 'yes'), +(97, 'auto_update_core_major', 'enabled', 'yes'), +(98, 'wp_force_deactivated_plugins', 'a:0:{}', 'yes'), +(99, 'initial_db_version', '53496', 'yes'), +(100, 'wp_user_roles', 'a:5:{s:13:\"administrator\";a:2:{s:4:\"name\";s:13:\"Administrator\";s:12:\"capabilities\";a:61:{s:13:\"switch_themes\";b:1;s:11:\"edit_themes\";b:1;s:16:\"activate_plugins\";b:1;s:12:\"edit_plugins\";b:1;s:10:\"edit_users\";b:1;s:10:\"edit_files\";b:1;s:14:\"manage_options\";b:1;s:17:\"moderate_comments\";b:1;s:17:\"manage_categories\";b:1;s:12:\"manage_links\";b:1;s:12:\"upload_files\";b:1;s:6:\"import\";b:1;s:15:\"unfiltered_html\";b:1;s:10:\"edit_posts\";b:1;s:17:\"edit_others_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:10:\"edit_pages\";b:1;s:4:\"read\";b:1;s:8:\"level_10\";b:1;s:7:\"level_9\";b:1;s:7:\"level_8\";b:1;s:7:\"level_7\";b:1;s:7:\"level_6\";b:1;s:7:\"level_5\";b:1;s:7:\"level_4\";b:1;s:7:\"level_3\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:17:\"edit_others_pages\";b:1;s:20:\"edit_published_pages\";b:1;s:13:\"publish_pages\";b:1;s:12:\"delete_pages\";b:1;s:19:\"delete_others_pages\";b:1;s:22:\"delete_published_pages\";b:1;s:12:\"delete_posts\";b:1;s:19:\"delete_others_posts\";b:1;s:22:\"delete_published_posts\";b:1;s:20:\"delete_private_posts\";b:1;s:18:\"edit_private_posts\";b:1;s:18:\"read_private_posts\";b:1;s:20:\"delete_private_pages\";b:1;s:18:\"edit_private_pages\";b:1;s:18:\"read_private_pages\";b:1;s:12:\"delete_users\";b:1;s:12:\"create_users\";b:1;s:17:\"unfiltered_upload\";b:1;s:14:\"edit_dashboard\";b:1;s:14:\"update_plugins\";b:1;s:14:\"delete_plugins\";b:1;s:15:\"install_plugins\";b:1;s:13:\"update_themes\";b:1;s:14:\"install_themes\";b:1;s:11:\"update_core\";b:1;s:10:\"list_users\";b:1;s:12:\"remove_users\";b:1;s:13:\"promote_users\";b:1;s:18:\"edit_theme_options\";b:1;s:13:\"delete_themes\";b:1;s:6:\"export\";b:1;}}s:6:\"editor\";a:2:{s:4:\"name\";s:6:\"Editor\";s:12:\"capabilities\";a:34:{s:17:\"moderate_comments\";b:1;s:17:\"manage_categories\";b:1;s:12:\"manage_links\";b:1;s:12:\"upload_files\";b:1;s:15:\"unfiltered_html\";b:1;s:10:\"edit_posts\";b:1;s:17:\"edit_others_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:10:\"edit_pages\";b:1;s:4:\"read\";b:1;s:7:\"level_7\";b:1;s:7:\"level_6\";b:1;s:7:\"level_5\";b:1;s:7:\"level_4\";b:1;s:7:\"level_3\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:17:\"edit_others_pages\";b:1;s:20:\"edit_published_pages\";b:1;s:13:\"publish_pages\";b:1;s:12:\"delete_pages\";b:1;s:19:\"delete_others_pages\";b:1;s:22:\"delete_published_pages\";b:1;s:12:\"delete_posts\";b:1;s:19:\"delete_others_posts\";b:1;s:22:\"delete_published_posts\";b:1;s:20:\"delete_private_posts\";b:1;s:18:\"edit_private_posts\";b:1;s:18:\"read_private_posts\";b:1;s:20:\"delete_private_pages\";b:1;s:18:\"edit_private_pages\";b:1;s:18:\"read_private_pages\";b:1;}}s:6:\"author\";a:2:{s:4:\"name\";s:6:\"Author\";s:12:\"capabilities\";a:10:{s:12:\"upload_files\";b:1;s:10:\"edit_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:4:\"read\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:12:\"delete_posts\";b:1;s:22:\"delete_published_posts\";b:1;}}s:11:\"contributor\";a:2:{s:4:\"name\";s:11:\"Contributor\";s:12:\"capabilities\";a:5:{s:10:\"edit_posts\";b:1;s:4:\"read\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:12:\"delete_posts\";b:1;}}s:10:\"subscriber\";a:2:{s:4:\"name\";s:10:\"Subscriber\";s:12:\"capabilities\";a:2:{s:4:\"read\";b:1;s:7:\"level_0\";b:1;}}}', 'yes'), +(101, 'fresh_site', '1', 'yes'), +(102, 'user_count', '1', 'no'), +(103, 'widget_block', 'a:6:{i:2;a:1:{s:7:\"content\";s:19:\"\";}i:3;a:1:{s:7:\"content\";s:154:\"

Recent Posts

\";}i:4;a:1:{s:7:\"content\";s:227:\"

Recent Comments

\";}i:5;a:1:{s:7:\"content\";s:146:\"

Archives

\";}i:6;a:1:{s:7:\"content\";s:150:\"

Categories

\";}s:12:\"_multiwidget\";i:1;}', 'yes'), +(104, 'sidebars_widgets', 'a:4:{s:19:\"wp_inactive_widgets\";a:0:{}s:9:\"sidebar-1\";a:3:{i:0;s:7:\"block-2\";i:1;s:7:\"block-3\";i:2;s:7:\"block-4\";}s:9:\"sidebar-2\";a:2:{i:0;s:7:\"block-5\";i:1;s:7:\"block-6\";}s:13:\"array_version\";i:3;}', 'yes'), +(105, 'cron', 'a:8:{i:1767687569;a:2:{s:17:\"wp_update_plugins\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:16:\"wp_update_themes\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}}i:1767687580;a:3:{s:19:\"wp_scheduled_delete\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}s:25:\"delete_expired_transients\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}s:21:\"wp_update_user_counts\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}}i:1767687585;a:1:{s:30:\"wp_scheduled_auto_draft_delete\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}i:1767687640;a:1:{s:28:\"wp_update_comment_type_batch\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:2:{s:8:\"schedule\";b:0;s:4:\"args\";a:0:{}}}}i:1767691169;a:1:{s:34:\"wp_privacy_delete_old_export_files\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"hourly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:3600;}}}i:1767730769;a:2:{s:18:\"wp_https_detection\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:16:\"wp_version_check\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}}i:1767773969;a:2:{s:30:\"wp_site_health_scheduled_check\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"weekly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:604800;}}s:32:\"recovery_mode_clean_expired_keys\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}s:7:\"version\";i:2;}', 'yes'), +(106, 'widget_pages', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(107, 'widget_calendar', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(108, 'widget_archives', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(109, 'widget_media_audio', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(110, 'widget_media_image', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(111, 'widget_media_gallery', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(112, 'widget_media_video', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(113, 'widget_meta', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(114, 'widget_search', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(115, 'widget_recent-posts', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(116, 'widget_recent-comments', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(117, 'widget_tag_cloud', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(118, 'widget_nav_menu', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(119, 'widget_custom_html', 'a:1:{s:12:\"_multiwidget\";i:1;}', 'yes'), +(120, '_transient_doing_cron', '1767687569.2989718914031982421875', 'yes'), +(121, 'theme_mods_twentytwentythree', 'a:1:{s:18:\"custom_css_post_id\";i:-1;}', 'yes'), +(122, 'recovery_keys', 'a:0:{}', 'yes'), +(123, 'https_detection_errors', 'a:1:{s:23:\"ssl_verification_failed\";a:1:{i:0;s:24:\"SSL verification failed.\";}}', 'yes'), +(124, 'can_compress_scripts', '0', 'no'); + +DROP TABLE IF EXISTS `wp_postmeta`; +CREATE TABLE `wp_postmeta` ( + `meta_id` bigint unsigned NOT NULL AUTO_INCREMENT, + `post_id` bigint unsigned NOT NULL DEFAULT '0', + `meta_key` varchar(255) COLLATE utf8mb4_unicode_520_ci DEFAULT NULL, + `meta_value` longtext COLLATE utf8mb4_unicode_520_ci, + PRIMARY KEY (`meta_id`), + KEY `post_id` (`post_id`), + KEY `meta_key` (`meta_key`(191)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +DROP TABLE IF EXISTS `wp_posts`; +CREATE TABLE `wp_posts` ( + `ID` bigint unsigned NOT NULL AUTO_INCREMENT, + `post_author` bigint unsigned NOT NULL DEFAULT '0', + `post_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `post_date_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `post_content` longtext COLLATE utf8mb4_unicode_520_ci NOT NULL, + `post_title` text COLLATE utf8mb4_unicode_520_ci NOT NULL, + `post_excerpt` text COLLATE utf8mb4_unicode_520_ci NOT NULL, + `post_status` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'publish', + `comment_status` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'open', + `ping_status` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'open', + `post_password` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `post_name` varchar(200) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `to_ping` text COLLATE utf8mb4_unicode_520_ci NOT NULL, + `pinged` text COLLATE utf8mb4_unicode_520_ci NOT NULL, + `post_modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `post_modified_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `post_content_filtered` longtext COLLATE utf8mb4_unicode_520_ci NOT NULL, + `post_parent` bigint unsigned NOT NULL DEFAULT '0', + `guid` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `menu_order` int NOT NULL DEFAULT '0', + `post_type` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'post', + `post_mime_type` varchar(100) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `comment_count` bigint NOT NULL DEFAULT '0', + PRIMARY KEY (`ID`), + KEY `post_name` (`post_name`(191)), + KEY `type_status_date` (`post_type`,`post_status`,`post_date`,`ID`), + KEY `post_parent` (`post_parent`), + KEY `post_author` (`post_author`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +DROP TABLE IF EXISTS `wp_term_relationships`; +CREATE TABLE `wp_term_relationships` ( + `object_id` bigint unsigned NOT NULL DEFAULT '0', + `term_taxonomy_id` bigint unsigned NOT NULL DEFAULT '0', + `term_order` int NOT NULL DEFAULT '0', + PRIMARY KEY (`object_id`,`term_taxonomy_id`), + KEY `term_taxonomy_id` (`term_taxonomy_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +INSERT INTO `wp_term_relationships` (`object_id`, `term_taxonomy_id`, `term_order`) VALUES +(1, 1, 0); + +DROP TABLE IF EXISTS `wp_term_taxonomy`; +CREATE TABLE `wp_term_taxonomy` ( + `term_taxonomy_id` bigint unsigned NOT NULL AUTO_INCREMENT, + `term_id` bigint unsigned NOT NULL DEFAULT '0', + `taxonomy` varchar(32) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `description` longtext COLLATE utf8mb4_unicode_520_ci NOT NULL, + `parent` bigint unsigned NOT NULL DEFAULT '0', + `count` bigint NOT NULL DEFAULT '0', + PRIMARY KEY (`term_taxonomy_id`), + UNIQUE KEY `term_id_taxonomy` (`term_id`,`taxonomy`), + KEY `taxonomy` (`taxonomy`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +INSERT INTO `wp_term_taxonomy` (`term_taxonomy_id`, `term_id`, `taxonomy`, `description`, `parent`, `count`) VALUES +(1, 1, 'category', '', 0, 1); + +DROP TABLE IF EXISTS `wp_termmeta`; +CREATE TABLE `wp_termmeta` ( + `meta_id` bigint unsigned NOT NULL AUTO_INCREMENT, + `term_id` bigint unsigned NOT NULL DEFAULT '0', + `meta_key` varchar(255) COLLATE utf8mb4_unicode_520_ci DEFAULT NULL, + `meta_value` longtext COLLATE utf8mb4_unicode_520_ci, + PRIMARY KEY (`meta_id`), + KEY `term_id` (`term_id`), + KEY `meta_key` (`meta_key`(191)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + + +DROP TABLE IF EXISTS `wp_terms`; +CREATE TABLE `wp_terms` ( + `term_id` bigint unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(200) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `slug` varchar(200) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `term_group` bigint NOT NULL DEFAULT '0', + PRIMARY KEY (`term_id`), + KEY `slug` (`slug`(191)), + KEY `name` (`name`(191)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +INSERT INTO `wp_terms` (`term_id`, `name`, `slug`, `term_group`) VALUES +(1, 'Uncategorized', 'uncategorized', 0); + +DROP TABLE IF EXISTS `wp_usermeta`; +CREATE TABLE `wp_usermeta` ( + `umeta_id` bigint unsigned NOT NULL AUTO_INCREMENT, + `user_id` bigint unsigned NOT NULL DEFAULT '0', + `meta_key` varchar(255) COLLATE utf8mb4_unicode_520_ci DEFAULT NULL, + `meta_value` longtext COLLATE utf8mb4_unicode_520_ci, + PRIMARY KEY (`umeta_id`), + KEY `user_id` (`user_id`), + KEY `meta_key` (`meta_key`(191)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +INSERT INTO `wp_usermeta` (`umeta_id`, `user_id`, `meta_key`, `meta_value`) VALUES +(1, 1, 'nickname', 'admin'), +(2, 1, 'first_name', ''), +(3, 1, 'last_name', ''), +(4, 1, 'description', ''), +(5, 1, 'rich_editing', 'true'), +(6, 1, 'syntax_highlighting', 'true'), +(7, 1, 'comment_shortcuts', 'false'), +(8, 1, 'admin_color', 'fresh'), +(9, 1, 'use_ssl', '0'), +(10, 1, 'show_admin_bar_front', 'true'), +(11, 1, 'locale', ''), +(12, 1, 'wp_capabilities', 'a:1:{s:13:\"administrator\";b:1;}'), +(13, 1, 'wp_user_level', '10'), +(14, 1, 'dismissed_wp_pointers', ''), +(15, 1, 'show_welcome_panel', '1'), +(16, 1, 'session_tokens', 'a:1:{s:64:\"d1edb8c7d17dc41fa6de9833631a6381dca0306f20dfd4b64947e6b8818dd16e\";a:4:{s:10:\"expiration\";i:1676810217;s:2:\"ip\";s:9:\"127.0.0.1\";s:2:\"ua\";s:117:\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36\";s:5:\"login\";i:1676637417;}}'), +(17, 1, 'wp_user-settings', 'unfold=1&ampmfold=o&ampeditor=html&amplibraryContent=browse&ampsiteorigin_panels_setting_tab=widgets&libraryContent=browse&editor=tinymce&libraryContent=browse&editor=tinymce&siteorigin_panels_setting_tab=welcome'), +(18, 1, 'wp_user-settings-time', '1676637417'), +(19, 1, 'wp_dashboard_quick_press_last_post_id', '1'), +(20, 1, 'edit_page_per_page', '100'), +(21, 1, 'edit_post_per_page', '100'), +(22, 1, 'wp_persisted_preferences', 'a:4:{s:4:"core";a:2:{s:26:"isComplementaryAreaVisible";b:1;s:24:"enableChoosePatternModal";b:0;}s:14:"core/edit-post";a:4:{s:12:"welcomeGuide";b:0;s:23:"metaBoxesMainOpenHeight";i:540;s:20:"welcomeGuideTemplate";b:0;s:19:"metaBoxesMainIsOpen";b:1;}s:9:"_modified";s:24:"2024-07-18T02:45:41.491Z";s:17:"core/edit-widgets";a:2:{s:26:"isComplementaryAreaVisible";b:1;s:12:"welcomeGuide";b:0;}}'); + +DROP TABLE IF EXISTS `wp_users`; +CREATE TABLE `wp_users` ( + `ID` bigint unsigned NOT NULL AUTO_INCREMENT, + `user_login` varchar(60) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `user_pass` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `user_nicename` varchar(50) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `user_email` varchar(100) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `user_url` varchar(100) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `user_registered` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `user_activation_key` varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + `user_status` int NOT NULL DEFAULT '0', + `display_name` varchar(250) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT '', + PRIMARY KEY (`ID`), + KEY `user_login_key` (`user_login`), + KEY `user_nicename` (`user_nicename`), + KEY `user_email` (`user_email`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +INSERT INTO `wp_users` (`ID`, `user_login`, `user_pass`, `user_nicename`, `user_email`, `user_url`, `user_registered`, `user_activation_key`, `user_status`, `display_name`) VALUES +(1, 'admin', '$P$BL6V8Lk8jOD6WFowZzH9DLpv9EWFP3.', 'admin', 'dev@flywheel.local', 'http://wordpress-628.local', '2026-01-06 08:19:29', '', 0, 'admin'); + +-- 2026-01-06 08:20:04 UTC diff --git a/tests/Support/Helper/KitPlugin.php b/tests/Support/Helper/KitPlugin.php index d3c00c168..eadee0e93 100644 --- a/tests/Support/Helper/KitPlugin.php +++ b/tests/Support/Helper/KitPlugin.php @@ -801,7 +801,7 @@ public function testBlockNoCredentialsPopupWindow($I, $blockName, $expectedMessa $I->closeTab(); // Wait until the block changes to refreshing. - $I->waitForElementVisible('.' . $blockName . ' div.convertkit-progress-bar', 30); + $I->waitForElementVisible('.' . $blockName . ' .convertkit-block-refreshing', 30); // Wait for the refresh button to disappear, confirming that the block refresh completed // and that resources now exist. @@ -899,6 +899,24 @@ public function seeFormBlockIFrameHasMessage($I, $message) $I->switchToIFrame(); } + /** + * Helper method to assert that the given element has the given CSS classes + * + * @since 3.1.4 + * + * @param EndToEndTester $I EndToEndTester. + * @param string $element Element. + * @param array $classes CSS classes. + */ + public function seeElementHasClasses($I, $element, $classes) + { + $I->seeElement($element); + $element_classes = $I->grabAttributeFrom($element, 'class'); + foreach ($classes as $class) { + $I->assertStringContainsString($class, $element_classes); + } + } + /** * Check that the given Page does output the Creator Network Recommendations * script. diff --git a/tests/Support/Helper/WPGutenberg.php b/tests/Support/Helper/WPGutenberg.php index fd5781988..eedd08381 100644 --- a/tests/Support/Helper/WPGutenberg.php +++ b/tests/Support/Helper/WPGutenberg.php @@ -45,6 +45,24 @@ public function addGutenbergPage($I, $postType = 'page', $title = 'Gutenberg Tit $I->fillField('.editor-post-title__input', $title); } + /** + * Clicks the Add Block Button, supporting both old and new selector names depending on the WordPress version. + * + * @since 3.1.4 + * + * @param EndToEndTester $I EndToEnd Tester. + */ + public function clickAddGutenbergBlockButton($I) + { + if ($I->grabMultiple('button.editor-document-tools__inserter-toggle')) { + $I->click('button.editor-document-tools__inserter-toggle'); + } elseif ($I->grabMultiple('button.edit-post-header-toolbar__inserter-toggle')) { + $I->click('button.edit-post-header-toolbar__inserter-toggle'); + } else { + $I->fail('Add Block button not found for either supported selector.'); + } + } + /** * Add the given block when adding or editing a Page, Post or Custom Post Type * in Gutenberg. @@ -61,7 +79,7 @@ public function addGutenbergPage($I, $postType = 'page', $title = 'Gutenberg Tit public function addGutenbergBlock($I, $blockName, $blockProgrammaticName, $blockConfiguration = false) { // Click Add Block Button. - $I->click('button.editor-document-tools__inserter-toggle'); + $this->clickAddGutenbergBlockButton($I); // When the Blocks sidebar appears, search for the block. $I->waitForElementVisible('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); @@ -72,22 +90,44 @@ public function addGutenbergBlock($I, $blockName, $blockProgrammaticName, $block // If we don't do this, we get stale reference errors when trying to click a block to insert. $I->wait(2); - // Insert the block. + // Build potential programmatic block names. + $potentialNames = [ $blockProgrammaticName ]; if (strpos($blockProgrammaticName, '/') !== false) { - // Use XPath if $blockProgrammaticName contains a forward slash. - $xpath = '//button[contains(@class, "editor-block-list-item-' . $blockProgrammaticName . '") and contains(@class,"block-editor-block-types-list__item") and contains(@class,"components-button")]'; - $I->waitForElementVisible([ 'xpath' => $xpath ]); - $I->seeElementInDOM([ 'xpath' => $xpath ]); - $I->click([ 'xpath' => $xpath ]); - } else { - $selector = '.block-editor-inserter__panel-content button.editor-block-list-item-' . $blockProgrammaticName; - $I->waitForElementVisible($selector); - $I->seeElementInDOM($selector); - $I->click($selector); + // Also add last segment after slash, for compatibility with older WordPress versions. + $parts = explode('/', $blockProgrammaticName); + if (count($parts) === 2) { + $potentialNames[] = $parts[1]; + } + } + + // Confirm the block is available. + foreach ($potentialNames as $name) { + // Check for both XPath (to cover slashes) and regular selector (no slash). + if (strpos($name, '/') !== false) { + $xpath = '//button[contains(@class, "editor-block-list-item-' . $name . '") and contains(@class,"block-editor-block-types-list__item") and contains(@class,"components-button")]'; + try { + $I->waitForElementVisible([ 'xpath' => $xpath ]); + $I->seeElementInDOM([ 'xpath' => $xpath ]); + $I->click([ 'xpath' => $xpath ]); + break; + } catch (\Exception $e) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Not found, try others. + } + } else { + $selector = '.block-editor-inserter__panel-content button.editor-block-list-item-' . $name; + try { + $I->waitForElementVisible($selector); + $I->seeElementInDOM($selector); + $I->click($selector); + break; + } catch (\Exception $e) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Not found, try others. + } + } } // Close block inserter. - $I->click('button.editor-document-tools__inserter-toggle'); + $this->clickAddGutenbergBlockButton($I); // If a Block configuration is specified, apply it to the Block now. if ($blockConfiguration) { @@ -149,7 +189,6 @@ public function addGutenbergBlock($I, $blockName, $blockProgrammaticName, $block public function addGutenbergParagraphBlock($I, $text) { $I->addGutenbergBlock($I, 'Paragraph', 'paragraph/paragraph'); - $I->click('.wp-block-post-content'); $I->fillField('.wp-block-post-content p[data-empty="true"]', $text); } @@ -254,18 +293,22 @@ private function insertGutenbergLink($I, $name) } /** - * Asserts that the given block is not available in the Gutenberg block library. + * Asserts that the given block is available in the Gutenberg block library. + * + * Supports blocks whose programmatic names may be either "block/block" or just "block", + * such as for Paragraph ("paragraph/paragraph" or "paragraph") and Heading ("heading/heading" or "heading") + * for compatibility with different WordPress versions. * * @since 3.0.0 * * @param EndToEndTester $I EndToEnd Tester. * @param string $blockName Block Name (e.g. 'Kit Form'). - * @param string $blockProgrammaticName Programmatic Block Name (e.g. 'convertkit-form'). + * @param string $blockProgrammaticName Programmatic Block Name (e.g. 'convertkit-form' or 'paragraph/paragraph'). */ public function seeGutenbergBlockAvailable($I, $blockName, $blockProgrammaticName) { // Click Add Block Button. - $I->click('button.editor-document-tools__inserter-toggle'); + $this->clickAddGutenbergBlockButton($I); // When the Blocks sidebar appears, search for the block. $I->waitForElementVisible('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); @@ -276,24 +319,51 @@ public function seeGutenbergBlockAvailable($I, $blockName, $blockProgrammaticNam // If we don't do this, we get stale reference errors when trying to click a block to insert. $I->wait(2); - // Confirm the block is available. - // $blockProgrammaticName may contain a slash, which is not valid in a CSS class selector. - // Use XPath to check for a button containing the relevant class. + // Build potential programmatic block names. + $potentialNames = [ $blockProgrammaticName ]; if (strpos($blockProgrammaticName, '/') !== false) { - // XPath: class attribute contains all required classes including block-programmatic-name. - $classParts = explode('/', $blockProgrammaticName); - $xpath = '//button[contains(@class, "editor-block-list-item-' . $blockProgrammaticName . '") and contains(@class,"block-editor-block-types-list__item") and contains(@class,"components-button")]'; - $I->waitForElementVisible([ 'xpath' => $xpath ]); - } else { - $selector = '.block-editor-inserter__panel-content button.editor-block-list-item-' . $blockProgrammaticName; - $I->waitForElementVisible($selector); + // Also add last segment after slash, for compatibility with older WordPress versions. + $parts = explode('/', $blockProgrammaticName); + if (count($parts) === 2) { + $potentialNames[] = $parts[1]; + } + } + + // Confirm the block is available. + $found = false; + foreach ($potentialNames as $name) { + // Check for both XPath (to cover slashes) and regular selector (no slash). + if (strpos($name, '/') !== false) { + $xpath = '//button[contains(@class, "editor-block-list-item-' . $name . '") and contains(@class,"block-editor-block-types-list__item") and contains(@class,"components-button")]'; + try { + $I->waitForElementVisible([ 'xpath' => $xpath ], 2); + $found = true; + break; + } catch (\Exception $e) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Not found, try others. + } + } else { + $selector = '.block-editor-inserter__panel-content button.editor-block-list-item-' . $name; + try { + $I->waitForElementVisible($selector, 2); + $found = true; + break; + } catch (\Exception $e) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Not found, try others. + } + } + } + + // Fail if we don't find any. + if ( ! $found) { + $I->fail("Block '{$blockName}' not found."); } // Clear the search field. $I->click('button[aria-label="Reset search"]'); // Close block inserter. - $I->click('button.editor-document-tools__inserter-toggle'); + $this->clickAddGutenbergBlockButton($I); } /** @@ -308,7 +378,7 @@ public function seeGutenbergBlockAvailable($I, $blockName, $blockProgrammaticNam public function dontSeeGutenbergBlockAvailable($I, $blockName, $blockProgrammaticName) { // Click Add Block Button. - $I->click('button.editor-document-tools__inserter-toggle'); + $this->clickAddGutenbergBlockButton($I); // When the Blocks sidebar appears, search for the block. $I->waitForElementVisible('.interface-interface-skeleton__secondary-sidebar[aria-label="Block Library"]'); @@ -326,7 +396,7 @@ public function dontSeeGutenbergBlockAvailable($I, $blockName, $blockProgrammati $I->click('button[aria-label="Reset search"]'); // Close block inserter. - $I->click('button.editor-document-tools__inserter-toggle'); + $this->clickAddGutenbergBlockButton($I); } /**