diff --git a/AGENTS.md b/AGENTS.md index 4321924..0f22a54 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,6 +92,7 @@ These are natural-language guidelines for agents to follow when developing the E - Prefer simplicity and clarity: avoid overly complex abstractions. - Load translation strings properly (`__()`, `_e()`), text domain declared in main plugin file. - Keep plugin bootstrap file small (`exelearning.php`), modularize into separate files/classes with specific responsibility. +- Keep the reference docs under `docs/` in sync with the code: `docs/SHORTCODES.md` documents the `[exelearning]` shortcode and all its attributes (including `teacher_mode` and `screenshot`), and `docs/HOOKS.md` documents the developer actions and filters. When you add or change a shortcode attribute or a hook, update the matching doc in the same change. ## Aider-specific usage diff --git a/README.md b/README.md index e160055..0e859ef 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,16 @@ EXELEARNING_EDITOR_REF=my-feature EXELEARNING_EDITOR_REF_TYPE=branch make build- ``` [exelearning id="123"] ``` -Replace `123` with the attachment ID of your ELPX file. +Replace `123` with the attachment ID of your ELPX file. The shortcode also +accepts options to set the height, activate teacher mode, show a download button, +or display the package screenshot: + +``` +[exelearning id="123" height="800" teacher_mode="1" screenshot="poster"] +``` + +See [`docs/SHORTCODES.md`](docs/SHORTCODES.md) for the full shortcode reference, +all attributes, and examples. ### Viewing ELPX Files diff --git a/docs/SHORTCODES.md b/docs/SHORTCODES.md new file mode 100644 index 0000000..05f31d7 --- /dev/null +++ b/docs/SHORTCODES.md @@ -0,0 +1,125 @@ +# Shortcode reference + +The eXeLearning plugin exposes a single shortcode, `[exelearning]`, to embed an +uploaded `.elpx` package anywhere classic-editor content is rendered (posts, +pages, widgets, or any context that runs `do_shortcode()`). + +The shortcode works directly with **WordPress attachments**: you reference an +uploaded package by its Media Library attachment ID. No custom post type is +involved. + +```text +[exelearning id="123"] +``` + +Replace `123` with the attachment ID of your `.elpx` file (visible in the Media +Library, e.g. in the URL `…/upload.php?item=123`). + +## How it renders + +- If the attachment has been extracted and contains a previewable `index.html`, + the shortcode renders a sandboxed, same-origin `', + $iframe_src_attr, + $height, + $is_poster ? ' display: none;' : '', + esc_attr( $title ) + ); + return sprintf( '
@@ -233,18 +370,91 @@ private function render_preview( $title, $preview_url, $height, $file_url, $teac
- - - ', esc_attr( $unique_id ), - esc_html( $title ), - '' !== $download_html ? $download_html : $fallback_download, - esc_attr__( 'View fullscreen', 'exelearning' ), - esc_url( $preview_url ), - $height, - esc_attr( $title ), - esc_attr( $unique_id ), - $teacher_mode_visible ? 'true' : 'false' + $body ); } } diff --git a/tests/unit/ShortcodesTest.php b/tests/unit/ShortcodesTest.php index db04b23..aae0647 100644 --- a/tests/unit/ShortcodesTest.php +++ b/tests/unit/ShortcodesTest.php @@ -299,4 +299,148 @@ public function test_register_shortcodes_exists() { public function test_display_exelearning_exists() { $this->assertTrue( method_exists( $this->shortcodes, 'display_exelearning' ) ); } + + /** + * Create an attachment with a previewable extraction directory. + * + * @param string $hash Extraction hash (40 hex chars). + * @param bool $with_screenshot Whether to also write a screenshot.png fixture. + * @return int Attachment ID. + */ + private function create_previewable_attachment( $hash, $with_screenshot = false ) { + $attachment_id = $this->factory->attachment->create(); + update_post_meta( $attachment_id, '_exelearning_extracted', $hash ); + update_post_meta( $attachment_id, '_exelearning_has_preview', '1' ); + + if ( $with_screenshot ) { + $upload_dir = wp_upload_dir(); + $dir = trailingslashit( $upload_dir['basedir'] ) . 'exelearning/' . $hash; + wp_mkdir_p( $dir ); + file_put_contents( $dir . '/screenshot.png', 'PNG' ); // phpcs:ignore + } + + return $attachment_id; + } + + /** + * Test teacher_mode is off by default (no activation script injected). + */ + public function test_teacher_mode_off_by_default() { + $attachment_id = $this->create_previewable_attachment( str_repeat( '1', 40 ) ); + + $result = $this->shortcodes->display_exelearning( array( 'id' => $attachment_id ) ); + + $this->assertStringContainsString( 'assertStringNotContainsString( 'mode-teacher', $result ); + $this->assertStringNotContainsString( 'exeTeacherMode', $result ); + } + + /** + * Test teacher_mode="1" injects the teacher-mode activation script. + */ + public function test_teacher_mode_activation() { + $attachment_id = $this->create_previewable_attachment( str_repeat( '2', 40 ) ); + + $result = $this->shortcodes->display_exelearning( + array( + 'id' => $attachment_id, + 'teacher_mode' => '1', + ) + ); + + $this->assertStringContainsString( 'assertStringContainsString( 'mode-teacher', $result ); + $this->assertStringContainsString( 'exeTeacherMode', $result ); + } + + /** + * Test screenshot="only" renders just the image and no iframe. + */ + public function test_screenshot_only_renders_image_without_iframe() { + $hash = str_repeat( '3', 40 ); + $attachment_id = $this->create_previewable_attachment( $hash, true ); + + $result = $this->shortcodes->display_exelearning( + array( + 'id' => $attachment_id, + 'screenshot' => 'only', + ) + ); + + $this->assertStringContainsString( 'assertStringContainsString( 'screenshot.png', $result ); + $this->assertStringContainsString( $hash, $result ); + $this->assertStringNotContainsString( 'create_previewable_attachment( $hash, true ); + + $result = $this->shortcodes->display_exelearning( + array( + 'id' => $attachment_id, + 'screenshot' => 'poster', + ) + ); + + $this->assertStringContainsString( 'screenshot.png', $result ); + $this->assertStringContainsString( 'exelearning-poster', $result ); + // The proxy URL must be available so the embed can load on click. + $this->assertStringContainsString( 'exelearning/v1/content/', $result ); + } + + /** + * Test screenshot="only" falls back to the iframe embed when no screenshot exists. + */ + public function test_screenshot_only_falls_back_without_screenshot() { + $attachment_id = $this->create_previewable_attachment( str_repeat( '5', 40 ) ); + + $result = $this->shortcodes->display_exelearning( + array( + 'id' => $attachment_id, + 'screenshot' => 'only', + ) + ); + + $this->assertStringContainsString( 'assertStringNotContainsString( 'screenshot.png', $result ); + } + + /** + * Test screenshot="poster" falls back to the iframe embed when no screenshot exists. + */ + public function test_screenshot_poster_falls_back_without_screenshot() { + $attachment_id = $this->create_previewable_attachment( str_repeat( '6', 40 ) ); + + $result = $this->shortcodes->display_exelearning( + array( + 'id' => $attachment_id, + 'screenshot' => 'poster', + ) + ); + + $this->assertStringContainsString( 'assertStringNotContainsString( 'exelearning-poster', $result ); + } + + /** + * Test screenshot="no" (default) keeps the current iframe behavior. + */ + public function test_screenshot_no_keeps_iframe() { + $attachment_id = $this->create_previewable_attachment( str_repeat( '7', 40 ), true ); + + $result = $this->shortcodes->display_exelearning( + array( + 'id' => $attachment_id, + 'screenshot' => 'no', + ) + ); + + $this->assertStringContainsString( 'assertStringNotContainsString( 'exelearning-poster', $result ); + } }