diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0db93d8e1cd6..635e1fbc4cd6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,6 @@ name: Build on: workflow_dispatch: - pull_request: paths-ignore: - '**.md' - '**.txt' @@ -20,6 +19,13 @@ on: branches: - 'main' - '3.*' + paths-ignore: + - '**.md' + - '**.txt' + - '.github/config.json' + - 'bin/**' + - '.gitignore' + - 'docs/**' jobs: build-plugin: diff --git a/.github/workflows/css-file-check.yml b/.github/workflows/css-file-check.yml index 4b32f2b79bb0..430d9d390567 100644 --- a/.github/workflows/css-file-check.yml +++ b/.github/workflows/css-file-check.yml @@ -1,7 +1,10 @@ name: Check CSS Size Limit on: - pull_request: + push: + branches: + - 'main' + - '3.*' types: ['opened', 'edited', 'reopened', 'synchronize'] paths-ignore: - '**.md' diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index 1f27d6ba1119..db51bcc137a0 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -1,7 +1,10 @@ name: Lighthouse on: - pull_request: + push: + branches: + - 'main' + - '3.*' paths-ignore: - '**.md' - '**.txt' diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 9f8a1bd43ddf..aa7e23ff7718 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -42,7 +42,7 @@ jobs: strategy: fail-fast: false matrix: - wordpress_versions: ['nightly', 'latest', '6.5', '6.4', '6.3'] + wordpress_versions: ['nightly', 'latest', '6.6', '6.5'] php_versions: ['7.4', '8.0'] name: PHPUnit - WordPress ${{ matrix.wordpress_versions }} - PHP version ${{ matrix.php_versions }} env: diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 40cef4f81850..9956cdfca078 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,7 +1,10 @@ name: Playwright on: - pull_request: + push: + branches: + - 'main' + - '3.*' paths-ignore: - '**.md' - '**.txt' @@ -156,20 +159,20 @@ jobs: if: ${{ needs.Playwright.result == 'failure' }} uses: actions/checkout@v4 - name: Send slack message - if: ${{ needs.Playwright.result == 'failure' && github.event_name == 'schedule' }} + if: ${{ needs.Playwright.result == 'failure' && github.event_name != 'workflow_dispatch'}} uses: ./.github/workflows/post-to-slack with: SLACK_BOT_TOKEN: ${{ secrets.SLACK_TOKEN }} SLACK_TAG_CHANNELS: ${{ secrets.TEST_AUTOMATION_RESULTS }} PAYLOAD: | { - "text": "Elementor Core: Playwright with WordPress nightly has failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", + "text": "Elementor Core: Playwright - ${{github.event_name}} has failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", - "text": "Elementor Core: Playwright with WordPress nightly failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + "text": "Elementor Core: Playwright - ${{github.event_name}} failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" } } ] diff --git a/.github/workflows/plugin-upgrade-test.yml b/.github/workflows/plugin-upgrade-test.yml index b7d12cc230a8..daf7908934cf 100644 --- a/.github/workflows/plugin-upgrade-test.yml +++ b/.github/workflows/plugin-upgrade-test.yml @@ -1,7 +1,10 @@ name: Upgrade Elementor test on: - pull_request: + push: + branches: + - 'main' + - '3.*' paths-ignore: - '**.md' - '**.txt' diff --git a/.grunt-config/sass.js b/.grunt-config/sass.js index ee249b143d98..f10e3e5ff9eb 100644 --- a/.grunt-config/sass.js +++ b/.grunt-config/sass.js @@ -52,7 +52,7 @@ const sass = { { expand: true, cwd: 'modules/styleguide/assets/scss', - src: '*.scss', + src: 'editor.scss', dest: 'assets/css/modules/styleguide', ext: '.css', }, diff --git a/.grunt-config/webpack.packages.js b/.grunt-config/webpack.packages.js index ea9c8c306c56..5f8d3ab4fa73 100644 --- a/.grunt-config/webpack.packages.js +++ b/.grunt-config/webpack.packages.js @@ -8,6 +8,18 @@ const usingLocalRepo = process.env.ELEMENTOR_PACKAGES_USE_LOCAL; const packages = usingLocalRepo ? getLocalRepoPackagesEntries() : getNodeModulesPackagesEntries(); +const REGEXES = { + // @elementor/ui/SvgIcon. Used inside @elementor/icons + elementorPathImports: /^@elementor\/(ui|icons)\/(.+)$/, + + // @elementor/editor + // We want to bundle `@elementor/design-tokens` inside the UI package since it's an internal thing. + elementorPackages: /^@elementor\/(?!design-tokens)(.+)$/, + + // @wordpress/components + wordpressPackages: /^@wordpress\/(.+)$/, +}; + const common = { name: 'packages', entry: Object.fromEntries( @@ -37,9 +49,9 @@ const common = { new GenerateWordPressAssetFileWebpackPlugin( { handle: ( entryName ) => `elementor-v2-${entryName}`, map: [ - { request: /^@elementor\/(ui|icons)(\/.+)?$/, handle: 'elementor-v2-$1' }, - { request: /^@elementor\/(.+)$/, handle: 'elementor-v2-$1' }, - { request: /^@wordpress\/(.+)$/, handle: 'wp-$1' }, + { request: REGEXES.elementorPathImports, handle: 'elementor-v2-$1' }, + { request: REGEXES.elementorPackages, handle: 'elementor-v2-$1' }, + { request: REGEXES.wordpressPackages, handle: 'wp-$1' }, { request: 'react', handle: 'react' }, { request: 'react-dom', handle: 'react-dom' }, ] @@ -47,9 +59,9 @@ const common = { new ExternalizeWordPressAssetsWebpackPlugin( { global: ( entryName ) => [ 'elementorV2', entryName ], map: [ - { request: /^@elementor\/(ui|icons)\/(.+)$/, global: [ 'elementorV2', '$1', '$2' ] }, - { request: /^@elementor\/(.+)$/, global: [ 'elementorV2', '$1' ] }, - { request: /^@wordpress\/(.+)$/, global: [ 'wp', '$1' ] }, + { request: REGEXES.elementorPathImports, global: [ 'elementorV2', '$1', '$2' ] }, + { request: REGEXES.elementorPackages, global: [ 'elementorV2', '$1' ] }, + { request: REGEXES.wordpressPackages, global: [ 'wp', '$1' ] }, { request: 'react', global: 'React' }, { request: 'react-dom', global: 'ReactDOM' }, ] diff --git a/app/modules/kit-library/data/repository.php b/app/modules/kit-library/data/repository.php index dcd39bb3846c..1af41580d9db 100644 --- a/app/modules/kit-library/data/repository.php +++ b/app/modules/kit-library/data/repository.php @@ -85,7 +85,7 @@ public function find( $id, $options = [] ) { $manifest = $this->api->get_manifest( $id ); if ( is_wp_error( $manifest ) ) { - throw new WP_Error_Exception( $manifest ); + throw new WP_Error_Exception( esc_html( $manifest ) ); } } @@ -128,7 +128,7 @@ public function get_download_link( $id ) { $response = $this->api->download_link( $id ); if ( is_wp_error( $response ) ) { - throw new WP_Error_Exception( $response ); + throw new WP_Error_Exception( esc_html( $response ) ); } return [ 'download_link' => $response->download_link ]; @@ -204,7 +204,7 @@ private function get_kits_data( $force_api_request = false ) { $data = $this->api->get_all( $args ); if ( is_wp_error( $data ) ) { - throw new WP_Error_Exception( $data ); + throw new WP_Error_Exception( esc_html( $data ) ); } set_transient( static::KITS_CACHE_KEY, $data, static::KITS_CACHE_TTL_HOURS * HOUR_IN_SECONDS ); @@ -225,7 +225,7 @@ private function get_taxonomies_data( $force_api_request = false ) { $data = $this->api->get_taxonomies(); if ( is_wp_error( $data ) ) { - throw new WP_Error_Exception( $data ); + throw new WP_Error_Exception( esc_html( $data ) ); } set_transient( static::KITS_TAXONOMIES_CACHE_KEY, $data, static::KITS_TAXONOMIES_CACHE_TTL_HOURS * HOUR_IN_SECONDS ); diff --git a/app/modules/onboarding/module.php b/app/modules/onboarding/module.php index e7d236c2610d..b93932c9145a 100644 --- a/app/modules/onboarding/module.php +++ b/app/modules/onboarding/module.php @@ -257,7 +257,7 @@ private function maybe_update_site_logo() { private function maybe_upload_logo_image() { $error_message = esc_html__( 'There was a problem uploading your file.', 'elementor' ); - $file = Utils::get_super_global_value( $_FILES, 'fileToUpload' ); + $file = Utils::get_super_global_value( $_FILES, 'fileToUpload' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( ! is_array( $file ) || empty( $file['type'] ) ) { @@ -342,7 +342,7 @@ private function upload_and_install_pro() { $error_message = esc_html__( 'There was a problem uploading your file.', 'elementor' ); - $file = Utils::get_super_global_value( $_FILES, 'fileToUpload' ) ?? []; + $file = Utils::get_super_global_value( $_FILES, 'fileToUpload' ) ?? []; // phpcs:ignore WordPress.Security.NonceVerification.Missing // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( ! is_array( $file ) || empty( $file['type'] ) ) { diff --git a/assets/dev/scss/frontend/widgets/text-editor.scss b/assets/dev/scss/frontend/widgets/text-editor.scss index e7c37d01d866..e2af3103a2f9 100644 --- a/assets/dev/scss/frontend/widgets/text-editor.scss +++ b/assets/dev/scss/frontend/widgets/text-editor.scss @@ -26,7 +26,7 @@ &:not(.elementor-drop-cap-view-default) { .elementor-drop-cap { - margin-top: 8px; + margin-block-start: 8px; &-letter { width: 1em; @@ -36,7 +36,7 @@ } .elementor-drop-cap { - float: $start; + float: inline-start; text-align: center; line-height: 1; font-size: 50px; diff --git a/changelog.txt b/changelog.txt index 6c1e514d78ba..4685c5651f4b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,27 @@ == Changelog == += 3.27.0 - 2025-01-20 = + +* New: Introducing local loading of Google Fonts to improve performance and enhance user privacy ([#4544](https://github.com/elementor/elementor/issues/4544), [#19966](https://github.com/elementor/elementor/issues/19966), [#23932](https://github.com/elementor/elementor/issues/23932), [#21716](https://github.com/elementor/elementor/issues/21716)) +* New: Introducing advanced layout customization for Grid Container with column and row span controls for precise grid-based designs ([#25256](https://github.com/elementor/elementor/issues/25256)) +* New: Added the option to animate with AI motion effects for creating AI-generated animations +* Tweak: Add 'YouTube shorts' support in the video widget ([#24220](https://github.com/elementor/elementor/issues/24220), [#20330](https://github.com/elementor/elementor/issues/20330)) +* Tweak: Added Safari browser compatibility for the 'Fit to Size' option in the Icon Widget ([#27679](https://github.com/elementor/elementor/issues/27679)) +* Tweak: Moved style loading to the head instead of the footer to improve CLS +* Tweak: Enabled conditional loading of `Swiper.js` based on widget dependencies to reduce unnecessary assets and improve page load times +* Tweak: Removed the `elementor-widget-container` div from the Spacer Widget as part of the Optimized Markup experiment to improve HTML structure +* Tweak: Improved keyboard accessibility for the nested container presets area +* Tweak: Added accessible and descriptive names to the icon link in the Icon Box widget +* Tweak: Optimize background video CSS by merging `elementor-html5-video` and `elementor-background-video-hosted` into a single class +* Tweak: Added the ability to disable the Element Cache +* Tweak: Removed animation class when no animations are set in Icon and Icon Widget widgets +* Tweak: Added support for captions on YouTube videos +* Tweak: Removed the limitation restricting heading hover color styling to links only +* Tweak: Removed `aspect-ratio` workaround for unsupported Safari browsers +* Fix: Switching between images in a lightbox on responsive mode displayed a tall blue rectangle ([#12830](https://github.com/elementor/elementor/issues/12830)) +* Fix: Image editing tools with AI are not working when launched in WooCommerce +* Fix: Improved HTML markup validity by removing the `type` attribute from `mock-value-1' ], + 'not-in-schema' => [ '$$type' => 'string', 'value' => 'Not in schema' ], + ], + ], + ] ); + + // Assert. + $this->assertEquals( + $result, + [ + '$$type' => 'dynamic', + 'value' => [ + 'name' => 'mock-dynamic-tag', + 'settings' => [ + 'mock-control-1' => [ '$$type' => 'string', 'value' => 'mock-value-1' ], + ], + ], + ] + ); + } + /** * @dataProvider invalid_value_data_provider */ diff --git a/tests/phpunit/elementor/modules/atomic-widgets/styles/__snapshots__/Test_Styles_Renderer__test_render__style_with_background_overlay_transformers__1.txt b/tests/phpunit/elementor/modules/atomic-widgets/styles/__snapshots__/Test_Styles_Renderer__test_render__style_with_background_overlay_transformers__1.txt index 763477bd15f3..846958a81b9f 100644 --- a/tests/phpunit/elementor/modules/atomic-widgets/styles/__snapshots__/Test_Styles_Renderer__test_render__style_with_background_overlay_transformers__1.txt +++ b/tests/phpunit/elementor/modules/atomic-widgets/styles/__snapshots__/Test_Styles_Renderer__test_render__style_with_background_overlay_transformers__1.txt @@ -1 +1 @@ -.test-background-overlay{background:linear-gradient(blue, blue),url(" https://example.com/image.jpg ");} \ No newline at end of file +.test-background-overlay{background:linear-gradient(blue, blue),url(" https://example.com/image.jpg ") 0% 0% / contain;} \ No newline at end of file diff --git a/tests/phpunit/elementor/modules/atomic-widgets/styles/__snapshots__/Test_Styles_Renderer__test_render__style_with_nested_background_image_transformers__1.txt b/tests/phpunit/elementor/modules/atomic-widgets/styles/__snapshots__/Test_Styles_Renderer__test_render__style_with_nested_background_image_transformers__1.txt index 4aac01c047e0..ab015868f7f0 100644 --- a/tests/phpunit/elementor/modules/atomic-widgets/styles/__snapshots__/Test_Styles_Renderer__test_render__style_with_nested_background_image_transformers__1.txt +++ b/tests/phpunit/elementor/modules/atomic-widgets/styles/__snapshots__/Test_Styles_Renderer__test_render__style_with_nested_background_image_transformers__1.txt @@ -1 +1 @@ -.test-style{background:linear-gradient(blue, blue),url(" https://example.com/image.jpg ") red;} \ No newline at end of file +.test-style{background:linear-gradient(blue, blue),url(" https://example.com/image.jpg ") 0% 0% / cover red;} \ No newline at end of file diff --git a/tests/phpunit/elementor/modules/atomic-widgets/styles/__snapshots__/Test_Styles_Renderer__test_render__style_with_nested_background_transformers__1.txt b/tests/phpunit/elementor/modules/atomic-widgets/styles/__snapshots__/Test_Styles_Renderer__test_render__style_with_nested_background_transformers__1.txt index 4aac01c047e0..2c6f6fd24b74 100644 --- a/tests/phpunit/elementor/modules/atomic-widgets/styles/__snapshots__/Test_Styles_Renderer__test_render__style_with_nested_background_transformers__1.txt +++ b/tests/phpunit/elementor/modules/atomic-widgets/styles/__snapshots__/Test_Styles_Renderer__test_render__style_with_nested_background_transformers__1.txt @@ -1 +1 @@ -.test-style{background:linear-gradient(blue, blue),url(" https://example.com/image.jpg ") red;} \ No newline at end of file +.test-style{background:linear-gradient(blue, blue),url(" https://example.com/image.jpg ") 0% 0% / auto red;} \ No newline at end of file diff --git a/tests/phpunit/elementor/modules/atomic-widgets/styles/test-styles-renderer.php b/tests/phpunit/elementor/modules/atomic-widgets/styles/test-styles-renderer.php index 70cba6123355..d3b19302b9f9 100644 --- a/tests/phpunit/elementor/modules/atomic-widgets/styles/test-styles-renderer.php +++ b/tests/phpunit/elementor/modules/atomic-widgets/styles/test-styles-renderer.php @@ -423,7 +423,8 @@ public function test_render__style_with_nested_background_transformers() { ], 'url' => null ], - ] + ], + 'size' => 'auto', ] ], ], @@ -483,7 +484,8 @@ public function test_render__style_with_nested_background_image_transformers() { 'id' => null, 'url' => 'https://example.com/image.jpg', ], - ] + ], + 'size' => 'cover', ] ], ], @@ -595,7 +597,8 @@ public function test_render__style_with_background_overlay_transformers() { ], 'url' => null, ], - ] + ], + 'size' => 'contain', ] ], ], diff --git a/tests/phpunit/elementor/modules/atomic-widgets/test-atomic-svg.php b/tests/phpunit/elementor/modules/atomic-widgets/test-atomic-svg.php new file mode 100644 index 000000000000..e21b21c8cda8 --- /dev/null +++ b/tests/phpunit/elementor/modules/atomic-widgets/test-atomic-svg.php @@ -0,0 +1,33 @@ + 'abcd123', + 'elType' => 'widget', + 'settings' => [], + 'widgetType' => 'a-svg', + ]; + + protected $instance; + + public function setUp(): void { + parent::setUp(); + + $this->instance = Plugin::$instance->elements_manager->create_element_instance( self::MOCK ); + } + + public function test__render_paragraph(): void { + // Act. + ob_start(); + $this->instance->render_content(); + $rendered_output = ob_get_clean(); + + // Assert. + $this->assertMatchesSnapshot( $rendered_output ); + } +} diff --git a/tests/phpunit/elementor/modules/atomic-widgets/test-atomic-widget-base.php b/tests/phpunit/elementor/modules/atomic-widgets/test-atomic-widget-base.php index 4ae09d36443b..aa7e8e932e9f 100644 --- a/tests/phpunit/elementor/modules/atomic-widgets/test-atomic-widget-base.php +++ b/tests/phpunit/elementor/modules/atomic-widgets/test-atomic-widget-base.php @@ -152,7 +152,14 @@ public function get_atomic_settings_data_provider() { 'value' => [ 'name' => 'dynamic-tag', 'settings' => [ - 'before' => 'Before text - ' + 'before' => [ + '$$type' => 'string', + 'value' => 'Before text - ', + ], + 'not-in-schema' => [ + '$$type' => 'string', + 'value' => 'Not in schema', + ], ], ], ], @@ -1341,7 +1348,8 @@ public function test_get_data_for_save__throws_on_styles_background_color_overla ], 'url' => null ], - ] + ], + 'size' => 'cover', ] ], ], diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index c2740351b8c7..c306fcdf00e7 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -21,7 +21,7 @@ export default defineConfig( { toHaveScreenshot: { maxDiffPixelRatio: 0.03 }, }, forbidOnly: !! process.env.CI, - retries: process.env.CI ? 1 : 0, + retries: process.env.CI ? 9 : 0, workers: process.env.CI ? 2 : 1, fullyParallel: false, reporter: process.env.CI diff --git a/tests/playwright/sanity/translations.languages.ts b/tests/playwright/sanity/translations.languages.ts new file mode 100644 index 000000000000..c8131414d3ae --- /dev/null +++ b/tests/playwright/sanity/translations.languages.ts @@ -0,0 +1,18 @@ +export const testCaseslanguages = [ + { + language: 'he_IL', + buttonText: 'פרסם', + }, + { + language: 'en_NZ', + buttonText: 'Publish', + }, + { + language: 'de_DE', + buttonText: 'Veröffentlichen', + }, + { + language: 'fr_BE', + buttonText: 'Publier', + }, +]; diff --git a/tests/playwright/sanity/translations.test.ts b/tests/playwright/sanity/translations.test.ts new file mode 100644 index 000000000000..9aea8f58bb0e --- /dev/null +++ b/tests/playwright/sanity/translations.test.ts @@ -0,0 +1,42 @@ +import { parallelTest as test } from '../parallelTest'; +import WpAdminPage from '../pages/wp-admin-page'; +import { expect } from '@playwright/test'; +import EditorPage from '../pages/editor-page'; +import { testCaseslanguages } from './translations.languages'; + +test.describe( 'Test site translation for different languages', () => { + testCaseslanguages.forEach( ( languageObj ) => { + test( `Test of site translation for language ${ languageObj.language }`, async ( { browser, apiRequests }, testInfo ) => { + // Arrange. + const context = await browser.newContext(); + const page = await context.newPage(); + const wpAdmin = new WpAdminPage( page, testInfo, apiRequests ); + const editor = new EditorPage( page, testInfo ); + const translationButton = 'Form[name=upgrade-translations] input[type=submit]'; + + // Act + await wpAdmin.setSiteLanguage( languageObj.language ); + await page.goto( '/wp-admin/update-core.php' ); + await page.locator( '.update-last-checked' ).click(); + if ( await page.locator( translationButton ).isVisible() ) { + await page.locator( translationButton ).click(); + await page.waitForLoadState( 'networkidle' ); + } + await wpAdmin.openNewPage(); + await editor.closeNavigatorIfOpen(); + const publishButton = page.locator( 'button.MuiButton-root' ).nth( 1 ); + + // Assert. + await expect( publishButton ).toHaveText( languageObj.buttonText ); + await page.close(); + } ); + } ); +} ); + +test.afterAll( async ( { browser, apiRequests }, testInfo ) => { + // Reset to default language (English) + const context = await browser.newContext(); + const page = await context.newPage(); + const wpAdmin = new WpAdminPage( page, testInfo, apiRequests ); + await wpAdmin.setSiteLanguage( '' ); +} );