elementor / elementor/elementor

Editor preview overwrites the published post-{id}.css with unpublished draft styles (external CSS print method)

Open
#36,851 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
PHP
Stars
7.1k
Forks
1.6k
Avg merge
1d 5h
Merged PRs (30d)
193

Description

### Description

When `elementor_css_print_method` is `external`, editing a **published** document in the editor **without publishing** overwrites that document's live CSS file with the draft's styles.

`Post_Preview` takes its *element data* from the autosave revision but derives its *file name* from the parent post, so the preview build is written to the published document's stylesheet. The front end then serves published markup styled by unpublished CSS.

I hit this on a production site: a published page rendered its correct published content while the linked stylesheet contained styles from an open, unpublished draft. It persisted for about 5.5 hours, until something triggered a normal rebuild of the published document.

**Cause**

`core/files/css/post-preview.php`:

```php
public function __construct( $post_id ) {
$this->post_id_for_data = $post_id; // autosave -> DRAFT data

$parent_id = wp_get_post_parent_id( $post_id );

parent::__construct( $parent_id ); // parent -> PUBLISHED file name
}
```

`Post_Local_Cache` overrides only `load_meta()` / `update_meta()` / `delete_meta()`, keeping metadata in memory. It does **not** override `update_file()` or `write()`, so `Core\Files\Base::write()` still runs `file_put_contents()` against the published path. `Post_Local_Cache::is_update_required()` also returns a hardcoded `true`, so this regenerates eagerly.

**Secondary effect: the overwrite is invisible to cache-busting**

The `?ver=` arg comes from the parent's `_elementor_css['time']` meta. Because `Post_Local_Cache` keeps meta in memory only, a preview write never updates it — so the **file contents change while the URL stays byte-for-byte identical**. With the long-lived `Cache-Control` typically served for files under `uploads/`, a draft build can get pinned in CDN and browser caches under an unchanged URL, which makes the symptom look intermittent and user-specific.

Observed directly (same URL, no front-end request in between):

```
before preview render: ....elementor-element-bbbb222 .elementor-heading-title{color:#756CDB;}
after preview render: ....elementor-element-bbbb222 .elementor-heading-title{color:#ABCDEF;}
```

`Elementor\Modules\LoopBuilder\Files\CSS\Loop_Preview` (Pro) extends `Post_Preview` and appears to inherit the same behaviour.

### Steps to reproduce

Tested on a clean install with **only Elementor, Elementor Pro and Hello Elementor active**, all caches flushed, on a brand-new page.

1. Under **Elementor → Settings → Advanced**, set **CSS Print Method** to **External File**.
2. Create a new page, add a Heading widget, set its **Text Color** to `#756CDB`, and **Publish**.
3. Note the page's ID (e.g. `21012`) and confirm `wp-content/uploads/elementor/css/post-21012.css` contains `color:#756CDB`.
4. Open the page in the editor and change the Heading's Text Color to `#ABCDEF`. **Do not publish or update.** Let the editor render its preview.
5. Re-read `wp-content/uploads/elementor/css/post-21012.css` on disk.

Result at step 5:

```
STEP 1 — published state
post-21012.css: 180 bytes
contains published colour #756CDB : yes

STEP 3 — editor preview renders
Post_Preview(21014) writes to: post-21012.css

STEP 4 — the PUBLISHED css file
md5 b3857db863ff -> 4c4659efd1a1 (OVERWRITTEN)
contains published colour #756CDB : NO <-- lost
contains DRAFT colour #ABCDEF : YES <-- unpublished style is now live
```

Requesting the stylesheet URL directly then returns the draft build:

```
$ curl https://example.test/wp-content/uploads/elementor/css/post-21012.css
.elementor-21012 .elementor-element.elementor-element-bbbb222 .elementor-heading-title{color:#ABCDEF;}
```

### Expected behavior

Editing a document without publishing should never modify what anonymous visitors load. Preview/autosave CSS should be written to its own file, leaving the published document's stylesheet untouched until the document is actually published.

Giving the preview file its own name is enough, and `Core\Files\Base::__construct()` already applies `elementor/files/file_name` *before* `set_file_name()` and `set_path()`, so it moves the write target and the enqueued URL together:

```php
add_filter( 'elementor/files/file_name', function ( $file_name, $file ) {
if ( $file instanceof \Elementor\Core\Files\CSS\Post_Preview ) {
return preg_replace( '/\.css$/', '-preview.css', $file_name );
}
return $file_name;
}, 10, 2 );
```

I've verified this as a workaround: with it applied, `Post_Preview` writes `post-{id}-preview.css`, the published file stays byte-identical, and the editor still shows draft styling because it enqueues the same filtered name. `Files\Manager::clear_cache()` globs `*`, so the extra files are still cleaned up by *Regenerate Files & Data*.

### Elementor System Info

```

== Server Environment ==
Operating System: Linux
Software: nginx/1.29.5
MySQL version: mariadb.org binary distribution v10.5.29
PHP Version: 8.3.33
PHP Memory Limit: 4096M
PHP Max Input Vars: 5000
PHP Max Post Size: 128M
GD Installed: Yes
ZIP Installed: Yes
Write Permissions: All right
Elementor Library: Connected

== WordPress Environment ==
Version: 7.0.3
Site URL: https://zendo.local
Home URL: https://zendo.local
WP Multisite: No
Max Upload Size: 128 MB
Memory limit: 1024M
Max Memory limit: 1024M
Permalink Structure: /blog/%postname%/
Language: en_US
Timezone: 0
Admin Email: admin@zendo.local
Debug Mode: Active

== Theme ==
Name: Hello Elementor
Version: 3.4.9
Author: Elementor Team
Child Theme: No

== User ==
Role: administrator
WP Profile lang: en-US
User Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36

== Active Plugins ==
Elementor
Version: 4.2.2
Author: Elementor.com

Elementor Pro
Version: 4.2.1
Author: Elementor.com

== Must-Use Plugins ==
Elementor Safe Mode
Version: 1.0.0
Author: Elementor.com

== Elementor - Compatibility Tag ==

Elementor Pro: Compatible

== Elementor Pro - Compatibility Tag ==

```

Relevant Elementor settings for this report, which the System Info export does not include:

```
elementor_css_print_method: 'external'
elementor_experiment-e_font_icon_svg: 'active'
elementor_disable_color_schemes: 'yes'
elementor_optimized_dom_output: false
```

Reproduced on the same versions on a production host (Apache 2.4.68 / PHP 8.3.31 / MariaDB 10.5.29) and on the local environment the export above was taken from (nginx / PHP 8.3.33 / MariaDB 10.5.29). Server, web server and OS differ between the two; the behaviour is identical.

_Refiled from #36849, which was closed for missing the full System Info export. Everything else is unchanged._

### Agreement

- [X] I confirm I have read and followed all the guidelines and instructions outlined in the Elementor Bug Report form.
- [X] I agree that my issue may be closed without further action if it doesn't meet all the requirements outlined in the Elementor Bug Report form.

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the external CSS workflow, then read core/files/css/post-preview.php, Post_Local_Cache, and Core\Files\Base::write(). Check how Files\Manager::clear_cache() handles generated files and compare the published and preview paths. Done means an unpublished preview no longer changes the published stylesheet, while the editor still loads its draft styles.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
backend, frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.