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/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/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/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); } /**