Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ Packages are plain WordPress **attachments** — there is no custom post type.
`exelearning/` is a gitignored clone of `exelearning/exelearning`, not a submodule;
`dist/static/` is its generated build. Do not hand-edit either. `npm run build`
is only a reminder, not a build.
Inside WordPress the editor runs on core's jQuery and jQuery UI
(`ExeLearning_Editor::core_jquery_tags()` replaces the bundle's two tags); the
bundled copies stay in `dist/static/libs/` only because exported packages embed
them to run without WordPress.

- `make build-editor`: fetch the source and build it (needs Bun).
- `make build-editor-if-needed`: build only when `dist/static/.build-commit` differs
Expand Down
11 changes: 11 additions & 0 deletions admin/views/editor-bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,17 @@ function normalizeEditorAssetUrl(url) {
$exelearning_integration_assets .= ob_get_clean();
$exelearning_template = str_replace( '</head>', $exelearning_integration_assets . '</head>', $exelearning_template );

// Run the editor on WordPress' jQuery and jQuery UI. The bundled copies stay in
// dist/static/ only because the editor embeds them in exported packages.
$exelearning_template = preg_replace_callback(
'#<(script) src="\./libs/jquery/jquery\.min\.js[^"]*"></\1>\s*<\1 src="\./libs/jquery-ui/jquery-ui\.min\.js[^"]*"></\1>#',
static function () {
return ExeLearning_Editor::core_jquery_tags();
},
$exelearning_template,
1
);

// Add <base> tag to set the base URL for all relative paths.
// This ensures paths like "files/perm/..." resolve to the static editor directory.
// The word boundary matters: the editor's own markup contains
Expand Down
38 changes: 38 additions & 0 deletions includes/class-exelearning-editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,44 @@ public function build_bootstrap_page( $attachment_id ) {
return include EXELEARNING_PLUGIN_DIR . 'admin/views/editor-bootstrap.php';
}

/**
* Script tags that load WordPress' own jQuery and jQuery UI.
*
* The editor bundle ships jQuery and jQuery UI because exported packages must
* run without WordPress; inside WordPress the editor uses core's copies
* instead. Every jQuery UI and effects handle core registers is loaded, in
* dependency order, from a private WP_Scripts so the page queue is untouched.
* Core's jQuery calls noConflict(), and the editor expects a global $.
*
* @return string
*/
public static function core_jquery_tags() {
$scripts = new WP_Scripts();
$handles = array( 'jquery-core' );
foreach ( array_keys( $scripts->registered ) as $handle ) {
if ( 0 === strpos( $handle, 'jquery-ui-' ) || 0 === strpos( $handle, 'jquery-effects-' ) ) {
$handles[] = $handle;
}
}
$scripts->all_deps( $handles );

$tags = '';
foreach ( $scripts->to_do as $handle ) {
$script = $scripts->registered[ $handle ];
// Only jQuery itself and jQuery UI: the bundled jQuery never ran with
// Migrate, and core's wp-a11y/i18n dependencies are not used by the editor.
if ( 'jquery-migrate' === $handle || false === strpos( (string) $script->src, '/js/jquery/' ) ) {
continue;
}
$src = preg_match( '#^(https?:)?//#', $script->src ) ? $script->src : $scripts->base_url . $script->src;
$tags .= wp_get_script_tag( array( 'src' => add_query_arg( 'ver', $script->ver ? $script->ver : $scripts->default_version, $src ) ) );
if ( 'jquery-core' === $handle ) {
$tags .= wp_get_inline_script_tag( 'window.$ = window.jQuery;' );
}
}
return $tags;
}

/**
* Settings screen URL that explains a missing editor bundle.
*
Expand Down
25 changes: 25 additions & 0 deletions tests/unit/EditorBootstrapPageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,31 @@ public function test_relative_asset_paths_in_the_bundle_are_rewritten() {
$this->assertStringNotContainsString( 'src="./app/main.js"', $html );
}

/**
* The editor runs on WordPress' jQuery and jQuery UI; the bundled copies are
* left for exported packages only (Plugin Directory guideline 13).
*/
public function test_the_editor_uses_core_jquery_instead_of_the_bundled_copies() {
wp_set_current_user( $this->factory->user->create( array( 'role' => 'administrator' ) ) );
ExeLearning_Bundle_Fixture::write(
'index.html',
'<html><head></head><body>'
. "<script src=\"./libs/jquery/jquery.min.js?v=1\"></script>\n <script src=\"./libs/jquery-ui/jquery-ui.min.js?v=1\"></script>"
. '<script src="./app/main.js"></script></body></html>'
);

$html = $this->editor->build_bootstrap_page( $this->make_elpx() );

$this->assertStringNotContainsString( 'libs/jquery/jquery.min.js', $html );
$this->assertStringNotContainsString( 'libs/jquery-ui/jquery-ui.min.js', $html );
$this->assertStringContainsString( includes_url( 'js/jquery/' ), $html );
$this->assertStringContainsString( includes_url( 'js/jquery/ui/sortable' ), $html );
$this->assertStringContainsString( 'window.$ = window.jQuery;', $html );
$this->assertStringNotContainsString( 'jquery-migrate', $html );
// Core's copies load before the editor code that relies on them.
$this->assertLessThan( strpos( $html, 'main.js' ), strpos( $html, includes_url( 'js/jquery/ui/sortable' ) ) );
}

/**
* The bridge script is loaded from the plugin's own assets, not from the
* editor bundle: it is the WordPress half of the protocol.
Expand Down
Loading