Skip to content
Open
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
25 changes: 12 additions & 13 deletions admin/views/editor-bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,8 @@
);

// Inject WordPress configuration BEFORE the closing </head> tag.
// phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedScript -- Standalone HTML page output, not a WordPress template.
$exelearning_wp_config_script = sprintf(
'
<!-- WordPress Integration Configuration -->
<script>
// WordPress Integration Configuration
window.__WP_EXE_CONFIG__ = {
mode: "WordPress",
Expand Down Expand Up @@ -465,8 +462,6 @@ function normalizeEditorAssetUrl(url) {
});
}
})();
</script>
<script src="%s/js/wp-exe-bridge.js"></script>
',
$exelearning_attachment_id,
wp_json_encode( $exelearning_elp_url ),
Expand All @@ -478,15 +473,11 @@ function normalizeEditorAssetUrl(url) {
$exelearning_user_id,
wp_json_encode( $exelearning_editor_base_url ),
wp_json_encode( $exelearning_i18n ),
wp_json_encode( $exelearning_theme_registry_override ),
esc_url( $exelearning_plugin_assets_url )
wp_json_encode( $exelearning_theme_registry_override )
);
// phpcs:enable WordPress.WP.EnqueuedResources.NonEnqueuedScript

// WordPress-specific styles.
$exelearning_page_styles = '
<!-- WordPress-specific styles -->
<style>
/* WordPress-specific overrides */
html, body {
height: 100%;
Expand Down Expand Up @@ -529,11 +520,19 @@ function normalizeEditorAssetUrl(url) {
#mobile-navbar-button-openuserodefiles {
display: none !important;
}
</style>
';

// Insert config script and styles before </head>.
$exelearning_template = str_replace( '</head>', $exelearning_wp_config_script . $exelearning_page_styles . '</head>', $exelearning_template );
// Print only our handles into the standalone document, without a theme header.
wp_enqueue_script( 'exelearning-editor-bridge', $exelearning_plugin_assets_url . '/js/wp-exe-bridge.js', array(), EXELEARNING_VERSION, false );
wp_add_inline_script( 'exelearning-editor-bridge', $exelearning_wp_config_script, 'before' );
wp_register_style( 'exelearning-editor-page', false, array(), EXELEARNING_VERSION );
wp_enqueue_style( 'exelearning-editor-page' );
wp_add_inline_style( 'exelearning-editor-page', $exelearning_page_styles );
ob_start();
wp_print_scripts( array( 'exelearning-editor-bridge' ) );
wp_print_styles( array( 'exelearning-editor-page' ) );
$exelearning_integration_assets = ob_get_clean();
$exelearning_template = str_replace( '</head>', $exelearning_integration_assets . '</head>', $exelearning_template );

// Add <base> tag to set the base URL for all relative paths.
// This ensures paths like "files/perm/..." resolve to the static editor directory.
Expand Down
85 changes: 85 additions & 0 deletions assets/js/exelearning-embed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* Toolbar and poster behavior for embedded eXeLearning packages.
*
* One enqueued script serves shortcode and block controls, including embeds
* inserted after load. Each click stays within its own embed container.
*/
( function () {
'use strict';

/**
* Promote a deferred frame: load it, reveal it and drop the poster.
*
* In poster mode the iframe ships with its URL in `data-src` and hidden, so the
* package is downloaded only when the visitor asks for it. Called both by the
* poster itself and by the fullscreen button, which must not expand a hidden
* frame that has no document yet.
*
* @param {Element} container The embed.
* @return {void}
*/
function activate( container ) {
var iframe = container.querySelector( '.exelearning-iframe' );
if ( ! iframe ) {
return;
}

var deferred = iframe.getAttribute( 'data-src' );
if ( deferred && ! iframe.getAttribute( 'src' ) ) {
iframe.setAttribute( 'src', deferred );
}

iframe.style.display = '';

var poster = container.querySelector( '.exelearning-poster' );
if ( poster ) {
poster.style.display = 'none';
}
}

/**
* Take the embed frame fullscreen, whatever the browser calls it.
*
* @param {Element} container The embed.
* @return {void}
*/
function fullscreen( container ) {
var iframe = container.querySelector( '.exelearning-iframe' );
if ( ! iframe ) {
return;
}

if ( iframe.requestFullscreen ) {
iframe.requestFullscreen();
} else if ( iframe.webkitRequestFullscreen ) {
iframe.webkitRequestFullscreen();
} else if ( iframe.msRequestFullscreen ) {
iframe.msRequestFullscreen();
}
}

document.addEventListener( 'click', function ( event ) {
var target = event.target;
if ( ! target || ! target.closest ) {
return;
}

var control = target.closest( '.exelearning-poster, .exelearning-fullscreen-btn' );
if ( ! control ) {
return;
}

var container = control.closest( '.exelearning-preview, .exelearning-block-frontend' );
if ( ! container ) {
return;
}

// The fullscreen button in poster mode loads and reveals the frame first:
// expanding a hidden, srcless frame would fill the screen with nothing.
activate( container );

if ( control.classList.contains( 'exelearning-fullscreen-btn' ) ) {
fullscreen( container );
}
} );
}() );
5 changes: 3 additions & 2 deletions docs/SHORTCODES.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ content keeps working.
`height` applies to the preview iframe (and screenshot poster). Pixel values
remain fixed, while percentage values define a responsive height relative to the
rendered embed width. For example, `height="75%"` produces a 4:3 preview and
`height="100%"` produces a square preview. The height is recalculated whenever
the embed width changes.
`height="100%"` produces a square preview. It is rendered as a CSS
`aspect-ratio`, so the browser keeps the proportion on every resize with no
script involved.

Anything outside the two supported forms (`0`, negative numbers, `calc()`,
`var()`, viewport units such as `100vh`, or attempted injections) is rejected and
Expand Down
5 changes: 1 addition & 4 deletions exelearning.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
require_once EXELEARNING_PLUGIN_DIR . 'includes/class-download-formats.php';
require_once EXELEARNING_PLUGIN_DIR . 'includes/class-download-button-renderer.php';
require_once EXELEARNING_PLUGIN_DIR . 'includes/class-elp-upload-block.php';
require_once EXELEARNING_PLUGIN_DIR . 'includes/class-viewer-enhancements.php';
require_once EXELEARNING_PLUGIN_DIR . 'includes/class-embed-assets.php';

// Styles management (uploaded/builtin registry).
require_once EXELEARNING_PLUGIN_DIR . 'includes/class-style-package.php';
Expand Down Expand Up @@ -100,9 +100,6 @@
* Starts the plugin.
*/
function exelearning_run() {
$viewer_enhancements = new ExeLearning_Viewer_Enhancements();
$viewer_enhancements->register_hooks();

$plugin = new ExeLearning();
$plugin->run();
}
Expand Down
45 changes: 4 additions & 41 deletions includes/class-elp-upload-block.php
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,10 @@ private function render_block_preview( $data, $download_html ) {
// prints; in a REST render the handle was never registered and this is a
// harmless no-op.
wp_enqueue_script( 'exelearning-embed-loader' );

// Drives the fullscreen button, shared with the shortcode. Enqueued here,
// at the point the markup it binds is rendered, for the same reason.
ExeLearning_Embed_Assets::enqueue();
$html .= '<div class="exelearning-embed-loader">';
$html .= sprintf(
'<iframe
Expand All @@ -379,47 +383,6 @@ class="exelearning-iframe"

$html .= '</div>';

if ( ! empty( $data['fullscreen'] ) ) {
$html .= $this->render_block_fullscreen_script( $data['container_id'] );
}

return $html;
}

/**
* Build the inline fullscreen behavior script for a block preview.
*
* Scoped to the instance container so multiple blocks on one page stay
* independent. The button fullscreens the iframe element from the parent
* page, which works regardless of the iframe sandbox.
*
* @param string $container_id Container element ID.
* @return string Inline <script> markup.
*/
private function render_block_fullscreen_script( $container_id ) {
return sprintf(
'<script>
(function() {
var container = document.getElementById("%s");
if (!container) return;

var btn = container.querySelector(".exelearning-fullscreen-btn");
var iframe = container.querySelector(".exelearning-iframe");

if (btn && iframe) {
btn.addEventListener("click", function() {
if (iframe.requestFullscreen) {
iframe.requestFullscreen();
} else if (iframe.webkitRequestFullscreen) {
iframe.webkitRequestFullscreen();
} else if (iframe.msRequestFullscreen) {
iframe.msRequestFullscreen();
}
});
}
})();
</script>',
esc_attr( $container_id )
);
}
}
40 changes: 40 additions & 0 deletions includes/class-embed-assets.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
/**
* Frontend assets shared by every eXeLearning embed.
*
* @package Exelearning
*/

if ( ! defined( 'WPINC' ) ) {
die;
}

/**
* Class ExeLearning_Embed_Assets.
*
* The shortcode and the block render the same controls (a fullscreen button, and
* a poster that loads the package on click) and used to carry the behavior as an
* inline <script> printed once per embed. Both now enqueue this one file instead.
*/
class ExeLearning_Embed_Assets {

/**
* Enqueue the script that drives the embed controls.
*
* Called from the renderers rather than from `wp_enqueue_scripts`: a page with
* no embed has nothing for the script to bind, and shortcodes render during
* `the_content`, after that hook has already run. Safe to call repeatedly;
* WordPress de-duplicates by handle.
*
* @return void
*/
public static function enqueue() {
wp_enqueue_script(
'exelearning-embed',
plugins_url( 'assets/js/exelearning-embed.js', EXELEARNING_PLUGIN_FILE ),
array(),
EXELEARNING_VERSION,
true
);
}
}
20 changes: 11 additions & 9 deletions includes/class-export-bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,10 @@ private function inject_bootstrap_payload( $template, $attachment_id, $elp_url,
'editorBaseUrl' => $editor_base_url,
);

$bridge_url = EXELEARNING_PLUGIN_URL . 'assets/js/wp-exe-bridge.js?ver=' . EXELEARNING_VERSION;
$bridge_url = EXELEARNING_PLUGIN_URL . 'assets/js/wp-exe-bridge.js';

// phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedScript -- Standalone HTML page output, not a WordPress template; scripts must be inline.
$inject = sprintf(
'<script>
$script = sprintf(
'
window.__WP_EXE_CONFIG__ = %1$s;
window.__EXE_STATIC_MODE__ = true;
window.__EXE_WP_MODE__ = true;
Expand All @@ -203,14 +202,17 @@ private function inject_bootstrap_payload( $template, $attachment_id, $elp_url,
trustedOrigins: [window.location.origin],
hideUI: { fileMenu: true, saveButton: true, userMenu: true },
};
</script>
<script src="%4$s" defer></script>',
',
wp_json_encode( $config ),
wp_json_encode( $editor_base_url ),
wp_json_encode( $elp_url ),
esc_url( $bridge_url )
wp_json_encode( $elp_url )
);
// phpcs:enable WordPress.WP.EnqueuedResources.NonEnqueuedScript
wp_enqueue_script( 'exelearning-export-bridge', $bridge_url, array(), EXELEARNING_VERSION, false );
wp_add_inline_script( 'exelearning-export-bridge', $script, 'before' );
// The bridge initializes on DOMContentLoaded, also on WordPress 6.1.
ob_start();
wp_print_scripts( array( 'exelearning-export-bridge' ) );
$inject = ob_get_clean();

// Inject config and our bridge before </head>.
$template = str_replace( '</head>', $inject . '</head>', $template );
Expand Down
Loading
Loading