diff --git a/.gitignore b/.gitignore index a4dca9c..fb06583 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,7 @@ public/style/workarea/*.css.map # Test results test-results/ test-results/* +playwright-report/ # Built static editor - download from releases or build with `make build-editor` dist/static/ diff --git a/README.md b/README.md index 475eba6..d3ff49f 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,30 @@ Administrators can upload eXeLearning style packages and control which styles th Uploaded ZIPs are validated against path traversal, absolute paths, oversize archives (default 20 MB, filterable via `exelearning_styles_max_zip_size`), and a strict file-extension allow-list. +## External embeds in secure mode + +In secure mode the `.elpx` content runs in a sandboxed, opaque-origin iframe. That +opaque origin propagates to any nested iframe, so cross-origin video players and PDF +viewers render blank. To keep them working, whitelisted video embeds (YouTube and +Vimeo hosts), any cross-origin `https` `.pdf`, and the package's own local PDFs are +*promoted* to the trusted parent page and rendered inline on top of the content. + +Two cooperating scripts make this work: + +- `assets/js/exe-embed-shim.js` runs inside the content iframe, replaces each + promotable iframe with a same-size placeholder, and `postMessage`s its geometry + and URL to the parent. +- `assets/js/exe-embed-relay.js` runs on the host page, validates each reported URL + against the whitelist, rebuilds the canonical player URL, and overlays the real + player exactly over the placeholder. + +A static Firefox end-to-end test exercises the real shim and relay against a +self-contained harness (no WordPress runtime needed): + +```bash +npm run test:e2e:embed +``` + ## Developer hooks The plugin exposes a set of WordPress actions and filters (all prefixed with diff --git a/admin/class-admin-settings.php b/admin/class-admin-settings.php index ac3bb49..549bed2 100644 --- a/admin/class-admin-settings.php +++ b/admin/class-admin-settings.php @@ -70,6 +70,7 @@ public function display_settings_page() {

render_editor_status_section(); ?> + render_security_section(); ?> render_styles_section(); ?> render_content_delivery_section(); ?> render_help_section(); ?> @@ -77,6 +78,65 @@ public function display_settings_page() {

' + . esc_html__( 'Settings saved.', 'exelearning' ) . '

'; + } + } + + $current = ExeLearning_Iframe_Sandbox::mode(); + ?> +
+

+
+ + + + + + + + +
+
+ true. + */ + function buildWhitelist( list ) { + var map = {}; + ( list || [] ).forEach( function ( host ) { + map[ String( host ).toLowerCase() ] = true; + } ); + return map; + } + + /** + * Directory portion of the content iframe src (everything up to the last '/'). + * + * @param {string} src The content iframe src. + * @return {string} The base directory URL, or '' if it cannot be parsed. + */ + function contentDir( src ) { + try { + return new URL( src, window.location.href ).href.replace( /[^/]*([?#].*)?$/, '' ); + } catch ( e ) { + return ''; + } + } + + /** + * Long hex token shared by the content URL and its assets (null when there is + * none, e.g. content URLs that use numeric ids). + * + * @param {string} src The content iframe src. + * @return {?string} The hash, or null if none is found. + */ + function packageId( src ) { + var match = String( src ).match( /[a-f0-9]{12,}/i ); + return match ? match[ 0 ] : null; + } + + /** + * Whether a same-origin URL is one of this package's own extracted files: under + * the content's own directory, or carrying the package hash as a path segment. + * + * @param {URL} url The candidate URL (already same-origin). + * @param {string} contentSrc The content iframe src. + * @return {boolean} True when the URL belongs to this package. + */ + function isSameOriginPackageFile( url, contentSrc ) { + var dir = contentDir( contentSrc ); + if ( dir && 0 === url.href.indexOf( dir ) ) { + return true; + } + var id = packageId( contentSrc ); + return !! ( id && url.pathname.indexOf( '/' + id + '/' ) !== -1 ); + } + + /** + * Whether a host is an IP literal (v4/v6) or a loopback/local name. Such hosts are + * cross-origin to the host yet target the machine/internal network, so they are + * rejected even though SOP would isolate them. + * + * @param {string} host Lowercased URL.hostname. + * @return {boolean} True when the host is an IP or local name. + */ + function isIpOrLocalHost( host ) { + if ( ! host ) { + return true; + } + if ( 'localhost' === host || /\.localhost$/.test( host ) || /\.local$/.test( host ) ) { + return true; + } + if ( '[' === host.charAt( 0 ) || host.indexOf( ':' ) !== -1 ) { + return true; // IPv6 (bracketed). + } + if ( /^\d{1,3}(\.\d{1,3}){3}$/.test( host ) ) { + return true; // Any IPv4 literal. + } + return false; + } + + /** + * Lowercase a hostname and strip a single trailing dot. 'host.example.org.' (the + * FQDN-root form) resolves to the same vhost as 'host.example.org' but compares + * unequal as a raw string, so without this it would slip past the same-origin / + * related-to-host gate below and be promoted as a cross-origin player. + * + * @param {string} host The hostname to normalise. + * @return {string} The lowercased hostname without a trailing dot. + */ + function normalizeHost( host ) { + return ( host || '' ).toLowerCase().replace( /\.$/, '' ); + } + + /** + * Whether a host equals, is a subdomain of, or is a superdomain of the host page's + * host (dotted boundary so 'evil-host.example' does not match 'host.example'). Such + * hosts may share the host page's cookies, so they are rejected. Both sides are + * normalised so the trailing-dot FQDN-root form cannot evade the comparison. + * + * @param {string} host The candidate host. + * @param {string} lmsHost The host page's host. + * @return {boolean} True when the host is related to the host page. + */ + function isRelatedToLms( host, lmsHost ) { + host = normalizeHost( host ); + lmsHost = normalizeHost( lmsHost ); + if ( ! lmsHost ) { + return false; + } + return host === lmsHost || host.endsWith( '.' + lmsHost ) || lmsHost.endsWith( '.' + host ); + } + + /** + * The structural invariant: an https URL cross-origin to the host page and not + * pointing at a sub/superdomain, an IP/loopback/local host, or carrying userinfo. + * This is the only attacker-influenced gate in 'open' mode, and it is what makes + * the sandboxed player's allow-same-origin safe (the embed keeps ITS OWN origin, + * isolated by SOP). + * + * @param {URL} url The candidate URL. + * @return {boolean} True when the URL is a safe cross-origin https embed. + */ + function isCrossOriginHttps( url ) { + if ( 'https:' !== url.protocol ) { + return false; + } + if ( url.username || url.password ) { + return false; + } + if ( url.origin === window.location.origin ) { + return false; + } + var host = normalizeHost( url.hostname ); + if ( isIpOrLocalHost( host ) ) { + return false; + } + var lmshost = ( window.location && window.location.hostname ) ? window.location.hostname : ''; + if ( isRelatedToLms( host, lmshost ) ) { + return false; + } + return true; + } + + /** + * Validate an embed URL. Returns { url, kind ('video'|'pdf'), sameorigin? } or null. + * + * @param {string} raw The reported (absolute) embed URL. + * @param {string} contentSrc The src of the content iframe that reported it. + * @param {Object} opts { strict: boolean, whitelist: Object }. + * @return {?Object} The validated result, or null. + */ + function validate( raw, contentSrc, opts ) { + opts = opts || {}; + var url; + try { + // Parse as an ABSOLUTE URL (the shim always reports absolute). No base: + // a relative/scheme-relative value would otherwise inherit the host origin + // and pass as same-origin -- here it throws and is rejected instead. + url = new URL( raw ); + } catch ( e ) { + return null; + } + if ( url.username || url.password ) { + return null; // Reject userinfo, e.g. https://evil.com@youtube.com/. + } + var host = url.hostname.toLowerCase(); + + // PDFs: any cross-origin https .pdf, or a same-origin file that belongs to this + // package (served as application/pdf + nosniff, never executable HTML). + if ( /\.pdf$/i.test( url.pathname ) ) { + if ( url.origin === window.location.origin ) { + return isSameOriginPackageFile( url, contentSrc ) ? { url: url.href, kind: 'pdf', sameorigin: true } : null; + } + return isCrossOriginHttps( url ) ? { url: url.href, kind: 'pdf' } : null; + } + + // Strict mode: maintained host allowlist + per-provider canonical reconstruction. + if ( opts.strict ) { + var whitelist = opts.whitelist || {}; + if ( whitelist[ host ] && 'https:' === url.protocol ) { + var m; + if ( host.indexOf( 'youtube' ) !== -1 ) { + m = url.pathname.match( /^\/embed\/([A-Za-z0-9_-]{6,})$/ ); + return m ? { url: 'https://www.youtube-nocookie.com/embed/' + m[ 1 ], kind: 'video' } : null; + } + if ( host.indexOf( 'vimeo' ) !== -1 ) { + m = url.pathname.match( /^\/video\/([0-9]+)$/ ); + return m ? { url: 'https://player.vimeo.com/video/' + m[ 1 ], kind: 'video' } : null; + } + if ( host.indexOf( 'dailymotion' ) !== -1 ) { + m = url.pathname.match( /^\/embed\/video\/([A-Za-z0-9]{5,})$/ ); + return m ? { url: 'https://www.dailymotion.com/embed/video/' + m[ 1 ], kind: 'video' } : null; + } + if ( 'mediateca.educa.madrid.org' === host ) { + m = url.pathname.match( /^\/video\/([A-Za-z0-9]{8,})(?:\/fs)?$/ ); + return m ? { url: 'https://mediateca.educa.madrid.org/video/' + m[ 1 ] + '/fs', kind: 'video' } : null; + } + } + return null; + } + + // Open mode (default): any cross-origin https iframe is a video embed. + return isCrossOriginHttps( url ) ? { url: url.href, kind: 'video' } : null; + } + + /** + * Create a SANDBOXED player iframe for a validated embed. The video player gets + * allow-same-origin so the cross-origin provider keeps its own origin and renders, + * while NO allow-top-navigation/allow-modals stops a hostile embed from redirecting + * the host tab or spamming dialogs. The PDF player omits allow-scripts (so any PDF JS + * cannot run) but keeps allow-same-origin so the browser viewer renders. + * + * @param {Object} result { url, kind } from validate(). + * @return {HTMLIFrameElement} The configured player iframe. + */ + function makePlayer( result ) { + var frame = document.createElement( 'iframe' ); + frame.style.cssText = 'position:absolute;border:0;pointer-events:auto;'; + // Mark as a player so it is never mistaken for a content source (message auth). + frame.setAttribute( 'data-exe-embed-player', '1' ); + if ( 'video' === result.kind ) { + frame.setAttribute( 'sandbox', 'allow-scripts allow-same-origin allow-popups allow-forms allow-presentation' ); + frame.setAttribute( 'allow', 'autoplay; encrypted-media; fullscreen; picture-in-picture; clipboard-write' ); + frame.setAttribute( 'allowfullscreen', '' ); + frame.setAttribute( 'referrerpolicy', 'strict-origin-when-cross-origin' ); + } else { + // The browser's built-in PDF viewer does NOT run inside a sandboxed iframe + // (it renders the broken-document icon), so the PDF player is left unsandboxed + // -- unchanged from before DEC-0061, where PDFs were already "any https .pdf". + // A cross-origin PDF is isolated by SOP; the same-origin path is restricted to + // this package's own files; the load guard below still removes a PDF that + // redirects to the host origin. Residual (documented): a server that serves + // HTML at a .pdf path could run scripts here -- pre-existing and low. + frame.setAttribute( 'allow', 'fullscreen' ); + frame.setAttribute( 'referrerpolicy', 'no-referrer' ); + } + frame.src = result.url; + // Tag with the URL it renders so sync() can detect when a reused embed id (the + // shim restarts its counter per page) now points at a different URL. + frame.setAttribute( 'data-exe-embed-src', result.url ); + return frame; + } + + /** + * Create a relay instance. + * + * @param {Object} config { mode: 'open'|'strict', whitelist: string[] }. + * @return {Object} The relay instance. + */ + function createRelay( config ) { + config = config || {}; + var strict = 'strict' === config.mode; + var whitelist = buildWhitelist( config.whitelist ); + var overlays = []; + + function findOverlay( iframe ) { + for ( var i = 0; i < overlays.length; i++ ) { + if ( overlays[ i ].iframe === iframe ) { + return overlays[ i ]; + } + } + return null; + } + + // Resolve the CONTENT iframe a message came from. Promoted players are excluded + // (data-exe-embed-player): a sandboxed player with allow-same-origin could + // otherwise postMessage a forged 'sync' and impersonate a content source. + function frameForSource( source ) { + var frames = document.getElementsByTagName( 'iframe' ); + for ( var i = 0; i < frames.length; i++ ) { + if ( frames[ i ].getAttribute( 'data-exe-embed-player' ) ) { + continue; + } + if ( frames[ i ].contentWindow === source ) { + return frames[ i ]; + } + } + return null; + } + + function overlayFor( iframe ) { + var entry = findOverlay( iframe ); + if ( entry ) { + return entry; + } + var el = document.createElement( 'div' ); + el.className = 'exe-embed-overlay'; + el.style.cssText = 'position:absolute;overflow:hidden;pointer-events:none;z-index:2147483646;'; + document.body.appendChild( el ); + entry = { iframe: iframe, el: el, players: {} }; + overlays.push( entry ); + return entry; + } + + function positionOverlay( entry, rect ) { + rect = rect || entry.iframe.getBoundingClientRect(); + var scrollX = window.pageXOffset || document.documentElement.scrollLeft || 0; + var scrollY = window.pageYOffset || document.documentElement.scrollTop || 0; + entry.el.style.left = ( rect.left + scrollX ) + 'px'; + entry.el.style.top = ( rect.top + scrollY ) + 'px'; + entry.el.style.width = rect.width + 'px'; + entry.el.style.height = rect.height + 'px'; + } + + // D1: if a promoted embed lands SAME-ORIGIN to the host (e.g. a cross-origin URL + // that 30x-redirects to this origin), with allow-same-origin it would become + // scriptable against this page -> remove it. A genuine cross-origin player throws + // on contentWindow.document (expected, kept). Not armed for same-origin package + // PDFs (intentionally same-origin, served as application/pdf). + function armSameOriginGuard( entry, id, player ) { + player.addEventListener( 'load', function () { + try { + if ( player.contentWindow && player.contentWindow.document ) { + if ( player.parentNode ) { + player.parentNode.removeChild( player ); + } + if ( entry.players[ id ] === player ) { + delete entry.players[ id ]; + } + } + } catch ( e ) { /* cross-origin: expected, keep the player */ } + } ); + } + + function sync( entry, embeds, contentSrc ) { + // The content iframe's box is invariant across this sync pass (the loop only + // mutates the overlay and its players), so read it once and reuse it for the + // overlay position and every player clamp -- avoids a forced reflow per embed. + var rect = entry.iframe.getBoundingClientRect(); + positionOverlay( entry, rect ); + var seen = {}; + embeds.forEach( function ( embed ) { + if ( ! embed || typeof embed.id !== 'string' ) { + return; + } + if ( ! isFinite( embed.x ) || ! isFinite( embed.y ) || ! isFinite( embed.w ) || ! isFinite( embed.h ) ) { + return; + } + var result = validate( embed.url, contentSrc, { strict: strict, whitelist: whitelist } ); + if ( ! result ) { + return; + } + seen[ embed.id ] = true; + var player = entry.players[ embed.id ]; + // After the content navigates, the shim reuses ids (exe-embed-1, ...) for + // the new page's embeds. If this id now renders a different URL, drop the + // stale player so the previous page's video does not linger here. + if ( player && player.getAttribute( 'data-exe-embed-src' ) !== result.url ) { + player.parentNode.removeChild( player ); + delete entry.players[ embed.id ]; + player = null; + } + if ( ! player ) { + player = makePlayer( result ); + entry.el.appendChild( player ); + entry.players[ embed.id ] = player; + if ( ! result.sameorigin ) { + armSameOriginGuard( entry, embed.id, player ); + } + } + // Defence in depth against clickjacking: the overlay is clamped to the + // content iframe's box and clips with overflow:hidden, so a player can + // never cover host UI outside the iframe. Cap the player size to the + // overlay too (the content reports geometry, the parent owns rendering). + // Reuses the iframe rect read once at the top of this pass. + player.style.left = embed.x + 'px'; + player.style.top = embed.y + 'px'; + player.style.width = Math.min( embed.w, rect.width ) + 'px'; + player.style.height = Math.min( embed.h, rect.height ) + 'px'; + } ); + Object.keys( entry.players ).forEach( function ( id ) { + if ( ! seen[ id ] ) { + entry.players[ id ].parentNode.removeChild( entry.players[ id ] ); + delete entry.players[ id ]; + } + } ); + } + + function onMessage( event ) { + var data = event.data; + if ( ! data || 'exe-embed' !== data.type || 'sync' !== data.action || ! Array.isArray( data.embeds ) ) { + return; + } + var iframe = frameForSource( event.source ); + if ( ! iframe ) { + return; + } + sync( overlayFor( iframe ), data.embeds, iframe.src ); + } + + // Ask every content iframe to report (covers the case where the shim fired its + // first report before this relay attached its listener). Promoted players are + // excluded so a sandboxed player is never pinged as a content source. + function pingAll() { + var frames = document.getElementsByTagName( 'iframe' ); + for ( var i = 0; i < frames.length; i++ ) { + if ( frames[ i ].getAttribute( 'data-exe-embed-player' ) ) { + continue; + } + try { + frames[ i ].contentWindow.postMessage( { type: 'exe-embed', action: 'request' }, '*' ); + } catch ( e ) { + // Cross-origin player iframes reject this; harmless. + } + } + } + + var scheduled = false; + function scheduleReflow() { + if ( scheduled ) { + return; + } + scheduled = true; + window.requestAnimationFrame( function () { + scheduled = false; + for ( var i = 0; i < overlays.length; i++ ) { + positionOverlay( overlays[ i ] ); + } + } ); + } + + return { + onMessage: onMessage, + sync: sync, + validate: function ( raw, contentSrc ) { + return validate( raw, contentSrc, { strict: strict, whitelist: whitelist } ); + }, + init: function () { + window.addEventListener( 'message', onMessage ); + window.addEventListener( 'resize', scheduleReflow ); + window.addEventListener( 'scroll', scheduleReflow, true ); + window.addEventListener( 'load', pingAll ); + pingAll(); + window.setTimeout( pingAll, 500 ); + return this; + } + }; + } + + var exp = { + buildWhitelist: buildWhitelist, + contentDir: contentDir, + packageId: packageId, + isSameOriginPackageFile: isSameOriginPackageFile, + isIpOrLocalHost: isIpOrLocalHost, + normalizeHost: normalizeHost, + isRelatedToLms: isRelatedToLms, + isCrossOriginHttps: isCrossOriginHttps, + validate: validate, + makePlayer: makePlayer, + createRelay: createRelay + }; + // Test runner (Vitest/Node) consumes module.exports. + if ( typeof module !== 'undefined' && module.exports ) { + module.exports = exp; + } + // Browser bootstrap: expose the factory and helpers, then auto-run a relay from the + // host-injected config (window.ExeEmbedRelayConfig is set before this script loads). + if ( typeof window !== 'undefined' ) { + window.exeEmbedRelay = exp; + createRelay( window.ExeEmbedRelayConfig || {} ).init(); + } +} )(); diff --git a/assets/js/exe-embed-shim.js b/assets/js/exe-embed-shim.js new file mode 100644 index 0000000..e77f280 --- /dev/null +++ b/assets/js/exe-embed-shim.js @@ -0,0 +1,187 @@ +/** + * eXeLearning external-embed shim (runs INSIDE the opaque-origin content iframe). + * + * In secure mode the .elpx HTML runs in a sandboxed, opaque-origin iframe. The + * sandbox origin flag propagates to any nested iframe, so cross-origin players + * (YouTube, Vimeo, ...) lose their own origin and render blank. This shim, injected + * by the content proxy only in secure mode, replaces each cross-origin (https) or + * .pdf ', $this->build_preview_url( $data ), $data['height'], - esc_attr( get_the_title( $data['attachment_id'] ) ) + esc_attr( get_the_title( $data['attachment_id'] ) ), + esc_attr( ExeLearning_Iframe_Sandbox::sandbox_tokens() ) ); return $html; diff --git a/includes/class-iframe-sandbox.php b/includes/class-iframe-sandbox.php new file mode 100644 index 0000000..dbf5cdf --- /dev/null +++ b/includes/class-iframe-sandbox.php @@ -0,0 +1,220 @@ + $mode, + 'whitelist' => self::EMBED_STRICT === $mode ? self::embed_whitelist() : array(), + ) + ) . ';', + 'before' + ); + } +} diff --git a/languages/exelearning-ca.mo b/languages/exelearning-ca.mo index 51a3084..a7d6482 100644 Binary files a/languages/exelearning-ca.mo and b/languages/exelearning-ca.mo differ diff --git a/languages/exelearning-ca.po b/languages/exelearning-ca.po index 8fcc41e..cc73d56 100644 --- a/languages/exelearning-ca.po +++ b/languages/exelearning-ca.po @@ -14,8 +14,7 @@ msgstr "" "X-Domain: exelearning\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: admin/class-admin-settings.php:45 -#: public/views/elp-list.php:63 +#: admin/class-admin-settings.php:45 public/views/elp-list.php:63 #: assets/js/elp-upload.js:401 msgid "Settings" msgstr "Configuració" @@ -24,20 +23,17 @@ msgstr "Configuració" msgid "Date" msgstr "Data" -#: admin/class-admin-settings.php:350 -#: includes/class-elp-list-table.php:109 +#: admin/class-admin-settings.php:350 includes/class-elp-list-table.php:109 msgid "Delete" msgstr "Elimina" -#: admin/class-admin-settings.php:325 -#: admin/class-admin-settings.php:368 +#: admin/class-admin-settings.php:325 admin/class-admin-settings.php:368 #: includes/class-elp-list-table.php:49 msgid "Title" msgstr "Títol" #. Plugin Name of the plugin -#: exelearning.php -#: admin/class-admin-settings.php:57 +#: exelearning.php admin/class-admin-settings.php:57 #: includes/class-mime-types.php:73 #: includes/integrations/class-media-library.php:381 msgid "eXeLearning" @@ -58,8 +54,7 @@ msgstr "Connector per gestionar fitxers .elp d'eXeLearning a WordPress. Puja, ge msgid "INTEF" msgstr "INTEF" -#: admin/class-admin-settings.php:56 -#: admin/class-admin-settings.php:70 +#: admin/class-admin-settings.php:56 admin/class-admin-settings.php:70 msgid "eXeLearning Settings" msgstr "Configuració d'eXeLearning" @@ -120,8 +115,7 @@ msgstr "Utilitza aquest fitxer" msgid "Invalid nonce." msgstr "Nonce no vàlid." -#: admin/class-admin-settings.php:247 -#: admin/class-admin-styles.php:164 +#: admin/class-admin-settings.php:247 admin/class-admin-styles.php:164 #: admin/class-admin-upload.php:41 msgid "Insufficient permissions." msgstr "Permisos insuficients." @@ -171,8 +165,7 @@ msgstr "Ruta de fitxer no vàlida." msgid "File not found." msgstr "Fitxer no trobat." -#: includes/class-content-proxy.php:176 -#: includes/class-content-proxy.php:184 +#: includes/class-content-proxy.php:176 includes/class-content-proxy.php:184 msgid "Access denied." msgstr "Accés denegat." @@ -180,8 +173,7 @@ msgstr "Accés denegat." msgid "This eXeLearning content is a source file and cannot be previewed directly." msgstr "Aquest contingut d'eXeLearning és un fitxer font i no es pot previsualitzar directament." -#: includes/class-elp-upload-block.php:239 -#: public/class-shortcodes.php:190 +#: includes/class-elp-upload-block.php:239 public/class-shortcodes.php:190 msgid "Download file" msgstr "Descarrega el fitxer" @@ -217,8 +209,7 @@ msgstr "No tens permís per editar aquest fitxer." #: includes/class-exelearning-editor.php:208 #: includes/integrations/class-media-library.php:195 #: includes/integrations/class-media-library.php:349 -#: assets/js/elp-upload.js:423 -#: assets/js/elp-upload.js:463 +#: assets/js/elp-upload.js:423 assets/js/elp-upload.js:463 msgid "Edit in eXeLearning" msgstr "Edita a eXeLearning" @@ -232,14 +223,12 @@ msgstr "Tanca" msgid "You do not have permission to read this file." msgstr "No tens permís per llegir aquest fitxer." -#: admin/class-admin-styles.php:42 -#: includes/class-exelearning-rest-api.php:227 +#: admin/class-admin-styles.php:42 includes/class-exelearning-rest-api.php:227 #: includes/class-exelearning-rest-api.php:581 msgid "No file uploaded." msgstr "No s'ha pujat cap fitxer." -#: admin/class-admin-styles.php:50 -#: includes/class-exelearning-rest-api.php:239 +#: admin/class-admin-styles.php:50 includes/class-exelearning-rest-api.php:239 #: includes/class-exelearning-rest-api.php:592 msgid "File upload failed." msgstr "Error en pujar el fitxer." @@ -312,8 +301,7 @@ msgstr "Fitxer eXeLearning no trobat." msgid "This is a source file that cannot be previewed directly. Download it to open with eXeLearning." msgstr "Aquest és un fitxer font que no es pot previsualitzar directament. Descarrega'l per obrir-lo amb eXeLearning." -#: public/class-shortcodes.php:277 -#: public/class-shortcodes.php:321 +#: public/class-shortcodes.php:277 public/class-shortcodes.php:321 msgid "Download source file" msgstr "Descarrega el fitxer font" @@ -338,8 +326,7 @@ msgstr "Tipus:" msgid "Preview in new tab" msgstr "Previsualitza en una pestanya nova" -#: assets/js/elp-upload.js:332 -#: assets/js/elp-upload.js:362 +#: assets/js/elp-upload.js:332 assets/js/elp-upload.js:362 #: assets/js/elp-upload.js:521 msgid "eXeLearning Content" msgstr "Contingut eXeLearning" @@ -418,13 +405,11 @@ msgstr "L'editor integrat és necessari per editar fitxers eXeLearning." msgid "Please install it using the button below." msgstr "Si us plau, instal·leu-lo amb el botó de sota." -#: admin/class-admin-settings.php:557 -#: admin/class-admin-settings.php:580 +#: admin/class-admin-settings.php:557 admin/class-admin-settings.php:580 msgid "Status:" msgstr "Estat:" -#: admin/class-admin-settings.php:328 -#: admin/class-admin-settings.php:558 +#: admin/class-admin-settings.php:328 admin/class-admin-settings.php:558 msgid "Installed" msgstr "Instal·lat" @@ -655,8 +640,7 @@ msgstr "Alçada de la previsualització, en píxels." msgid "When enabled, the teacher layer selector is offered so viewers can reveal teacher content." msgstr "Quan s'activa, s'ofereix el selector de capa docent perquè qui el vegi pugui mostrar el contingut docent." -#: admin/class-admin-settings.php:130 -#: assets/js/elp-upload.js:414 +#: admin/class-admin-settings.php:130 assets/js/elp-upload.js:414 msgid "If disabled, the teacher layer selector is hidden in the embedded eXeLearning content." msgstr "Si es desactiva, el selector de capa docent s'ocultarà al contingut eXeLearning incrustat." @@ -734,20 +718,16 @@ msgstr "Estils penjats" msgid "No uploaded styles yet." msgstr "Encara no hi ha estils penjats." -#: admin/class-admin-settings.php:326 -#: admin/class-admin-settings.php:369 +#: admin/class-admin-settings.php:326 admin/class-admin-settings.php:369 msgid "Id" msgstr "Id" -#: admin/class-admin-settings.php:327 -#: admin/class-admin-settings.php:370 +#: admin/class-admin-settings.php:327 admin/class-admin-settings.php:370 msgid "Version" msgstr "Versió" -#: admin/class-admin-settings.php:329 -#: admin/class-admin-settings.php:345 -#: admin/class-admin-settings.php:371 -#: admin/class-admin-settings.php:386 +#: admin/class-admin-settings.php:329 admin/class-admin-settings.php:345 +#: admin/class-admin-settings.php:371 admin/class-admin-settings.php:386 msgid "Enabled" msgstr "Habilitat" @@ -771,8 +751,7 @@ msgstr "Els estils integrats desactivats s'amaguen de l'editor. Els estils penja msgid "Uploading…" msgstr "S'està penjant…" -#: admin/class-admin-settings.php:445 -#: admin/class-admin-styles.php:71 +#: admin/class-admin-settings.php:445 admin/class-admin-styles.php:71 msgid "Style installed." msgstr "Estil instal·lat." @@ -780,16 +759,13 @@ msgstr "Estil instal·lat." msgid "Upload failed." msgstr "La pujada ha fallat." -#: admin/class-admin-settings.php:231 -#: admin/class-admin-settings.php:451 -#: admin/class-admin-settings.php:473 -#: admin/class-admin-settings.php:494 +#: admin/class-admin-settings.php:231 admin/class-admin-settings.php:451 +#: admin/class-admin-settings.php:473 admin/class-admin-settings.php:494 #: admin/class-admin-settings.php:519 msgid "Network error." msgstr "Error de xarxa." -#: admin/class-admin-settings.php:225 -#: admin/class-admin-settings.php:469 +#: admin/class-admin-settings.php:225 admin/class-admin-settings.php:469 #: admin/class-admin-settings.php:490 msgid "Update failed." msgstr "L'actualització ha fallat." @@ -810,25 +786,21 @@ msgstr "L'eliminació ha fallat." msgid "Uploaded file is not accessible." msgstr "No es pot accedir al fitxer penjat." -#: admin/class-admin-styles.php:87 -#: admin/class-admin-styles.php:106 +#: admin/class-admin-styles.php:87 admin/class-admin-styles.php:106 #: admin/class-admin-styles.php:130 msgid "Missing style id." msgstr "Falta l'identificador de l'estil." -#: admin/class-admin-settings.php:251 -#: admin/class-admin-styles.php:168 +#: admin/class-admin-settings.php:251 admin/class-admin-styles.php:168 msgid "Invalid or missing security token." msgstr "Testimoni de seguretat no vàlid o absent." -#: includes/class-download-button-renderer.php:107 -#: assets/js/elp-upload.js:180 +#: includes/class-download-button-renderer.php:107 assets/js/elp-upload.js:180 msgid "More download formats" msgstr "Més formats de baixada" #: includes/class-download-button-renderer.php:138 -#: includes/class-download-button-renderer.php:209 -#: assets/js/elp-upload.js:79 +#: includes/class-download-button-renderer.php:209 assets/js/elp-upload.js:79 msgid "Install the eXeLearning editor from the plugin settings page to enable this format." msgstr "Instal·la l'editor d'eXeLearning des de la pàgina de configuració del connector per habilitar aquest format." @@ -836,13 +808,11 @@ msgstr "Instal·la l'editor d'eXeLearning des de la pàgina de configuració del msgid "Preparing download…" msgstr "S'està preparant la baixada…" -#: includes/class-download-button-renderer.php:208 -#: assets/js/elp-upload.js:136 +#: includes/class-download-button-renderer.php:208 assets/js/elp-upload.js:136 msgid "Download failed. Please try again." msgstr "La baixada ha fallat. Torneu-ho a provar." -#: includes/class-download-formats.php:31 -#: assets/js/elp-upload.js:36 +#: includes/class-download-formats.php:31 assets/js/elp-upload.js:36 msgid "Download .elpx" msgstr "Baixa .elpx" @@ -948,8 +918,7 @@ msgstr "S'ha rebutjat una entrada d'arxiu insegura durant l'extracció." msgid "Refused path traversal during extraction." msgstr "S'ha rebutjat un salt de ruta durant l'extracció." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:297 includes/class-style-package.php:301 msgid "Failed to create a directory from the archive." msgstr "No s'ha pogut crear un directori de l'arxiu." @@ -969,8 +938,7 @@ msgstr "config.xml no és XML vàlid." msgid "config.xml must declare a element." msgstr "config.xml ha de declarar un element ." -#: includes/class-styles-service.php:244 -#: includes/class-styles-service.php:293 +#: includes/class-styles-service.php:244 includes/class-styles-service.php:293 msgid "Style not found." msgstr "No s'ha trobat l'estil." @@ -1025,3 +993,43 @@ msgstr "Serveix els recursos del paquet a través del proxy de WordPress" #: admin/class-admin-settings.php:191 msgid "Use this option only if your web server returns incorrect MIME types for package assets, for example JavaScript files served as text/plain. When enabled, CSS, JavaScript, fonts, images and other package files are served through WordPress so the plugin can send explicit Content-Type headers. This can reduce performance because requests are handled by PHP instead of being served directly by the web server." msgstr "Utilitzeu aquesta opció només si el vostre servidor web retorna tipus MIME incorrectes per als recursos del paquet, per exemple fitxers JavaScript servits com a text/plain. Quan està activada, els fitxers CSS, JavaScript, tipus de lletra, imatges i altres fitxers del paquet es serveixen a través de WordPress perquè el plugin pugui enviar capçaleres Content-Type explícites. Això pot reduir el rendiment perquè les peticions les gestiona PHP en lloc de servir-se directament des del servidor web." + +#: admin/class-admin-settings.php:104 +msgid "Settings saved." +msgstr "S'han desat els ajustaments." + +#: admin/class-admin-settings.php:111 +msgid "Security" +msgstr "Seguretat" + +#: admin/class-admin-settings.php:117 +msgid "Iframe security mode" +msgstr "Mode de seguretat de l'iframe" + +#: admin/class-admin-settings.php:122 +msgid "Secure (opaque-origin sandbox)" +msgstr "Segur (aïllament d'origen opac)" + +#: admin/class-admin-settings.php:125 +msgid "Legacy (same-origin)" +msgstr "Heretat (mateix origen)" + +#: admin/class-admin-settings.php:129 +msgid "Secure (recommended) isolates embedded content in an opaque-origin iframe. Legacy keeps same-origin behavior, needed only in some environments such as WordPress Playground." +msgstr "El mode segur (recomanat) aïlla el contingut incrustat en un iframe d'origen opac. El mode heretat manté el mateix origen, necessari només en alguns entorns com ara WordPress Playground." + +#: admin/class-admin-settings.php:134 +msgid "Save changes" +msgstr "Desa els canvis" + +#: admin/class-admin-settings.php:185 +msgid "When enabled, the content loads with teacher mode active." +msgstr "Quan s'activa, el contingut es carrega amb el mode docent actiu." + +#: admin/class-admin-settings.php:190 +msgid "Whether the teacher-mode toggle button is shown." +msgstr "Si es mostra el botó per commutar el mode docent." + +#: assets/js/elp-upload.js:413 +msgid "Show Teacher Mode toggler" +msgstr "Mostra el commutador de Mode Professor" diff --git a/languages/exelearning-ca_valencia.mo b/languages/exelearning-ca_valencia.mo index 1d89c86..3854597 100644 Binary files a/languages/exelearning-ca_valencia.mo and b/languages/exelearning-ca_valencia.mo differ diff --git a/languages/exelearning-ca_valencia.po b/languages/exelearning-ca_valencia.po index deb0073..c010f4d 100644 --- a/languages/exelearning-ca_valencia.po +++ b/languages/exelearning-ca_valencia.po @@ -14,8 +14,7 @@ msgstr "" "X-Domain: exelearning\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: admin/class-admin-settings.php:45 -#: public/views/elp-list.php:63 +#: admin/class-admin-settings.php:45 public/views/elp-list.php:63 #: assets/js/elp-upload.js:401 msgid "Settings" msgstr "Configuració" @@ -24,20 +23,17 @@ msgstr "Configuració" msgid "Date" msgstr "Data" -#: admin/class-admin-settings.php:350 -#: includes/class-elp-list-table.php:109 +#: admin/class-admin-settings.php:350 includes/class-elp-list-table.php:109 msgid "Delete" msgstr "Eliminar" -#: admin/class-admin-settings.php:325 -#: admin/class-admin-settings.php:368 +#: admin/class-admin-settings.php:325 admin/class-admin-settings.php:368 #: includes/class-elp-list-table.php:49 msgid "Title" msgstr "Títol" #. Plugin Name of the plugin -#: exelearning.php -#: admin/class-admin-settings.php:57 +#: exelearning.php admin/class-admin-settings.php:57 #: includes/class-mime-types.php:73 #: includes/integrations/class-media-library.php:381 msgid "eXeLearning" @@ -58,8 +54,7 @@ msgstr "Connector per a gestionar fitxers .elp d'eXeLearning en WordPress. Puja, msgid "INTEF" msgstr "INTEF" -#: admin/class-admin-settings.php:56 -#: admin/class-admin-settings.php:70 +#: admin/class-admin-settings.php:56 admin/class-admin-settings.php:70 msgid "eXeLearning Settings" msgstr "Configuració d'eXeLearning" @@ -120,8 +115,7 @@ msgstr "Utilitzar este fitxer" msgid "Invalid nonce." msgstr "Nonce no vàlid." -#: admin/class-admin-settings.php:247 -#: admin/class-admin-styles.php:164 +#: admin/class-admin-settings.php:247 admin/class-admin-styles.php:164 #: admin/class-admin-upload.php:41 msgid "Insufficient permissions." msgstr "Permisos insuficients." @@ -171,8 +165,7 @@ msgstr "Ruta de fitxer no vàlida." msgid "File not found." msgstr "Fitxer no trobat." -#: includes/class-content-proxy.php:176 -#: includes/class-content-proxy.php:184 +#: includes/class-content-proxy.php:176 includes/class-content-proxy.php:184 msgid "Access denied." msgstr "Accés denegat." @@ -180,8 +173,7 @@ msgstr "Accés denegat." msgid "This eXeLearning content is a source file and cannot be previewed directly." msgstr "Este contingut d'eXeLearning és un fitxer font i no es pot previsualitzar directament." -#: includes/class-elp-upload-block.php:239 -#: public/class-shortcodes.php:190 +#: includes/class-elp-upload-block.php:239 public/class-shortcodes.php:190 msgid "Download file" msgstr "Descarregar el fitxer" @@ -217,8 +209,7 @@ msgstr "No tens permís per a editar este fitxer." #: includes/class-exelearning-editor.php:208 #: includes/integrations/class-media-library.php:195 #: includes/integrations/class-media-library.php:349 -#: assets/js/elp-upload.js:423 -#: assets/js/elp-upload.js:463 +#: assets/js/elp-upload.js:423 assets/js/elp-upload.js:463 msgid "Edit in eXeLearning" msgstr "Editar en eXeLearning" @@ -232,14 +223,12 @@ msgstr "Tancar" msgid "You do not have permission to read this file." msgstr "No tens permís per a llegir este fitxer." -#: admin/class-admin-styles.php:42 -#: includes/class-exelearning-rest-api.php:227 +#: admin/class-admin-styles.php:42 includes/class-exelearning-rest-api.php:227 #: includes/class-exelearning-rest-api.php:581 msgid "No file uploaded." msgstr "No s'ha pujat cap fitxer." -#: admin/class-admin-styles.php:50 -#: includes/class-exelearning-rest-api.php:239 +#: admin/class-admin-styles.php:50 includes/class-exelearning-rest-api.php:239 #: includes/class-exelearning-rest-api.php:592 msgid "File upload failed." msgstr "Error en pujar el fitxer." @@ -312,8 +301,7 @@ msgstr "Fitxer eXeLearning no trobat." msgid "This is a source file that cannot be previewed directly. Download it to open with eXeLearning." msgstr "Este és un fitxer font que no es pot previsualitzar directament. Descarrega'l per a obrir-lo amb eXeLearning." -#: public/class-shortcodes.php:277 -#: public/class-shortcodes.php:321 +#: public/class-shortcodes.php:277 public/class-shortcodes.php:321 msgid "Download source file" msgstr "Descarregar el fitxer font" @@ -338,8 +326,7 @@ msgstr "Tipus:" msgid "Preview in new tab" msgstr "Previsualitzar en una pestanya nova" -#: assets/js/elp-upload.js:332 -#: assets/js/elp-upload.js:362 +#: assets/js/elp-upload.js:332 assets/js/elp-upload.js:362 #: assets/js/elp-upload.js:521 msgid "eXeLearning Content" msgstr "Contingut eXeLearning" @@ -418,13 +405,11 @@ msgstr "L'editor integrat és necessari per a editar fitxers eXeLearning." msgid "Please install it using the button below." msgstr "Per favor, instal·leu-lo amb el botó de baix." -#: admin/class-admin-settings.php:557 -#: admin/class-admin-settings.php:580 +#: admin/class-admin-settings.php:557 admin/class-admin-settings.php:580 msgid "Status:" msgstr "Estat:" -#: admin/class-admin-settings.php:328 -#: admin/class-admin-settings.php:558 +#: admin/class-admin-settings.php:328 admin/class-admin-settings.php:558 msgid "Installed" msgstr "Instal·lat" @@ -655,8 +640,7 @@ msgstr "Alçada de la previsualització, en píxels." msgid "When enabled, the teacher layer selector is offered so viewers can reveal teacher content." msgstr "Quan s'activa, s'ofereix el selector de capa docent perquè qui el veja puga mostrar el contingut docent." -#: admin/class-admin-settings.php:130 -#: assets/js/elp-upload.js:414 +#: admin/class-admin-settings.php:130 assets/js/elp-upload.js:414 msgid "If disabled, the teacher layer selector is hidden in the embedded eXeLearning content." msgstr "Si es desactiva, el selector de capa docent s'ocultarà al contingut eXeLearning incrustat." @@ -734,20 +718,16 @@ msgstr "Estils penjats" msgid "No uploaded styles yet." msgstr "Encara no hi ha estils penjats." -#: admin/class-admin-settings.php:326 -#: admin/class-admin-settings.php:369 +#: admin/class-admin-settings.php:326 admin/class-admin-settings.php:369 msgid "Id" msgstr "Id" -#: admin/class-admin-settings.php:327 -#: admin/class-admin-settings.php:370 +#: admin/class-admin-settings.php:327 admin/class-admin-settings.php:370 msgid "Version" msgstr "Versió" -#: admin/class-admin-settings.php:329 -#: admin/class-admin-settings.php:345 -#: admin/class-admin-settings.php:371 -#: admin/class-admin-settings.php:386 +#: admin/class-admin-settings.php:329 admin/class-admin-settings.php:345 +#: admin/class-admin-settings.php:371 admin/class-admin-settings.php:386 msgid "Enabled" msgstr "Habilitat" @@ -771,8 +751,7 @@ msgstr "Els estils integrats desactivats s'amaguen de l'editor. Els estils penja msgid "Uploading…" msgstr "S'està penjant…" -#: admin/class-admin-settings.php:445 -#: admin/class-admin-styles.php:71 +#: admin/class-admin-settings.php:445 admin/class-admin-styles.php:71 msgid "Style installed." msgstr "Estil instal·lat." @@ -780,16 +759,13 @@ msgstr "Estil instal·lat." msgid "Upload failed." msgstr "La pujada ha fallat." -#: admin/class-admin-settings.php:231 -#: admin/class-admin-settings.php:451 -#: admin/class-admin-settings.php:473 -#: admin/class-admin-settings.php:494 +#: admin/class-admin-settings.php:231 admin/class-admin-settings.php:451 +#: admin/class-admin-settings.php:473 admin/class-admin-settings.php:494 #: admin/class-admin-settings.php:519 msgid "Network error." msgstr "Error de xarxa." -#: admin/class-admin-settings.php:225 -#: admin/class-admin-settings.php:469 +#: admin/class-admin-settings.php:225 admin/class-admin-settings.php:469 #: admin/class-admin-settings.php:490 msgid "Update failed." msgstr "L'actualització ha fallat." @@ -810,25 +786,21 @@ msgstr "L'eliminació ha fallat." msgid "Uploaded file is not accessible." msgstr "No es pot accedir al fitxer penjat." -#: admin/class-admin-styles.php:87 -#: admin/class-admin-styles.php:106 +#: admin/class-admin-styles.php:87 admin/class-admin-styles.php:106 #: admin/class-admin-styles.php:130 msgid "Missing style id." msgstr "Falta l'identificador de l'estil." -#: admin/class-admin-settings.php:251 -#: admin/class-admin-styles.php:168 +#: admin/class-admin-settings.php:251 admin/class-admin-styles.php:168 msgid "Invalid or missing security token." msgstr "Testimoni de seguretat no vàlid o absent." -#: includes/class-download-button-renderer.php:107 -#: assets/js/elp-upload.js:180 +#: includes/class-download-button-renderer.php:107 assets/js/elp-upload.js:180 msgid "More download formats" msgstr "Més formats de baixada" #: includes/class-download-button-renderer.php:138 -#: includes/class-download-button-renderer.php:209 -#: assets/js/elp-upload.js:79 +#: includes/class-download-button-renderer.php:209 assets/js/elp-upload.js:79 msgid "Install the eXeLearning editor from the plugin settings page to enable this format." msgstr "Instal·la l'editor d'eXeLearning des de la pàgina de configuració del connector per habilitar aquest format." @@ -836,13 +808,11 @@ msgstr "Instal·la l'editor d'eXeLearning des de la pàgina de configuració del msgid "Preparing download…" msgstr "S'està preparant la baixada…" -#: includes/class-download-button-renderer.php:208 -#: assets/js/elp-upload.js:136 +#: includes/class-download-button-renderer.php:208 assets/js/elp-upload.js:136 msgid "Download failed. Please try again." msgstr "La baixada ha fallat. Torneu-ho a provar." -#: includes/class-download-formats.php:31 -#: assets/js/elp-upload.js:36 +#: includes/class-download-formats.php:31 assets/js/elp-upload.js:36 msgid "Download .elpx" msgstr "Baixa .elpx" @@ -948,8 +918,7 @@ msgstr "S'ha rebutjat una entrada d'arxiu insegura durant l'extracció." msgid "Refused path traversal during extraction." msgstr "S'ha rebutjat un salt de ruta durant l'extracció." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:297 includes/class-style-package.php:301 msgid "Failed to create a directory from the archive." msgstr "No s'ha pogut crear un directori de l'arxiu." @@ -969,8 +938,7 @@ msgstr "config.xml no és XML vàlid." msgid "config.xml must declare a element." msgstr "config.xml ha de declarar un element ." -#: includes/class-styles-service.php:244 -#: includes/class-styles-service.php:293 +#: includes/class-styles-service.php:244 includes/class-styles-service.php:293 msgid "Style not found." msgstr "No s'ha trobat l'estil." @@ -1025,3 +993,43 @@ msgstr "Servix els recursos del paquet a través del proxy de WordPress" #: admin/class-admin-settings.php:191 msgid "Use this option only if your web server returns incorrect MIME types for package assets, for example JavaScript files served as text/plain. When enabled, CSS, JavaScript, fonts, images and other package files are served through WordPress so the plugin can send explicit Content-Type headers. This can reduce performance because requests are handled by PHP instead of being served directly by the web server." msgstr "Utilitzeu esta opció només si el vostre servidor web torna tipus MIME incorrectes per als recursos del paquet, per exemple fitxers JavaScript servits com a text/plain. Quan està activada, els fitxers CSS, JavaScript, tipus de lletra, imatges i altres fitxers del paquet es servixen a través de WordPress perquè el plugin puga enviar capçaleres Content-Type explícites. Açò pot reduir el rendiment perquè les peticions les gestiona PHP en lloc de servir-se directament des del servidor web." + +#: admin/class-admin-settings.php:104 +msgid "Settings saved." +msgstr "S'han guardat els ajustos." + +#: admin/class-admin-settings.php:111 +msgid "Security" +msgstr "Seguretat" + +#: admin/class-admin-settings.php:117 +msgid "Iframe security mode" +msgstr "Mode de seguretat de l'iframe" + +#: admin/class-admin-settings.php:122 +msgid "Secure (opaque-origin sandbox)" +msgstr "Segur (aïllament d'origen opac)" + +#: admin/class-admin-settings.php:125 +msgid "Legacy (same-origin)" +msgstr "Heretat (mateix origen)" + +#: admin/class-admin-settings.php:129 +msgid "Secure (recommended) isolates embedded content in an opaque-origin iframe. Legacy keeps same-origin behavior, needed only in some environments such as WordPress Playground." +msgstr "El mode segur (recomanat) aïlla el contingut incrustat en un iframe d'origen opac. El mode heretat manté el mateix origen, necessari només en alguns entorns com ara WordPress Playground." + +#: admin/class-admin-settings.php:134 +msgid "Save changes" +msgstr "Guarda els canvis" + +#: admin/class-admin-settings.php:185 +msgid "When enabled, the content loads with teacher mode active." +msgstr "Quan s'activa, el contingut es carrega amb el mode docent actiu." + +#: admin/class-admin-settings.php:190 +msgid "Whether the teacher-mode toggle button is shown." +msgstr "Si es mostra el botó per commutar el mode docent." + +#: assets/js/elp-upload.js:413 +msgid "Show Teacher Mode toggler" +msgstr "Mostrar el commutador de Mode Professor" diff --git a/languages/exelearning-de_DE.mo b/languages/exelearning-de_DE.mo index fd16002..a1558a5 100644 Binary files a/languages/exelearning-de_DE.mo and b/languages/exelearning-de_DE.mo differ diff --git a/languages/exelearning-de_DE.po b/languages/exelearning-de_DE.po index 9c9efe8..208e582 100644 --- a/languages/exelearning-de_DE.po +++ b/languages/exelearning-de_DE.po @@ -14,8 +14,7 @@ msgstr "" "X-Domain: exelearning\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: admin/class-admin-settings.php:45 -#: public/views/elp-list.php:63 +#: admin/class-admin-settings.php:45 public/views/elp-list.php:63 #: assets/js/elp-upload.js:401 msgid "Settings" msgstr "Einstellungen" @@ -24,20 +23,17 @@ msgstr "Einstellungen" msgid "Date" msgstr "Datum" -#: admin/class-admin-settings.php:350 -#: includes/class-elp-list-table.php:109 +#: admin/class-admin-settings.php:350 includes/class-elp-list-table.php:109 msgid "Delete" msgstr "Löschen" -#: admin/class-admin-settings.php:325 -#: admin/class-admin-settings.php:368 +#: admin/class-admin-settings.php:325 admin/class-admin-settings.php:368 #: includes/class-elp-list-table.php:49 msgid "Title" msgstr "Titel" #. Plugin Name of the plugin -#: exelearning.php -#: admin/class-admin-settings.php:57 +#: exelearning.php admin/class-admin-settings.php:57 #: includes/class-mime-types.php:73 #: includes/integrations/class-media-library.php:381 msgid "eXeLearning" @@ -58,8 +54,7 @@ msgstr "Plugin zur Unterstützung von eXeLearning .elp-Dateien in WordPress. Hoc msgid "INTEF" msgstr "INTEF" -#: admin/class-admin-settings.php:56 -#: admin/class-admin-settings.php:70 +#: admin/class-admin-settings.php:56 admin/class-admin-settings.php:70 msgid "eXeLearning Settings" msgstr "eXeLearning-Einstellungen" @@ -120,8 +115,7 @@ msgstr "Diese Datei verwenden" msgid "Invalid nonce." msgstr "Ungültiger Nonce." -#: admin/class-admin-settings.php:247 -#: admin/class-admin-styles.php:164 +#: admin/class-admin-settings.php:247 admin/class-admin-styles.php:164 #: admin/class-admin-upload.php:41 msgid "Insufficient permissions." msgstr "Unzureichende Berechtigungen." @@ -171,8 +165,7 @@ msgstr "Ungültiger Dateipfad." msgid "File not found." msgstr "Datei nicht gefunden." -#: includes/class-content-proxy.php:176 -#: includes/class-content-proxy.php:184 +#: includes/class-content-proxy.php:176 includes/class-content-proxy.php:184 msgid "Access denied." msgstr "Zugriff verweigert." @@ -180,8 +173,7 @@ msgstr "Zugriff verweigert." msgid "This eXeLearning content is a source file and cannot be previewed directly." msgstr "Dieser eXeLearning-Inhalt ist eine Quelldatei und kann nicht direkt in der Vorschau angezeigt werden." -#: includes/class-elp-upload-block.php:239 -#: public/class-shortcodes.php:190 +#: includes/class-elp-upload-block.php:239 public/class-shortcodes.php:190 msgid "Download file" msgstr "Datei herunterladen" @@ -217,8 +209,7 @@ msgstr "Sie haben keine Berechtigung, diese Datei zu bearbeiten." #: includes/class-exelearning-editor.php:208 #: includes/integrations/class-media-library.php:195 #: includes/integrations/class-media-library.php:349 -#: assets/js/elp-upload.js:423 -#: assets/js/elp-upload.js:463 +#: assets/js/elp-upload.js:423 assets/js/elp-upload.js:463 msgid "Edit in eXeLearning" msgstr "In eXeLearning bearbeiten" @@ -232,14 +223,12 @@ msgstr "Schließen" msgid "You do not have permission to read this file." msgstr "Sie haben keine Berechtigung, diese Datei zu lesen." -#: admin/class-admin-styles.php:42 -#: includes/class-exelearning-rest-api.php:227 +#: admin/class-admin-styles.php:42 includes/class-exelearning-rest-api.php:227 #: includes/class-exelearning-rest-api.php:581 msgid "No file uploaded." msgstr "Keine Datei hochgeladen." -#: admin/class-admin-styles.php:50 -#: includes/class-exelearning-rest-api.php:239 +#: admin/class-admin-styles.php:50 includes/class-exelearning-rest-api.php:239 #: includes/class-exelearning-rest-api.php:592 msgid "File upload failed." msgstr "Datei-Upload fehlgeschlagen." @@ -312,8 +301,7 @@ msgstr "eXeLearning-Datei nicht gefunden." msgid "This is a source file that cannot be previewed directly. Download it to open with eXeLearning." msgstr "Dies ist eine Quelldatei, die nicht direkt in der Vorschau angezeigt werden kann. Laden Sie sie herunter, um sie mit eXeLearning zu öffnen." -#: public/class-shortcodes.php:277 -#: public/class-shortcodes.php:321 +#: public/class-shortcodes.php:277 public/class-shortcodes.php:321 msgid "Download source file" msgstr "Quelldatei herunterladen" @@ -338,8 +326,7 @@ msgstr "Typ:" msgid "Preview in new tab" msgstr "Vorschau in neuem Tab" -#: assets/js/elp-upload.js:332 -#: assets/js/elp-upload.js:362 +#: assets/js/elp-upload.js:332 assets/js/elp-upload.js:362 #: assets/js/elp-upload.js:521 msgid "eXeLearning Content" msgstr "eXeLearning-Inhalt" @@ -418,13 +405,11 @@ msgstr "Der eingebettete Editor wird zum Bearbeiten von eXeLearning-Dateien ben msgid "Please install it using the button below." msgstr "Bitte installieren Sie ihn über die Schaltfläche unten." -#: admin/class-admin-settings.php:557 -#: admin/class-admin-settings.php:580 +#: admin/class-admin-settings.php:557 admin/class-admin-settings.php:580 msgid "Status:" msgstr "Status:" -#: admin/class-admin-settings.php:328 -#: admin/class-admin-settings.php:558 +#: admin/class-admin-settings.php:328 admin/class-admin-settings.php:558 msgid "Installed" msgstr "Installiert" @@ -655,8 +640,7 @@ msgstr "Höhe der Vorschau in Pixeln." msgid "When enabled, the teacher layer selector is offered so viewers can reveal teacher content." msgstr "Wenn aktiviert, wird die Lehrerebenen-Auswahl angeboten, damit Betrachter Lehrinhalte einblenden können." -#: admin/class-admin-settings.php:130 -#: assets/js/elp-upload.js:414 +#: admin/class-admin-settings.php:130 assets/js/elp-upload.js:414 msgid "If disabled, the teacher layer selector is hidden in the embedded eXeLearning content." msgstr "Wenn deaktiviert, wird die Lehrerebenen-Auswahl im eingebetteten eXeLearning-Inhalt ausgeblendet." @@ -734,20 +718,16 @@ msgstr "Hochgeladene Stile" msgid "No uploaded styles yet." msgstr "Noch keine hochgeladenen Stile." -#: admin/class-admin-settings.php:326 -#: admin/class-admin-settings.php:369 +#: admin/class-admin-settings.php:326 admin/class-admin-settings.php:369 msgid "Id" msgstr "ID" -#: admin/class-admin-settings.php:327 -#: admin/class-admin-settings.php:370 +#: admin/class-admin-settings.php:327 admin/class-admin-settings.php:370 msgid "Version" msgstr "Version" -#: admin/class-admin-settings.php:329 -#: admin/class-admin-settings.php:345 -#: admin/class-admin-settings.php:371 -#: admin/class-admin-settings.php:386 +#: admin/class-admin-settings.php:329 admin/class-admin-settings.php:345 +#: admin/class-admin-settings.php:371 admin/class-admin-settings.php:386 msgid "Enabled" msgstr "Aktiviert" @@ -771,8 +751,7 @@ msgstr "Deaktivierte integrierte Stile werden im Editor ausgeblendet. Hochgelade msgid "Uploading…" msgstr "Wird hochgeladen…" -#: admin/class-admin-settings.php:445 -#: admin/class-admin-styles.php:71 +#: admin/class-admin-settings.php:445 admin/class-admin-styles.php:71 msgid "Style installed." msgstr "Stil installiert." @@ -780,16 +759,13 @@ msgstr "Stil installiert." msgid "Upload failed." msgstr "Hochladen fehlgeschlagen." -#: admin/class-admin-settings.php:231 -#: admin/class-admin-settings.php:451 -#: admin/class-admin-settings.php:473 -#: admin/class-admin-settings.php:494 +#: admin/class-admin-settings.php:231 admin/class-admin-settings.php:451 +#: admin/class-admin-settings.php:473 admin/class-admin-settings.php:494 #: admin/class-admin-settings.php:519 msgid "Network error." msgstr "Netzwerkfehler." -#: admin/class-admin-settings.php:225 -#: admin/class-admin-settings.php:469 +#: admin/class-admin-settings.php:225 admin/class-admin-settings.php:469 #: admin/class-admin-settings.php:490 msgid "Update failed." msgstr "Aktualisierung fehlgeschlagen." @@ -810,25 +786,21 @@ msgstr "Löschen fehlgeschlagen." msgid "Uploaded file is not accessible." msgstr "Auf die hochgeladene Datei kann nicht zugegriffen werden." -#: admin/class-admin-styles.php:87 -#: admin/class-admin-styles.php:106 +#: admin/class-admin-styles.php:87 admin/class-admin-styles.php:106 #: admin/class-admin-styles.php:130 msgid "Missing style id." msgstr "Fehlende Stil-ID." -#: admin/class-admin-settings.php:251 -#: admin/class-admin-styles.php:168 +#: admin/class-admin-settings.php:251 admin/class-admin-styles.php:168 msgid "Invalid or missing security token." msgstr "Ungültiges oder fehlendes Sicherheitstoken." -#: includes/class-download-button-renderer.php:107 -#: assets/js/elp-upload.js:180 +#: includes/class-download-button-renderer.php:107 assets/js/elp-upload.js:180 msgid "More download formats" msgstr "Weitere Download-Formate" #: includes/class-download-button-renderer.php:138 -#: includes/class-download-button-renderer.php:209 -#: assets/js/elp-upload.js:79 +#: includes/class-download-button-renderer.php:209 assets/js/elp-upload.js:79 msgid "Install the eXeLearning editor from the plugin settings page to enable this format." msgstr "Installieren Sie den eXeLearning-Editor über die Plugin-Einstellungsseite, um dieses Format zu aktivieren." @@ -836,13 +808,11 @@ msgstr "Installieren Sie den eXeLearning-Editor über die Plugin-Einstellungssei msgid "Preparing download…" msgstr "Download wird vorbereitet…" -#: includes/class-download-button-renderer.php:208 -#: assets/js/elp-upload.js:136 +#: includes/class-download-button-renderer.php:208 assets/js/elp-upload.js:136 msgid "Download failed. Please try again." msgstr "Download fehlgeschlagen. Bitte versuchen Sie es erneut." -#: includes/class-download-formats.php:31 -#: assets/js/elp-upload.js:36 +#: includes/class-download-formats.php:31 assets/js/elp-upload.js:36 msgid "Download .elpx" msgstr ".elpx herunterladen" @@ -948,8 +918,7 @@ msgstr "Unsicherer Archiveintrag bei der Extraktion abgelehnt." msgid "Refused path traversal during extraction." msgstr "Pfad-Traversal bei der Extraktion abgelehnt." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:297 includes/class-style-package.php:301 msgid "Failed to create a directory from the archive." msgstr "Verzeichnis konnte nicht aus dem Archiv erstellt werden." @@ -969,8 +938,7 @@ msgstr "config.xml ist kein gültiges XML." msgid "config.xml must declare a element." msgstr "config.xml muss ein -Element deklarieren." -#: includes/class-styles-service.php:244 -#: includes/class-styles-service.php:293 +#: includes/class-styles-service.php:244 includes/class-styles-service.php:293 msgid "Style not found." msgstr "Stil nicht gefunden." @@ -1025,3 +993,43 @@ msgstr "Paket-Assets über den WordPress-Proxy ausliefern" #: admin/class-admin-settings.php:191 msgid "Use this option only if your web server returns incorrect MIME types for package assets, for example JavaScript files served as text/plain. When enabled, CSS, JavaScript, fonts, images and other package files are served through WordPress so the plugin can send explicit Content-Type headers. This can reduce performance because requests are handled by PHP instead of being served directly by the web server." msgstr "Verwenden Sie diese Option nur, wenn Ihr Webserver falsche MIME-Typen für Paket-Assets zurückgibt, zum Beispiel JavaScript-Dateien, die als text/plain ausgeliefert werden. Wenn aktiviert, werden CSS-, JavaScript-, Schriftart-, Bild- und andere Paketdateien über WordPress ausgeliefert, sodass das Plugin explizite Content-Type-Header senden kann. Dies kann die Leistung verringern, da die Anfragen von PHP verarbeitet werden, anstatt direkt vom Webserver ausgeliefert zu werden." + +#: admin/class-admin-settings.php:104 +msgid "Settings saved." +msgstr "Einstellungen gespeichert." + +#: admin/class-admin-settings.php:111 +msgid "Security" +msgstr "Sicherheit" + +#: admin/class-admin-settings.php:117 +msgid "Iframe security mode" +msgstr "Iframe-Sicherheitsmodus" + +#: admin/class-admin-settings.php:122 +msgid "Secure (opaque-origin sandbox)" +msgstr "Sicher (Sandbox mit undurchsichtigem Ursprung)" + +#: admin/class-admin-settings.php:125 +msgid "Legacy (same-origin)" +msgstr "Legacy (gleicher Ursprung)" + +#: admin/class-admin-settings.php:129 +msgid "Secure (recommended) isolates embedded content in an opaque-origin iframe. Legacy keeps same-origin behavior, needed only in some environments such as WordPress Playground." +msgstr "Der sichere Modus (empfohlen) isoliert eingebettete Inhalte in einem iframe mit undurchsichtigem Ursprung. Der Legacy-Modus behält denselben Ursprung bei und wird nur in einigen Umgebungen wie WordPress Playground benötigt." + +#: admin/class-admin-settings.php:134 +msgid "Save changes" +msgstr "Änderungen speichern" + +#: admin/class-admin-settings.php:185 +msgid "When enabled, the content loads with teacher mode active." +msgstr "Wenn aktiviert, wird der Inhalt mit aktivem Lehrermodus geladen." + +#: admin/class-admin-settings.php:190 +msgid "Whether the teacher-mode toggle button is shown." +msgstr "Legt fest, ob die Umschaltfläche für den Lehrermodus angezeigt wird." + +#: assets/js/elp-upload.js:413 +msgid "Show Teacher Mode toggler" +msgstr "Umschalter für Lehrermodus anzeigen" diff --git a/languages/exelearning-eo.mo b/languages/exelearning-eo.mo index c989fcd..baa12b1 100644 Binary files a/languages/exelearning-eo.mo and b/languages/exelearning-eo.mo differ diff --git a/languages/exelearning-eo.po b/languages/exelearning-eo.po index 2eebdfe..cb3a029 100644 --- a/languages/exelearning-eo.po +++ b/languages/exelearning-eo.po @@ -14,8 +14,7 @@ msgstr "" "X-Domain: exelearning\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: admin/class-admin-settings.php:45 -#: public/views/elp-list.php:63 +#: admin/class-admin-settings.php:45 public/views/elp-list.php:63 #: assets/js/elp-upload.js:401 msgid "Settings" msgstr "Agordoj" @@ -24,20 +23,17 @@ msgstr "Agordoj" msgid "Date" msgstr "Dato" -#: admin/class-admin-settings.php:350 -#: includes/class-elp-list-table.php:109 +#: admin/class-admin-settings.php:350 includes/class-elp-list-table.php:109 msgid "Delete" msgstr "Forigi" -#: admin/class-admin-settings.php:325 -#: admin/class-admin-settings.php:368 +#: admin/class-admin-settings.php:325 admin/class-admin-settings.php:368 #: includes/class-elp-list-table.php:49 msgid "Title" msgstr "Titolo" #. Plugin Name of the plugin -#: exelearning.php -#: admin/class-admin-settings.php:57 +#: exelearning.php admin/class-admin-settings.php:57 #: includes/class-mime-types.php:73 #: includes/integrations/class-media-library.php:381 msgid "eXeLearning" @@ -58,8 +54,7 @@ msgstr "Kromprogramo por subteni eXeLearning .elp-dosierojn en WordPress. Alŝut msgid "INTEF" msgstr "INTEF" -#: admin/class-admin-settings.php:56 -#: admin/class-admin-settings.php:70 +#: admin/class-admin-settings.php:56 admin/class-admin-settings.php:70 msgid "eXeLearning Settings" msgstr "Agordoj de eXeLearning" @@ -120,8 +115,7 @@ msgstr "Uzi ĉi tiun dosieron" msgid "Invalid nonce." msgstr "Nevalida nonce." -#: admin/class-admin-settings.php:247 -#: admin/class-admin-styles.php:164 +#: admin/class-admin-settings.php:247 admin/class-admin-styles.php:164 #: admin/class-admin-upload.php:41 msgid "Insufficient permissions." msgstr "Nesufiĉaj permesoj." @@ -171,8 +165,7 @@ msgstr "Nevalida dosiera vojo." msgid "File not found." msgstr "Dosiero ne trovita." -#: includes/class-content-proxy.php:176 -#: includes/class-content-proxy.php:184 +#: includes/class-content-proxy.php:176 includes/class-content-proxy.php:184 msgid "Access denied." msgstr "Aliro rifuzita." @@ -180,8 +173,7 @@ msgstr "Aliro rifuzita." msgid "This eXeLearning content is a source file and cannot be previewed directly." msgstr "Ĉi tiu eXeLearning-enhavo estas fontdosiero kaj ne povas esti antaŭrigardata rekte." -#: includes/class-elp-upload-block.php:239 -#: public/class-shortcodes.php:190 +#: includes/class-elp-upload-block.php:239 public/class-shortcodes.php:190 msgid "Download file" msgstr "Elŝuti dosieron" @@ -217,8 +209,7 @@ msgstr "Vi ne havas permeson redakti ĉi tiun dosieron." #: includes/class-exelearning-editor.php:208 #: includes/integrations/class-media-library.php:195 #: includes/integrations/class-media-library.php:349 -#: assets/js/elp-upload.js:423 -#: assets/js/elp-upload.js:463 +#: assets/js/elp-upload.js:423 assets/js/elp-upload.js:463 msgid "Edit in eXeLearning" msgstr "Redakti en eXeLearning" @@ -232,14 +223,12 @@ msgstr "Fermi" msgid "You do not have permission to read this file." msgstr "Vi ne havas permeson legi ĉi tiun dosieron." -#: admin/class-admin-styles.php:42 -#: includes/class-exelearning-rest-api.php:227 +#: admin/class-admin-styles.php:42 includes/class-exelearning-rest-api.php:227 #: includes/class-exelearning-rest-api.php:581 msgid "No file uploaded." msgstr "Neniu dosiero alŝutita." -#: admin/class-admin-styles.php:50 -#: includes/class-exelearning-rest-api.php:239 +#: admin/class-admin-styles.php:50 includes/class-exelearning-rest-api.php:239 #: includes/class-exelearning-rest-api.php:592 msgid "File upload failed." msgstr "Dosiera alŝuto malsukcesis." @@ -312,8 +301,7 @@ msgstr "eXeLearning-dosiero ne trovita." msgid "This is a source file that cannot be previewed directly. Download it to open with eXeLearning." msgstr "Ĉi tio estas fontdosiero kiu ne povas esti antaŭrigardata rekte. Elŝutu ĝin por malfermi per eXeLearning." -#: public/class-shortcodes.php:277 -#: public/class-shortcodes.php:321 +#: public/class-shortcodes.php:277 public/class-shortcodes.php:321 msgid "Download source file" msgstr "Elŝuti fontdosieron" @@ -338,8 +326,7 @@ msgstr "Tipo:" msgid "Preview in new tab" msgstr "Antaŭrigardo en nova langeto" -#: assets/js/elp-upload.js:332 -#: assets/js/elp-upload.js:362 +#: assets/js/elp-upload.js:332 assets/js/elp-upload.js:362 #: assets/js/elp-upload.js:521 msgid "eXeLearning Content" msgstr "eXeLearning-enhavo" @@ -418,13 +405,11 @@ msgstr "La integra redaktilo estas bezonata por redakti eXeLearning-dosierojn." msgid "Please install it using the button below." msgstr "Bonvolu instali ĝin per la suba butono." -#: admin/class-admin-settings.php:557 -#: admin/class-admin-settings.php:580 +#: admin/class-admin-settings.php:557 admin/class-admin-settings.php:580 msgid "Status:" msgstr "Stato:" -#: admin/class-admin-settings.php:328 -#: admin/class-admin-settings.php:558 +#: admin/class-admin-settings.php:328 admin/class-admin-settings.php:558 msgid "Installed" msgstr "Instalita" @@ -655,8 +640,7 @@ msgstr "Alteco de la antaŭrigardo, en bilderoj." msgid "When enabled, the teacher layer selector is offered so viewers can reveal teacher content." msgstr "Kiam ŝaltita, la elekto de instruista tavolo estas proponata por ke spektantoj povu malkaŝi instruistan enhavon." -#: admin/class-admin-settings.php:130 -#: assets/js/elp-upload.js:414 +#: admin/class-admin-settings.php:130 assets/js/elp-upload.js:414 msgid "If disabled, the teacher layer selector is hidden in the embedded eXeLearning content." msgstr "Se malŝaltita, la elekto de instruista tavolo estas kaŝita en la enkorpigita eXeLearning-enhavo." @@ -734,20 +718,16 @@ msgstr "Alŝutitaj stiloj" msgid "No uploaded styles yet." msgstr "Ankoraŭ neniuj alŝutitaj stiloj." -#: admin/class-admin-settings.php:326 -#: admin/class-admin-settings.php:369 +#: admin/class-admin-settings.php:326 admin/class-admin-settings.php:369 msgid "Id" msgstr "Id" -#: admin/class-admin-settings.php:327 -#: admin/class-admin-settings.php:370 +#: admin/class-admin-settings.php:327 admin/class-admin-settings.php:370 msgid "Version" msgstr "Versio" -#: admin/class-admin-settings.php:329 -#: admin/class-admin-settings.php:345 -#: admin/class-admin-settings.php:371 -#: admin/class-admin-settings.php:386 +#: admin/class-admin-settings.php:329 admin/class-admin-settings.php:345 +#: admin/class-admin-settings.php:371 admin/class-admin-settings.php:386 msgid "Enabled" msgstr "Ŝaltita" @@ -771,8 +751,7 @@ msgstr "Malŝaltitaj enkonstruitaj stiloj estas kaŝitaj de la redaktilo. Alŝut msgid "Uploading…" msgstr "Alŝutante…" -#: admin/class-admin-settings.php:445 -#: admin/class-admin-styles.php:71 +#: admin/class-admin-settings.php:445 admin/class-admin-styles.php:71 msgid "Style installed." msgstr "Stilo instalita." @@ -780,16 +759,13 @@ msgstr "Stilo instalita." msgid "Upload failed." msgstr "Alŝuto malsukcesis." -#: admin/class-admin-settings.php:231 -#: admin/class-admin-settings.php:451 -#: admin/class-admin-settings.php:473 -#: admin/class-admin-settings.php:494 +#: admin/class-admin-settings.php:231 admin/class-admin-settings.php:451 +#: admin/class-admin-settings.php:473 admin/class-admin-settings.php:494 #: admin/class-admin-settings.php:519 msgid "Network error." msgstr "Reta eraro." -#: admin/class-admin-settings.php:225 -#: admin/class-admin-settings.php:469 +#: admin/class-admin-settings.php:225 admin/class-admin-settings.php:469 #: admin/class-admin-settings.php:490 msgid "Update failed." msgstr "Ĝisdatigo malsukcesis." @@ -810,25 +786,21 @@ msgstr "Forigo malsukcesis." msgid "Uploaded file is not accessible." msgstr "La alŝutita dosiero ne estas alirebla." -#: admin/class-admin-styles.php:87 -#: admin/class-admin-styles.php:106 +#: admin/class-admin-styles.php:87 admin/class-admin-styles.php:106 #: admin/class-admin-styles.php:130 msgid "Missing style id." msgstr "Mankas stilo-ID." -#: admin/class-admin-settings.php:251 -#: admin/class-admin-styles.php:168 +#: admin/class-admin-settings.php:251 admin/class-admin-styles.php:168 msgid "Invalid or missing security token." msgstr "Nevalida aŭ mankanta sekureca ĵetono." -#: includes/class-download-button-renderer.php:107 -#: assets/js/elp-upload.js:180 +#: includes/class-download-button-renderer.php:107 assets/js/elp-upload.js:180 msgid "More download formats" msgstr "Pliaj elŝutformatoj" #: includes/class-download-button-renderer.php:138 -#: includes/class-download-button-renderer.php:209 -#: assets/js/elp-upload.js:79 +#: includes/class-download-button-renderer.php:209 assets/js/elp-upload.js:79 msgid "Install the eXeLearning editor from the plugin settings page to enable this format." msgstr "Instalu la eXeLearning-redaktilon el la agordpaĝo de la kromaĵo por ŝalti ĉi tiun formaton." @@ -836,13 +808,11 @@ msgstr "Instalu la eXeLearning-redaktilon el la agordpaĝo de la kromaĵo por ŝ msgid "Preparing download…" msgstr "Preparante elŝuton…" -#: includes/class-download-button-renderer.php:208 -#: assets/js/elp-upload.js:136 +#: includes/class-download-button-renderer.php:208 assets/js/elp-upload.js:136 msgid "Download failed. Please try again." msgstr "Elŝuto malsukcesis. Bonvolu reprovi." -#: includes/class-download-formats.php:31 -#: assets/js/elp-upload.js:36 +#: includes/class-download-formats.php:31 assets/js/elp-upload.js:36 msgid "Download .elpx" msgstr "Elŝuti .elpx" @@ -948,8 +918,7 @@ msgstr "Malsekura arkiverero malakceptita dum la malpakado." msgid "Refused path traversal during extraction." msgstr "Vojtravado malakceptita dum la malpakado." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:297 includes/class-style-package.php:301 msgid "Failed to create a directory from the archive." msgstr "Ne eblis krei dosierujon el la arkivo." @@ -969,8 +938,7 @@ msgstr "config.xml ne estas valida XML." msgid "config.xml must declare a element." msgstr "config.xml devas deklari elementon ." -#: includes/class-styles-service.php:244 -#: includes/class-styles-service.php:293 +#: includes/class-styles-service.php:244 includes/class-styles-service.php:293 msgid "Style not found." msgstr "Stilo ne trovita." @@ -1025,3 +993,43 @@ msgstr "Servi pakaĵajn risurcojn tra la WordPress-prokurilo" #: admin/class-admin-settings.php:191 msgid "Use this option only if your web server returns incorrect MIME types for package assets, for example JavaScript files served as text/plain. When enabled, CSS, JavaScript, fonts, images and other package files are served through WordPress so the plugin can send explicit Content-Type headers. This can reduce performance because requests are handled by PHP instead of being served directly by the web server." msgstr "Uzu ĉi tiun opcion nur se via retservilo redonas malĝustajn MIME-tipojn por pakaĵaj risurcoj, ekzemple JavaScript-dosieroj servataj kiel text/plain. Kiam ĝi estas ŝaltita, CSS-, JavaScript-, tiparaj, bildaj kaj aliaj pakaĵaj dosieroj estas servataj tra WordPress por ke la kromprogramo povu sendi eksplicitajn Content-Type-kapliniojn. Tio povas malpliigi la efikecon ĉar la petoj estas traktataj de PHP anstataŭ esti servataj rekte de la retservilo." + +#: admin/class-admin-settings.php:104 +msgid "Settings saved." +msgstr "Agordoj konservitaj." + +#: admin/class-admin-settings.php:111 +msgid "Security" +msgstr "Sekureco" + +#: admin/class-admin-settings.php:117 +msgid "Iframe security mode" +msgstr "Sekureca reĝimo de iframe" + +#: admin/class-admin-settings.php:122 +msgid "Secure (opaque-origin sandbox)" +msgstr "Sekura (sablujo kun maldiafana origino)" + +#: admin/class-admin-settings.php:125 +msgid "Legacy (same-origin)" +msgstr "Hereda (sama origino)" + +#: admin/class-admin-settings.php:129 +msgid "Secure (recommended) isolates embedded content in an opaque-origin iframe. Legacy keeps same-origin behavior, needed only in some environments such as WordPress Playground." +msgstr "La sekura reĝimo (rekomendita) izolas enkorpigitan enhavon en iframe kun maldiafana origino. La hereda reĝimo konservas la saman originon, bezonata nur en kelkaj medioj kiel WordPress Playground." + +#: admin/class-admin-settings.php:134 +msgid "Save changes" +msgstr "Konservi ŝanĝojn" + +#: admin/class-admin-settings.php:185 +msgid "When enabled, the content loads with teacher mode active." +msgstr "Kiam ŝaltita, la enhavo ŝargiĝas kun la instruista reĝimo aktiva." + +#: admin/class-admin-settings.php:190 +msgid "Whether the teacher-mode toggle button is shown." +msgstr "Ĉu la ŝaltbutono de la instruista reĝimo estas montrata." + +#: assets/js/elp-upload.js:413 +msgid "Show Teacher Mode toggler" +msgstr "Montri la ŝaltilon de Instruista Reĝimo" diff --git a/languages/exelearning-es_ES.mo b/languages/exelearning-es_ES.mo index 1b693e3..ff6c7eb 100644 Binary files a/languages/exelearning-es_ES.mo and b/languages/exelearning-es_ES.mo differ diff --git a/languages/exelearning-es_ES.po b/languages/exelearning-es_ES.po index 3650f9f..3859528 100644 --- a/languages/exelearning-es_ES.po +++ b/languages/exelearning-es_ES.po @@ -15,8 +15,7 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. Plugin Name of the plugin -#: exelearning.php -#: admin/class-admin-settings.php:57 +#: exelearning.php admin/class-admin-settings.php:57 #: includes/class-mime-types.php:73 #: includes/integrations/class-media-library.php:381 msgid "eXeLearning" @@ -42,14 +41,12 @@ msgstr "INTEF" msgid "https://exelearning.net/" msgstr "https://exelearning.net/" -#: admin/class-admin-settings.php:45 -#: public/views/elp-list.php:63 +#: admin/class-admin-settings.php:45 public/views/elp-list.php:63 #: assets/js/elp-upload.js:401 msgid "Settings" msgstr "Ajustes" -#: admin/class-admin-settings.php:56 -#: admin/class-admin-settings.php:70 +#: admin/class-admin-settings.php:56 admin/class-admin-settings.php:70 msgid "eXeLearning Settings" msgstr "Ajustes de eXeLearning" @@ -95,31 +92,25 @@ msgstr "Estilos subidos" msgid "No uploaded styles yet." msgstr "Aún no hay estilos subidos." -#: admin/class-admin-settings.php:325 -#: admin/class-admin-settings.php:368 +#: admin/class-admin-settings.php:325 admin/class-admin-settings.php:368 #: includes/class-elp-list-table.php:49 msgid "Title" msgstr "Título" -#: admin/class-admin-settings.php:326 -#: admin/class-admin-settings.php:369 +#: admin/class-admin-settings.php:326 admin/class-admin-settings.php:369 msgid "Id" msgstr "Id" -#: admin/class-admin-settings.php:327 -#: admin/class-admin-settings.php:370 +#: admin/class-admin-settings.php:327 admin/class-admin-settings.php:370 msgid "Version" msgstr "Versión" -#: admin/class-admin-settings.php:328 -#: admin/class-admin-settings.php:558 +#: admin/class-admin-settings.php:328 admin/class-admin-settings.php:558 msgid "Installed" msgstr "Instalado" -#: admin/class-admin-settings.php:329 -#: admin/class-admin-settings.php:345 -#: admin/class-admin-settings.php:371 -#: admin/class-admin-settings.php:386 +#: admin/class-admin-settings.php:329 admin/class-admin-settings.php:345 +#: admin/class-admin-settings.php:371 admin/class-admin-settings.php:386 msgid "Enabled" msgstr "Habilitado" @@ -127,8 +118,7 @@ msgstr "Habilitado" msgid "Actions" msgstr "Acciones" -#: admin/class-admin-settings.php:350 -#: includes/class-elp-list-table.php:109 +#: admin/class-admin-settings.php:350 includes/class-elp-list-table.php:109 msgid "Delete" msgstr "Eliminar" @@ -148,8 +138,7 @@ msgstr "Los estilos integrados deshabilitados se ocultan del editor. Los estilos msgid "Uploading…" msgstr "Subiendo…" -#: admin/class-admin-settings.php:445 -#: admin/class-admin-styles.php:71 +#: admin/class-admin-settings.php:445 admin/class-admin-styles.php:71 msgid "Style installed." msgstr "Estilo instalado." @@ -157,16 +146,13 @@ msgstr "Estilo instalado." msgid "Upload failed." msgstr "Error al subir." -#: admin/class-admin-settings.php:231 -#: admin/class-admin-settings.php:451 -#: admin/class-admin-settings.php:473 -#: admin/class-admin-settings.php:494 +#: admin/class-admin-settings.php:231 admin/class-admin-settings.php:451 +#: admin/class-admin-settings.php:473 admin/class-admin-settings.php:494 #: admin/class-admin-settings.php:519 msgid "Network error." msgstr "Error de red." -#: admin/class-admin-settings.php:225 -#: admin/class-admin-settings.php:469 +#: admin/class-admin-settings.php:225 admin/class-admin-settings.php:469 #: admin/class-admin-settings.php:490 msgid "Update failed." msgstr "Error al actualizar." @@ -195,8 +181,7 @@ msgstr "El editor integrado es necesario para editar archivos eXeLearning." msgid "Please install it using the button below." msgstr "Por favor, instálalo usando el botón de abajo." -#: admin/class-admin-settings.php:557 -#: admin/class-admin-settings.php:580 +#: admin/class-admin-settings.php:557 admin/class-admin-settings.php:580 msgid "Status:" msgstr "Estado:" @@ -255,14 +240,12 @@ msgstr "La instalación falló." msgid "Network error. Please check your connection and try again." msgstr "Error de red. Por favor, comprueba tu conexión e inténtalo de nuevo." -#: admin/class-admin-styles.php:42 -#: includes/class-exelearning-rest-api.php:227 +#: admin/class-admin-styles.php:42 includes/class-exelearning-rest-api.php:227 #: includes/class-exelearning-rest-api.php:581 msgid "No file uploaded." msgstr "No se subió ningún archivo." -#: admin/class-admin-styles.php:50 -#: includes/class-exelearning-rest-api.php:239 +#: admin/class-admin-styles.php:50 includes/class-exelearning-rest-api.php:239 #: includes/class-exelearning-rest-api.php:592 msgid "File upload failed." msgstr "Error al subir el archivo." @@ -271,20 +254,17 @@ msgstr "Error al subir el archivo." msgid "Uploaded file is not accessible." msgstr "No se puede acceder al archivo subido." -#: admin/class-admin-styles.php:87 -#: admin/class-admin-styles.php:106 +#: admin/class-admin-styles.php:87 admin/class-admin-styles.php:106 #: admin/class-admin-styles.php:130 msgid "Missing style id." msgstr "Falta el identificador del estilo." -#: admin/class-admin-settings.php:247 -#: admin/class-admin-styles.php:164 +#: admin/class-admin-settings.php:247 admin/class-admin-styles.php:164 #: admin/class-admin-upload.php:41 msgid "Insufficient permissions." msgstr "Permisos insuficientes." -#: admin/class-admin-settings.php:251 -#: admin/class-admin-styles.php:168 +#: admin/class-admin-settings.php:251 admin/class-admin-styles.php:168 msgid "Invalid or missing security token." msgstr "Token de seguridad no válido o ausente." @@ -337,19 +317,16 @@ msgstr "Ruta de archivo no válida." msgid "File not found." msgstr "Archivo no encontrado." -#: includes/class-content-proxy.php:176 -#: includes/class-content-proxy.php:184 +#: includes/class-content-proxy.php:176 includes/class-content-proxy.php:184 msgid "Access denied." msgstr "Acceso denegado." -#: includes/class-download-button-renderer.php:107 -#: assets/js/elp-upload.js:180 +#: includes/class-download-button-renderer.php:107 assets/js/elp-upload.js:180 msgid "More download formats" msgstr "Más formatos de descarga" #: includes/class-download-button-renderer.php:138 -#: includes/class-download-button-renderer.php:209 -#: assets/js/elp-upload.js:79 +#: includes/class-download-button-renderer.php:209 assets/js/elp-upload.js:79 msgid "Install the eXeLearning editor from the plugin settings page to enable this format." msgstr "Instala el editor eXeLearning desde la página de ajustes del plugin para habilitar este formato." @@ -357,13 +334,11 @@ msgstr "Instala el editor eXeLearning desde la página de ajustes del plugin par msgid "Preparing download…" msgstr "Preparando la descarga…" -#: includes/class-download-button-renderer.php:208 -#: assets/js/elp-upload.js:136 +#: includes/class-download-button-renderer.php:208 assets/js/elp-upload.js:136 msgid "Download failed. Please try again." msgstr "La descarga ha fallado. Por favor, inténtelo de nuevo." -#: includes/class-download-formats.php:31 -#: assets/js/elp-upload.js:36 +#: includes/class-download-formats.php:31 assets/js/elp-upload.js:36 msgid "Download .elpx" msgstr "Descargar .elpx" @@ -428,8 +403,7 @@ msgstr "Error al crear el directorio para los archivos extraídos." msgid "Error: eXeLearning content not found" msgstr "Error: contenido de eXeLearning no encontrado" -#: includes/class-elp-upload-block.php:239 -#: public/class-shortcodes.php:190 +#: includes/class-elp-upload-block.php:239 public/class-shortcodes.php:190 msgid "Download file" msgstr "Descargar archivo" @@ -469,8 +443,7 @@ msgstr "No tienes permiso para editar este archivo." #: includes/class-exelearning-editor.php:208 #: includes/integrations/class-media-library.php:195 #: includes/integrations/class-media-library.php:349 -#: assets/js/elp-upload.js:423 -#: assets/js/elp-upload.js:463 +#: assets/js/elp-upload.js:423 assets/js/elp-upload.js:463 msgid "Edit in eXeLearning" msgstr "Editar en eXeLearning" @@ -700,8 +673,7 @@ msgstr "Se rechazó una entrada no segura del archivo durante la extracción." msgid "Refused path traversal during extraction." msgstr "Se rechazó un intento de escape de ruta durante la extracción." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:297 includes/class-style-package.php:301 msgid "Failed to create a directory from the archive." msgstr "No se pudo crear un directorio del archivo." @@ -721,8 +693,7 @@ msgstr "config.xml no es XML válido." msgid "config.xml must declare a element." msgstr "config.xml debe declarar un elemento ." -#: includes/class-styles-service.php:244 -#: includes/class-styles-service.php:293 +#: includes/class-styles-service.php:244 includes/class-styles-service.php:293 msgid "Style not found." msgstr "Estilo no encontrado." @@ -850,8 +821,7 @@ msgstr "Archivo eXeLearning no encontrado." msgid "This is a source file that cannot be previewed directly. Download it to open with eXeLearning." msgstr "Este es un archivo fuente que no puede previsualizarse directamente. Descárgalo para abrirlo con eXeLearning." -#: public/class-shortcodes.php:277 -#: public/class-shortcodes.php:321 +#: public/class-shortcodes.php:277 public/class-shortcodes.php:321 msgid "Download source file" msgstr "Descargar archivo fuente" @@ -867,8 +837,7 @@ msgstr "Seleccionar archivo eXeLearning" msgid "Use this file" msgstr "Usar este archivo" -#: assets/js/elp-upload.js:332 -#: assets/js/elp-upload.js:362 +#: assets/js/elp-upload.js:332 assets/js/elp-upload.js:362 #: assets/js/elp-upload.js:521 msgid "eXeLearning Content" msgstr "Contenido eXeLearning" @@ -961,8 +930,7 @@ msgstr "Altura de la previsualización, en píxeles." msgid "When enabled, the teacher layer selector is offered so viewers can reveal teacher content." msgstr "Cuando se activa, se ofrece el selector de capa docente para que quien lo vea pueda mostrar el contenido docente." -#: admin/class-admin-settings.php:130 -#: assets/js/elp-upload.js:414 +#: admin/class-admin-settings.php:130 assets/js/elp-upload.js:414 msgid "If disabled, the teacher layer selector is hidden in the embedded eXeLearning content." msgstr "Si se desactiva, se ocultará el selector de capa docente en el contenido eXeLearning embebido." @@ -1025,3 +993,43 @@ msgstr "Servir los recursos del paquete a través del proxy de WordPress" #: admin/class-admin-settings.php:191 msgid "Use this option only if your web server returns incorrect MIME types for package assets, for example JavaScript files served as text/plain. When enabled, CSS, JavaScript, fonts, images and other package files are served through WordPress so the plugin can send explicit Content-Type headers. This can reduce performance because requests are handled by PHP instead of being served directly by the web server." msgstr "Use esta opción solo si su servidor web devuelve tipos MIME incorrectos para los recursos del paquete, por ejemplo archivos JavaScript servidos como text/plain. Cuando está activada, los archivos CSS, JavaScript, fuentes, imágenes y otros archivos del paquete se sirven a través de WordPress para que el plugin pueda enviar cabeceras Content-Type explícitas. Esto puede reducir el rendimiento porque las solicitudes las gestiona PHP en lugar de servirse directamente desde el servidor web." + +#: admin/class-admin-settings.php:104 +msgid "Settings saved." +msgstr "Ajustes guardados." + +#: admin/class-admin-settings.php:111 +msgid "Security" +msgstr "Seguridad" + +#: admin/class-admin-settings.php:117 +msgid "Iframe security mode" +msgstr "Modo de seguridad del iframe" + +#: admin/class-admin-settings.php:122 +msgid "Secure (opaque-origin sandbox)" +msgstr "Seguro (aislamiento de origen opaco)" + +#: admin/class-admin-settings.php:125 +msgid "Legacy (same-origin)" +msgstr "Heredado (mismo origen)" + +#: admin/class-admin-settings.php:129 +msgid "Secure (recommended) isolates embedded content in an opaque-origin iframe. Legacy keeps same-origin behavior, needed only in some environments such as WordPress Playground." +msgstr "El modo seguro (recomendado) aísla el contenido incrustado en un iframe de origen opaco. El modo heredado mantiene el mismo origen, necesario solo en algunos entornos como WordPress Playground." + +#: admin/class-admin-settings.php:134 +msgid "Save changes" +msgstr "Guardar cambios" + +#: admin/class-admin-settings.php:185 +msgid "When enabled, the content loads with teacher mode active." +msgstr "Cuando se activa, el contenido se carga con el modo docente activo." + +#: admin/class-admin-settings.php:190 +msgid "Whether the teacher-mode toggle button is shown." +msgstr "Si se muestra el botón para conmutar el modo docente." + +#: assets/js/elp-upload.js:413 +msgid "Show Teacher Mode toggler" +msgstr "Mostrar el conmutador de Modo Docente" diff --git a/languages/exelearning-eu.mo b/languages/exelearning-eu.mo index 16012f4..1ff16f5 100644 Binary files a/languages/exelearning-eu.mo and b/languages/exelearning-eu.mo differ diff --git a/languages/exelearning-eu.po b/languages/exelearning-eu.po index 0221dad..785ca63 100644 --- a/languages/exelearning-eu.po +++ b/languages/exelearning-eu.po @@ -14,8 +14,7 @@ msgstr "" "X-Domain: exelearning\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: admin/class-admin-settings.php:45 -#: public/views/elp-list.php:63 +#: admin/class-admin-settings.php:45 public/views/elp-list.php:63 #: assets/js/elp-upload.js:401 msgid "Settings" msgstr "Ezarpenak" @@ -24,20 +23,17 @@ msgstr "Ezarpenak" msgid "Date" msgstr "Data" -#: admin/class-admin-settings.php:350 -#: includes/class-elp-list-table.php:109 +#: admin/class-admin-settings.php:350 includes/class-elp-list-table.php:109 msgid "Delete" msgstr "Ezabatu" -#: admin/class-admin-settings.php:325 -#: admin/class-admin-settings.php:368 +#: admin/class-admin-settings.php:325 admin/class-admin-settings.php:368 #: includes/class-elp-list-table.php:49 msgid "Title" msgstr "Izenburua" #. Plugin Name of the plugin -#: exelearning.php -#: admin/class-admin-settings.php:57 +#: exelearning.php admin/class-admin-settings.php:57 #: includes/class-mime-types.php:73 #: includes/integrations/class-media-library.php:381 msgid "eXeLearning" @@ -58,8 +54,7 @@ msgstr "eXeLearning .elp fitxategiak WordPressen kudeatzeko plugina. Igo, kudeat msgid "INTEF" msgstr "INTEF" -#: admin/class-admin-settings.php:56 -#: admin/class-admin-settings.php:70 +#: admin/class-admin-settings.php:56 admin/class-admin-settings.php:70 msgid "eXeLearning Settings" msgstr "eXeLearning ezarpenak" @@ -120,8 +115,7 @@ msgstr "Erabili fitxategi hau" msgid "Invalid nonce." msgstr "Nonce baliogabea." -#: admin/class-admin-settings.php:247 -#: admin/class-admin-styles.php:164 +#: admin/class-admin-settings.php:247 admin/class-admin-styles.php:164 #: admin/class-admin-upload.php:41 msgid "Insufficient permissions." msgstr "Baimen nahikorik ez." @@ -171,8 +165,7 @@ msgstr "Fitxategi bide baliogabea." msgid "File not found." msgstr "Fitxategia ez da aurkitu." -#: includes/class-content-proxy.php:176 -#: includes/class-content-proxy.php:184 +#: includes/class-content-proxy.php:176 includes/class-content-proxy.php:184 msgid "Access denied." msgstr "Sarbidea ukatua." @@ -180,8 +173,7 @@ msgstr "Sarbidea ukatua." msgid "This eXeLearning content is a source file and cannot be previewed directly." msgstr "eXeLearning eduki hau iturburu fitxategia da eta ezin da zuzenean aurreikusi." -#: includes/class-elp-upload-block.php:239 -#: public/class-shortcodes.php:190 +#: includes/class-elp-upload-block.php:239 public/class-shortcodes.php:190 msgid "Download file" msgstr "Deskargatu fitxategia" @@ -217,8 +209,7 @@ msgstr "Ez duzu fitxategi hau editatzeko baimenik." #: includes/class-exelearning-editor.php:208 #: includes/integrations/class-media-library.php:195 #: includes/integrations/class-media-library.php:349 -#: assets/js/elp-upload.js:423 -#: assets/js/elp-upload.js:463 +#: assets/js/elp-upload.js:423 assets/js/elp-upload.js:463 msgid "Edit in eXeLearning" msgstr "Editatu eXeLearningen" @@ -232,14 +223,12 @@ msgstr "Itxi" msgid "You do not have permission to read this file." msgstr "Ez duzu fitxategi hau irakurtzeko baimenik." -#: admin/class-admin-styles.php:42 -#: includes/class-exelearning-rest-api.php:227 +#: admin/class-admin-styles.php:42 includes/class-exelearning-rest-api.php:227 #: includes/class-exelearning-rest-api.php:581 msgid "No file uploaded." msgstr "Ez da fitxategirik igo." -#: admin/class-admin-styles.php:50 -#: includes/class-exelearning-rest-api.php:239 +#: admin/class-admin-styles.php:50 includes/class-exelearning-rest-api.php:239 #: includes/class-exelearning-rest-api.php:592 msgid "File upload failed." msgstr "Fitxategia igotzeak huts egin du." @@ -312,8 +301,7 @@ msgstr "eXeLearning fitxategia ez da aurkitu." msgid "This is a source file that cannot be previewed directly. Download it to open with eXeLearning." msgstr "Hau iturburu fitxategia da eta ezin da zuzenean aurreikusi. Deskargatu eXeLearningekin irekitzeko." -#: public/class-shortcodes.php:277 -#: public/class-shortcodes.php:321 +#: public/class-shortcodes.php:277 public/class-shortcodes.php:321 msgid "Download source file" msgstr "Deskargatu iturburu fitxategia" @@ -338,8 +326,7 @@ msgstr "Mota:" msgid "Preview in new tab" msgstr "Aurrebista fitxa berrian" -#: assets/js/elp-upload.js:332 -#: assets/js/elp-upload.js:362 +#: assets/js/elp-upload.js:332 assets/js/elp-upload.js:362 #: assets/js/elp-upload.js:521 msgid "eXeLearning Content" msgstr "eXeLearning edukia" @@ -418,13 +405,11 @@ msgstr "Editore txertatua beharrezkoa da eXeLearning fitxategiak editatzeko." msgid "Please install it using the button below." msgstr "Mesedez, instalatu beheko botoia erabiliz." -#: admin/class-admin-settings.php:557 -#: admin/class-admin-settings.php:580 +#: admin/class-admin-settings.php:557 admin/class-admin-settings.php:580 msgid "Status:" msgstr "Egoera:" -#: admin/class-admin-settings.php:328 -#: admin/class-admin-settings.php:558 +#: admin/class-admin-settings.php:328 admin/class-admin-settings.php:558 msgid "Installed" msgstr "Instalatuta" @@ -655,8 +640,7 @@ msgstr "Aurrebistaren altuera, pixeletan." msgid "When enabled, the teacher layer selector is offered so viewers can reveal teacher content." msgstr "Aktibatzen denean, irakasle-geruza hautatzailea eskaintzen da ikusleek irakasle-edukia ager dezaten." -#: admin/class-admin-settings.php:130 -#: assets/js/elp-upload.js:414 +#: admin/class-admin-settings.php:130 assets/js/elp-upload.js:414 msgid "If disabled, the teacher layer selector is hidden in the embedded eXeLearning content." msgstr "Desaktibatuta badago, irakasle-geruza hautatzailea ezkutatuko da eXeLearning eduki txertatuan." @@ -734,20 +718,16 @@ msgstr "Igotako estiloak" msgid "No uploaded styles yet." msgstr "Oraindik ez dago igotako estilorik." -#: admin/class-admin-settings.php:326 -#: admin/class-admin-settings.php:369 +#: admin/class-admin-settings.php:326 admin/class-admin-settings.php:369 msgid "Id" msgstr "Id" -#: admin/class-admin-settings.php:327 -#: admin/class-admin-settings.php:370 +#: admin/class-admin-settings.php:327 admin/class-admin-settings.php:370 msgid "Version" msgstr "Bertsioa" -#: admin/class-admin-settings.php:329 -#: admin/class-admin-settings.php:345 -#: admin/class-admin-settings.php:371 -#: admin/class-admin-settings.php:386 +#: admin/class-admin-settings.php:329 admin/class-admin-settings.php:345 +#: admin/class-admin-settings.php:371 admin/class-admin-settings.php:386 msgid "Enabled" msgstr "Gaituta" @@ -771,8 +751,7 @@ msgstr "Desgaitutako estilo integratuak editoretik ezkutatzen dira. Igotako esti msgid "Uploading…" msgstr "Igotzen…" -#: admin/class-admin-settings.php:445 -#: admin/class-admin-styles.php:71 +#: admin/class-admin-settings.php:445 admin/class-admin-styles.php:71 msgid "Style installed." msgstr "Estiloa instalatu da." @@ -780,16 +759,13 @@ msgstr "Estiloa instalatu da." msgid "Upload failed." msgstr "Igoerak huts egin du." -#: admin/class-admin-settings.php:231 -#: admin/class-admin-settings.php:451 -#: admin/class-admin-settings.php:473 -#: admin/class-admin-settings.php:494 +#: admin/class-admin-settings.php:231 admin/class-admin-settings.php:451 +#: admin/class-admin-settings.php:473 admin/class-admin-settings.php:494 #: admin/class-admin-settings.php:519 msgid "Network error." msgstr "Sare-errorea." -#: admin/class-admin-settings.php:225 -#: admin/class-admin-settings.php:469 +#: admin/class-admin-settings.php:225 admin/class-admin-settings.php:469 #: admin/class-admin-settings.php:490 msgid "Update failed." msgstr "Eguneratzeak huts egin du." @@ -810,25 +786,21 @@ msgstr "Ezabatzeak huts egin du." msgid "Uploaded file is not accessible." msgstr "Igotako fitxategia ezin da atzitu." -#: admin/class-admin-styles.php:87 -#: admin/class-admin-styles.php:106 +#: admin/class-admin-styles.php:87 admin/class-admin-styles.php:106 #: admin/class-admin-styles.php:130 msgid "Missing style id." msgstr "Estiloaren IDa falta da." -#: admin/class-admin-settings.php:251 -#: admin/class-admin-styles.php:168 +#: admin/class-admin-settings.php:251 admin/class-admin-styles.php:168 msgid "Invalid or missing security token." msgstr "Segurtasun-token baliogabea edo falta dena." -#: includes/class-download-button-renderer.php:107 -#: assets/js/elp-upload.js:180 +#: includes/class-download-button-renderer.php:107 assets/js/elp-upload.js:180 msgid "More download formats" msgstr "Deskarga-formatu gehiago" #: includes/class-download-button-renderer.php:138 -#: includes/class-download-button-renderer.php:209 -#: assets/js/elp-upload.js:79 +#: includes/class-download-button-renderer.php:209 assets/js/elp-upload.js:79 msgid "Install the eXeLearning editor from the plugin settings page to enable this format." msgstr "Instalatu eXeLearning editorea pluginaren ezarpen-orritik formatu hau gaitzeko." @@ -836,13 +808,11 @@ msgstr "Instalatu eXeLearning editorea pluginaren ezarpen-orritik formatu hau ga msgid "Preparing download…" msgstr "Deskarga prestatzen…" -#: includes/class-download-button-renderer.php:208 -#: assets/js/elp-upload.js:136 +#: includes/class-download-button-renderer.php:208 assets/js/elp-upload.js:136 msgid "Download failed. Please try again." msgstr "Deskargak huts egin du. Saiatu berriro." -#: includes/class-download-formats.php:31 -#: assets/js/elp-upload.js:36 +#: includes/class-download-formats.php:31 assets/js/elp-upload.js:36 msgid "Download .elpx" msgstr ".elpx deskargatu" @@ -948,8 +918,7 @@ msgstr "Erauzketa garaian artxibo-sarrera ez segurua baztertu da." msgid "Refused path traversal during extraction." msgstr "Erauzketa garaian bide-zeharkatzea baztertu da." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:297 includes/class-style-package.php:301 msgid "Failed to create a directory from the archive." msgstr "Ezin izan da artxibotik direktorio bat sortu." @@ -969,8 +938,7 @@ msgstr "config.xml ez da baliozko XML." msgid "config.xml must declare a element." msgstr "config.xml-ek elementu bat deklaratu behar du." -#: includes/class-styles-service.php:244 -#: includes/class-styles-service.php:293 +#: includes/class-styles-service.php:244 includes/class-styles-service.php:293 msgid "Style not found." msgstr "Ez da estiloa aurkitu." @@ -1025,3 +993,43 @@ msgstr "Paketearen baliabideak WordPress-en proxyaren bidez zerbitzatu" #: admin/class-admin-settings.php:191 msgid "Use this option only if your web server returns incorrect MIME types for package assets, for example JavaScript files served as text/plain. When enabled, CSS, JavaScript, fonts, images and other package files are served through WordPress so the plugin can send explicit Content-Type headers. This can reduce performance because requests are handled by PHP instead of being served directly by the web server." msgstr "Erabili aukera hau soilik zure web zerbitzariak paketearen baliabideentzat MIME mota okerrak itzultzen baditu, adibidez text/plain gisa zerbitzatzen diren JavaScript fitxategiak. Gaituta dagoenean, CSS, JavaScript, letra-tipo, irudi eta paketearen beste fitxategi batzuk WordPress-en bidez zerbitzatzen dira, pluginak Content-Type goiburu esplizituak bidali ahal izateko. Horrek errendimendua murriztu dezake, eskaerak PHPk kudeatzen baititu web zerbitzaritik zuzenean zerbitzatu beharrean." + +#: admin/class-admin-settings.php:104 +msgid "Settings saved." +msgstr "Ezarpenak gorde dira." + +#: admin/class-admin-settings.php:111 +msgid "Security" +msgstr "Segurtasuna" + +#: admin/class-admin-settings.php:117 +msgid "Iframe security mode" +msgstr "Iframe-aren segurtasun modua" + +#: admin/class-admin-settings.php:122 +msgid "Secure (opaque-origin sandbox)" +msgstr "Segurua (jatorri opakoko isolamendua)" + +#: admin/class-admin-settings.php:125 +msgid "Legacy (same-origin)" +msgstr "Aurrekoa (jatorri berekoa)" + +#: admin/class-admin-settings.php:129 +msgid "Secure (recommended) isolates embedded content in an opaque-origin iframe. Legacy keeps same-origin behavior, needed only in some environments such as WordPress Playground." +msgstr "Modu seguruak (gomendatua) kapsulatutako edukia jatorri opakoko iframe batean isolatzen du. Aurreko moduak jatorri bera mantentzen du, eta zenbait ingurunetan baino ez da beharrezkoa, hala nola WordPress Playground." + +#: admin/class-admin-settings.php:134 +msgid "Save changes" +msgstr "Gorde aldaketak" + +#: admin/class-admin-settings.php:185 +msgid "When enabled, the content loads with teacher mode active." +msgstr "Gaituta dagoenean, edukia irakasle modua aktibatuta kargatzen da." + +#: admin/class-admin-settings.php:190 +msgid "Whether the teacher-mode toggle button is shown." +msgstr "Irakasle moduaren txandakatze-botoia erakusten den ala ez." + +#: assets/js/elp-upload.js:413 +msgid "Show Teacher Mode toggler" +msgstr "Erakutsi Irakasle Moduaren txandakatzailea" diff --git a/languages/exelearning-gl_ES.mo b/languages/exelearning-gl_ES.mo index 32f83e7..8593fa3 100644 Binary files a/languages/exelearning-gl_ES.mo and b/languages/exelearning-gl_ES.mo differ diff --git a/languages/exelearning-gl_ES.po b/languages/exelearning-gl_ES.po index d998c55..07185a0 100644 --- a/languages/exelearning-gl_ES.po +++ b/languages/exelearning-gl_ES.po @@ -14,8 +14,7 @@ msgstr "" "X-Domain: exelearning\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: admin/class-admin-settings.php:45 -#: public/views/elp-list.php:63 +#: admin/class-admin-settings.php:45 public/views/elp-list.php:63 #: assets/js/elp-upload.js:401 msgid "Settings" msgstr "Axustes" @@ -24,20 +23,17 @@ msgstr "Axustes" msgid "Date" msgstr "Data" -#: admin/class-admin-settings.php:350 -#: includes/class-elp-list-table.php:109 +#: admin/class-admin-settings.php:350 includes/class-elp-list-table.php:109 msgid "Delete" msgstr "Eliminar" -#: admin/class-admin-settings.php:325 -#: admin/class-admin-settings.php:368 +#: admin/class-admin-settings.php:325 admin/class-admin-settings.php:368 #: includes/class-elp-list-table.php:49 msgid "Title" msgstr "Título" #. Plugin Name of the plugin -#: exelearning.php -#: admin/class-admin-settings.php:57 +#: exelearning.php admin/class-admin-settings.php:57 #: includes/class-mime-types.php:73 #: includes/integrations/class-media-library.php:381 msgid "eXeLearning" @@ -58,8 +54,7 @@ msgstr "Complemento para xestionar ficheiros .elp de eXeLearning en WordPress. S msgid "INTEF" msgstr "INTEF" -#: admin/class-admin-settings.php:56 -#: admin/class-admin-settings.php:70 +#: admin/class-admin-settings.php:56 admin/class-admin-settings.php:70 msgid "eXeLearning Settings" msgstr "Axustes de eXeLearning" @@ -120,8 +115,7 @@ msgstr "Usar este ficheiro" msgid "Invalid nonce." msgstr "Nonce non válido." -#: admin/class-admin-settings.php:247 -#: admin/class-admin-styles.php:164 +#: admin/class-admin-settings.php:247 admin/class-admin-styles.php:164 #: admin/class-admin-upload.php:41 msgid "Insufficient permissions." msgstr "Permisos insuficientes." @@ -171,8 +165,7 @@ msgstr "Ruta de ficheiro non válida." msgid "File not found." msgstr "Ficheiro non atopado." -#: includes/class-content-proxy.php:176 -#: includes/class-content-proxy.php:184 +#: includes/class-content-proxy.php:176 includes/class-content-proxy.php:184 msgid "Access denied." msgstr "Acceso denegado." @@ -180,8 +173,7 @@ msgstr "Acceso denegado." msgid "This eXeLearning content is a source file and cannot be previewed directly." msgstr "Este contido de eXeLearning é un ficheiro fonte e non se pode previsualizar directamente." -#: includes/class-elp-upload-block.php:239 -#: public/class-shortcodes.php:190 +#: includes/class-elp-upload-block.php:239 public/class-shortcodes.php:190 msgid "Download file" msgstr "Descargar ficheiro" @@ -217,8 +209,7 @@ msgstr "Non tes permiso para editar este ficheiro." #: includes/class-exelearning-editor.php:208 #: includes/integrations/class-media-library.php:195 #: includes/integrations/class-media-library.php:349 -#: assets/js/elp-upload.js:423 -#: assets/js/elp-upload.js:463 +#: assets/js/elp-upload.js:423 assets/js/elp-upload.js:463 msgid "Edit in eXeLearning" msgstr "Editar en eXeLearning" @@ -232,14 +223,12 @@ msgstr "Pechar" msgid "You do not have permission to read this file." msgstr "Non tes permiso para ler este ficheiro." -#: admin/class-admin-styles.php:42 -#: includes/class-exelearning-rest-api.php:227 +#: admin/class-admin-styles.php:42 includes/class-exelearning-rest-api.php:227 #: includes/class-exelearning-rest-api.php:581 msgid "No file uploaded." msgstr "Non se subiu ningún ficheiro." -#: admin/class-admin-styles.php:50 -#: includes/class-exelearning-rest-api.php:239 +#: admin/class-admin-styles.php:50 includes/class-exelearning-rest-api.php:239 #: includes/class-exelearning-rest-api.php:592 msgid "File upload failed." msgstr "Erro ao subir o ficheiro." @@ -312,8 +301,7 @@ msgstr "Ficheiro eXeLearning non atopado." msgid "This is a source file that cannot be previewed directly. Download it to open with eXeLearning." msgstr "Este é un ficheiro fonte que non se pode previsualizar directamente. Descárgao para abrilo con eXeLearning." -#: public/class-shortcodes.php:277 -#: public/class-shortcodes.php:321 +#: public/class-shortcodes.php:277 public/class-shortcodes.php:321 msgid "Download source file" msgstr "Descargar ficheiro fonte" @@ -338,8 +326,7 @@ msgstr "Tipo:" msgid "Preview in new tab" msgstr "Vista previa nunha nova pestana" -#: assets/js/elp-upload.js:332 -#: assets/js/elp-upload.js:362 +#: assets/js/elp-upload.js:332 assets/js/elp-upload.js:362 #: assets/js/elp-upload.js:521 msgid "eXeLearning Content" msgstr "Contido eXeLearning" @@ -418,13 +405,11 @@ msgstr "O editor integrado é necesario para editar ficheiros eXeLearning." msgid "Please install it using the button below." msgstr "Por favor, instáleo usando o botón de abaixo." -#: admin/class-admin-settings.php:557 -#: admin/class-admin-settings.php:580 +#: admin/class-admin-settings.php:557 admin/class-admin-settings.php:580 msgid "Status:" msgstr "Estado:" -#: admin/class-admin-settings.php:328 -#: admin/class-admin-settings.php:558 +#: admin/class-admin-settings.php:328 admin/class-admin-settings.php:558 msgid "Installed" msgstr "Instalado" @@ -655,8 +640,7 @@ msgstr "Altura da vista previa, en píxeles." msgid "When enabled, the teacher layer selector is offered so viewers can reveal teacher content." msgstr "Cando se activa, ofrécese o selector de capa docente para que quen o vexa poida amosar o contido docente." -#: admin/class-admin-settings.php:130 -#: assets/js/elp-upload.js:414 +#: admin/class-admin-settings.php:130 assets/js/elp-upload.js:414 msgid "If disabled, the teacher layer selector is hidden in the embedded eXeLearning content." msgstr "Se se desactiva, ocultarase o selector de capa docente no contido eXeLearning embebido." @@ -734,20 +718,16 @@ msgstr "Estilos subidos" msgid "No uploaded styles yet." msgstr "Aínda non hai estilos subidos." -#: admin/class-admin-settings.php:326 -#: admin/class-admin-settings.php:369 +#: admin/class-admin-settings.php:326 admin/class-admin-settings.php:369 msgid "Id" msgstr "Id" -#: admin/class-admin-settings.php:327 -#: admin/class-admin-settings.php:370 +#: admin/class-admin-settings.php:327 admin/class-admin-settings.php:370 msgid "Version" msgstr "Versión" -#: admin/class-admin-settings.php:329 -#: admin/class-admin-settings.php:345 -#: admin/class-admin-settings.php:371 -#: admin/class-admin-settings.php:386 +#: admin/class-admin-settings.php:329 admin/class-admin-settings.php:345 +#: admin/class-admin-settings.php:371 admin/class-admin-settings.php:386 msgid "Enabled" msgstr "Habilitado" @@ -771,8 +751,7 @@ msgstr "Os estilos integrados desactivados agóchanse do editor. Os estilos subi msgid "Uploading…" msgstr "Subindo…" -#: admin/class-admin-settings.php:445 -#: admin/class-admin-styles.php:71 +#: admin/class-admin-settings.php:445 admin/class-admin-styles.php:71 msgid "Style installed." msgstr "Estilo instalado." @@ -780,16 +759,13 @@ msgstr "Estilo instalado." msgid "Upload failed." msgstr "Produciuse un erro ao subir." -#: admin/class-admin-settings.php:231 -#: admin/class-admin-settings.php:451 -#: admin/class-admin-settings.php:473 -#: admin/class-admin-settings.php:494 +#: admin/class-admin-settings.php:231 admin/class-admin-settings.php:451 +#: admin/class-admin-settings.php:473 admin/class-admin-settings.php:494 #: admin/class-admin-settings.php:519 msgid "Network error." msgstr "Erro de rede." -#: admin/class-admin-settings.php:225 -#: admin/class-admin-settings.php:469 +#: admin/class-admin-settings.php:225 admin/class-admin-settings.php:469 #: admin/class-admin-settings.php:490 msgid "Update failed." msgstr "Produciuse un erro ao actualizar." @@ -810,25 +786,21 @@ msgstr "Produciuse un erro ao eliminar." msgid "Uploaded file is not accessible." msgstr "Non se pode acceder ao ficheiro subido." -#: admin/class-admin-styles.php:87 -#: admin/class-admin-styles.php:106 +#: admin/class-admin-styles.php:87 admin/class-admin-styles.php:106 #: admin/class-admin-styles.php:130 msgid "Missing style id." msgstr "Falta o identificador do estilo." -#: admin/class-admin-settings.php:251 -#: admin/class-admin-styles.php:168 +#: admin/class-admin-settings.php:251 admin/class-admin-styles.php:168 msgid "Invalid or missing security token." msgstr "Testemuño de seguranza non válido ou ausente." -#: includes/class-download-button-renderer.php:107 -#: assets/js/elp-upload.js:180 +#: includes/class-download-button-renderer.php:107 assets/js/elp-upload.js:180 msgid "More download formats" msgstr "Máis formatos de descarga" #: includes/class-download-button-renderer.php:138 -#: includes/class-download-button-renderer.php:209 -#: assets/js/elp-upload.js:79 +#: includes/class-download-button-renderer.php:209 assets/js/elp-upload.js:79 msgid "Install the eXeLearning editor from the plugin settings page to enable this format." msgstr "Instala o editor de eXeLearning desde a páxina de configuración do complemento para habilitar este formato." @@ -836,13 +808,11 @@ msgstr "Instala o editor de eXeLearning desde a páxina de configuración do com msgid "Preparing download…" msgstr "Preparando a descarga…" -#: includes/class-download-button-renderer.php:208 -#: assets/js/elp-upload.js:136 +#: includes/class-download-button-renderer.php:208 assets/js/elp-upload.js:136 msgid "Download failed. Please try again." msgstr "A descarga fallou. Téntao de novo." -#: includes/class-download-formats.php:31 -#: assets/js/elp-upload.js:36 +#: includes/class-download-formats.php:31 assets/js/elp-upload.js:36 msgid "Download .elpx" msgstr "Descargar .elpx" @@ -948,8 +918,7 @@ msgstr "Rexeitouse unha entrada de arquivo insegura durante a extracción." msgid "Refused path traversal during extraction." msgstr "Rexeitouse un salto de ruta durante a extracción." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:297 includes/class-style-package.php:301 msgid "Failed to create a directory from the archive." msgstr "Non se puido crear un directorio do arquivo." @@ -969,8 +938,7 @@ msgstr "config.xml non é XML válido." msgid "config.xml must declare a element." msgstr "config.xml debe declarar un elemento ." -#: includes/class-styles-service.php:244 -#: includes/class-styles-service.php:293 +#: includes/class-styles-service.php:244 includes/class-styles-service.php:293 msgid "Style not found." msgstr "Non se atopou o estilo." @@ -1025,3 +993,43 @@ msgstr "Servir os recursos do paquete a través do proxy de WordPress" #: admin/class-admin-settings.php:191 msgid "Use this option only if your web server returns incorrect MIME types for package assets, for example JavaScript files served as text/plain. When enabled, CSS, JavaScript, fonts, images and other package files are served through WordPress so the plugin can send explicit Content-Type headers. This can reduce performance because requests are handled by PHP instead of being served directly by the web server." msgstr "Use esta opción só se o seu servidor web devolve tipos MIME incorrectos para os recursos do paquete, por exemplo ficheiros JavaScript servidos como text/plain. Cando está activada, os ficheiros CSS, JavaScript, fontes, imaxes e outros ficheiros do paquete sérvense a través de WordPress para que o complemento poida enviar cabeceiras Content-Type explícitas. Isto pode reducir o rendemento porque as solicitudes xestiónaas PHP en lugar de servirse directamente desde o servidor web." + +#: admin/class-admin-settings.php:104 +msgid "Settings saved." +msgstr "Configuración gardada." + +#: admin/class-admin-settings.php:111 +msgid "Security" +msgstr "Seguridade" + +#: admin/class-admin-settings.php:117 +msgid "Iframe security mode" +msgstr "Modo de seguridade do iframe" + +#: admin/class-admin-settings.php:122 +msgid "Secure (opaque-origin sandbox)" +msgstr "Seguro (illamento de orixe opaca)" + +#: admin/class-admin-settings.php:125 +msgid "Legacy (same-origin)" +msgstr "Herdado (mesma orixe)" + +#: admin/class-admin-settings.php:129 +msgid "Secure (recommended) isolates embedded content in an opaque-origin iframe. Legacy keeps same-origin behavior, needed only in some environments such as WordPress Playground." +msgstr "O modo seguro (recomendado) illa o contido incrustado nun iframe de orixe opaca. O modo herdado mantén a mesma orixe, necesario só nalgúns contornos como WordPress Playground." + +#: admin/class-admin-settings.php:134 +msgid "Save changes" +msgstr "Gardar os cambios" + +#: admin/class-admin-settings.php:185 +msgid "When enabled, the content loads with teacher mode active." +msgstr "Cando se activa, o contido cárgase co modo docente activo." + +#: admin/class-admin-settings.php:190 +msgid "Whether the teacher-mode toggle button is shown." +msgstr "Se se amosa o botón para conmutar o modo docente." + +#: assets/js/elp-upload.js:413 +msgid "Show Teacher Mode toggler" +msgstr "Mostrar o conmutador de Modo Profesor" diff --git a/languages/exelearning-it_IT.mo b/languages/exelearning-it_IT.mo index 16f0af3..49eba32 100644 Binary files a/languages/exelearning-it_IT.mo and b/languages/exelearning-it_IT.mo differ diff --git a/languages/exelearning-it_IT.po b/languages/exelearning-it_IT.po index 9b9092b..32ccb40 100644 --- a/languages/exelearning-it_IT.po +++ b/languages/exelearning-it_IT.po @@ -14,8 +14,7 @@ msgstr "" "X-Domain: exelearning\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: admin/class-admin-settings.php:45 -#: public/views/elp-list.php:63 +#: admin/class-admin-settings.php:45 public/views/elp-list.php:63 #: assets/js/elp-upload.js:401 msgid "Settings" msgstr "Impostazioni" @@ -24,20 +23,17 @@ msgstr "Impostazioni" msgid "Date" msgstr "Data" -#: admin/class-admin-settings.php:350 -#: includes/class-elp-list-table.php:109 +#: admin/class-admin-settings.php:350 includes/class-elp-list-table.php:109 msgid "Delete" msgstr "Elimina" -#: admin/class-admin-settings.php:325 -#: admin/class-admin-settings.php:368 +#: admin/class-admin-settings.php:325 admin/class-admin-settings.php:368 #: includes/class-elp-list-table.php:49 msgid "Title" msgstr "Titolo" #. Plugin Name of the plugin -#: exelearning.php -#: admin/class-admin-settings.php:57 +#: exelearning.php admin/class-admin-settings.php:57 #: includes/class-mime-types.php:73 #: includes/integrations/class-media-library.php:381 msgid "eXeLearning" @@ -58,8 +54,7 @@ msgstr "Plugin per gestire i file .elp di eXeLearning in WordPress. Carica, gest msgid "INTEF" msgstr "INTEF" -#: admin/class-admin-settings.php:56 -#: admin/class-admin-settings.php:70 +#: admin/class-admin-settings.php:56 admin/class-admin-settings.php:70 msgid "eXeLearning Settings" msgstr "Impostazioni di eXeLearning" @@ -120,8 +115,7 @@ msgstr "Usa questo file" msgid "Invalid nonce." msgstr "Nonce non valido." -#: admin/class-admin-settings.php:247 -#: admin/class-admin-styles.php:164 +#: admin/class-admin-settings.php:247 admin/class-admin-styles.php:164 #: admin/class-admin-upload.php:41 msgid "Insufficient permissions." msgstr "Permessi insufficienti." @@ -171,8 +165,7 @@ msgstr "Percorso file non valido." msgid "File not found." msgstr "File non trovato." -#: includes/class-content-proxy.php:176 -#: includes/class-content-proxy.php:184 +#: includes/class-content-proxy.php:176 includes/class-content-proxy.php:184 msgid "Access denied." msgstr "Accesso negato." @@ -180,8 +173,7 @@ msgstr "Accesso negato." msgid "This eXeLearning content is a source file and cannot be previewed directly." msgstr "Questo contenuto eXeLearning è un file sorgente e non può essere visualizzato in anteprima direttamente." -#: includes/class-elp-upload-block.php:239 -#: public/class-shortcodes.php:190 +#: includes/class-elp-upload-block.php:239 public/class-shortcodes.php:190 msgid "Download file" msgstr "Scarica file" @@ -217,8 +209,7 @@ msgstr "Non hai il permesso di modificare questo file." #: includes/class-exelearning-editor.php:208 #: includes/integrations/class-media-library.php:195 #: includes/integrations/class-media-library.php:349 -#: assets/js/elp-upload.js:423 -#: assets/js/elp-upload.js:463 +#: assets/js/elp-upload.js:423 assets/js/elp-upload.js:463 msgid "Edit in eXeLearning" msgstr "Modifica in eXeLearning" @@ -232,14 +223,12 @@ msgstr "Chiudi" msgid "You do not have permission to read this file." msgstr "Non hai il permesso di leggere questo file." -#: admin/class-admin-styles.php:42 -#: includes/class-exelearning-rest-api.php:227 +#: admin/class-admin-styles.php:42 includes/class-exelearning-rest-api.php:227 #: includes/class-exelearning-rest-api.php:581 msgid "No file uploaded." msgstr "Nessun file caricato." -#: admin/class-admin-styles.php:50 -#: includes/class-exelearning-rest-api.php:239 +#: admin/class-admin-styles.php:50 includes/class-exelearning-rest-api.php:239 #: includes/class-exelearning-rest-api.php:592 msgid "File upload failed." msgstr "Caricamento file fallito." @@ -312,8 +301,7 @@ msgstr "File eXeLearning non trovato." msgid "This is a source file that cannot be previewed directly. Download it to open with eXeLearning." msgstr "Questo è un file sorgente che non può essere visualizzato in anteprima direttamente. Scaricalo per aprirlo con eXeLearning." -#: public/class-shortcodes.php:277 -#: public/class-shortcodes.php:321 +#: public/class-shortcodes.php:277 public/class-shortcodes.php:321 msgid "Download source file" msgstr "Scarica file sorgente" @@ -338,8 +326,7 @@ msgstr "Tipo:" msgid "Preview in new tab" msgstr "Anteprima in una nuova scheda" -#: assets/js/elp-upload.js:332 -#: assets/js/elp-upload.js:362 +#: assets/js/elp-upload.js:332 assets/js/elp-upload.js:362 #: assets/js/elp-upload.js:521 msgid "eXeLearning Content" msgstr "Contenuto eXeLearning" @@ -418,13 +405,11 @@ msgstr "L'editor integrato è necessario per modificare i file eXeLearning." msgid "Please install it using the button below." msgstr "Installalo utilizzando il pulsante qui sotto." -#: admin/class-admin-settings.php:557 -#: admin/class-admin-settings.php:580 +#: admin/class-admin-settings.php:557 admin/class-admin-settings.php:580 msgid "Status:" msgstr "Stato:" -#: admin/class-admin-settings.php:328 -#: admin/class-admin-settings.php:558 +#: admin/class-admin-settings.php:328 admin/class-admin-settings.php:558 msgid "Installed" msgstr "Installato" @@ -655,8 +640,7 @@ msgstr "Altezza dell'anteprima, in pixel." msgid "When enabled, the teacher layer selector is offered so viewers can reveal teacher content." msgstr "Se attivato, viene offerto il selettore del livello docente così gli spettatori possono mostrare i contenuti per docenti." -#: admin/class-admin-settings.php:130 -#: assets/js/elp-upload.js:414 +#: admin/class-admin-settings.php:130 assets/js/elp-upload.js:414 msgid "If disabled, the teacher layer selector is hidden in the embedded eXeLearning content." msgstr "Se disattivato, il selettore del livello docente è nascosto nel contenuto eXeLearning incorporato." @@ -734,20 +718,16 @@ msgstr "Stili caricati" msgid "No uploaded styles yet." msgstr "Nessuno stile caricato." -#: admin/class-admin-settings.php:326 -#: admin/class-admin-settings.php:369 +#: admin/class-admin-settings.php:326 admin/class-admin-settings.php:369 msgid "Id" msgstr "ID" -#: admin/class-admin-settings.php:327 -#: admin/class-admin-settings.php:370 +#: admin/class-admin-settings.php:327 admin/class-admin-settings.php:370 msgid "Version" msgstr "Versione" -#: admin/class-admin-settings.php:329 -#: admin/class-admin-settings.php:345 -#: admin/class-admin-settings.php:371 -#: admin/class-admin-settings.php:386 +#: admin/class-admin-settings.php:329 admin/class-admin-settings.php:345 +#: admin/class-admin-settings.php:371 admin/class-admin-settings.php:386 msgid "Enabled" msgstr "Abilitato" @@ -771,8 +751,7 @@ msgstr "Gli stili integrati disabilitati sono nascosti nell'editor. Gli stili ca msgid "Uploading…" msgstr "Caricamento…" -#: admin/class-admin-settings.php:445 -#: admin/class-admin-styles.php:71 +#: admin/class-admin-settings.php:445 admin/class-admin-styles.php:71 msgid "Style installed." msgstr "Stile installato." @@ -780,16 +759,13 @@ msgstr "Stile installato." msgid "Upload failed." msgstr "Caricamento non riuscito." -#: admin/class-admin-settings.php:231 -#: admin/class-admin-settings.php:451 -#: admin/class-admin-settings.php:473 -#: admin/class-admin-settings.php:494 +#: admin/class-admin-settings.php:231 admin/class-admin-settings.php:451 +#: admin/class-admin-settings.php:473 admin/class-admin-settings.php:494 #: admin/class-admin-settings.php:519 msgid "Network error." msgstr "Errore di rete." -#: admin/class-admin-settings.php:225 -#: admin/class-admin-settings.php:469 +#: admin/class-admin-settings.php:225 admin/class-admin-settings.php:469 #: admin/class-admin-settings.php:490 msgid "Update failed." msgstr "Aggiornamento non riuscito." @@ -810,25 +786,21 @@ msgstr "Eliminazione non riuscita." msgid "Uploaded file is not accessible." msgstr "Il file caricato non è accessibile." -#: admin/class-admin-styles.php:87 -#: admin/class-admin-styles.php:106 +#: admin/class-admin-styles.php:87 admin/class-admin-styles.php:106 #: admin/class-admin-styles.php:130 msgid "Missing style id." msgstr "ID dello stile mancante." -#: admin/class-admin-settings.php:251 -#: admin/class-admin-styles.php:168 +#: admin/class-admin-settings.php:251 admin/class-admin-styles.php:168 msgid "Invalid or missing security token." msgstr "Token di sicurezza non valido o mancante." -#: includes/class-download-button-renderer.php:107 -#: assets/js/elp-upload.js:180 +#: includes/class-download-button-renderer.php:107 assets/js/elp-upload.js:180 msgid "More download formats" msgstr "Altri formati di download" #: includes/class-download-button-renderer.php:138 -#: includes/class-download-button-renderer.php:209 -#: assets/js/elp-upload.js:79 +#: includes/class-download-button-renderer.php:209 assets/js/elp-upload.js:79 msgid "Install the eXeLearning editor from the plugin settings page to enable this format." msgstr "Installa l'editor eXeLearning dalla pagina delle impostazioni del plugin per abilitare questo formato." @@ -836,13 +808,11 @@ msgstr "Installa l'editor eXeLearning dalla pagina delle impostazioni del plugin msgid "Preparing download…" msgstr "Preparazione del download…" -#: includes/class-download-button-renderer.php:208 -#: assets/js/elp-upload.js:136 +#: includes/class-download-button-renderer.php:208 assets/js/elp-upload.js:136 msgid "Download failed. Please try again." msgstr "Download non riuscito. Riprova." -#: includes/class-download-formats.php:31 -#: assets/js/elp-upload.js:36 +#: includes/class-download-formats.php:31 assets/js/elp-upload.js:36 msgid "Download .elpx" msgstr "Scarica .elpx" @@ -948,8 +918,7 @@ msgstr "Voce di archivio non sicura rifiutata durante l'estrazione." msgid "Refused path traversal during extraction." msgstr "Attraversamento di percorso rifiutato durante l'estrazione." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:297 includes/class-style-package.php:301 msgid "Failed to create a directory from the archive." msgstr "Impossibile creare una directory dall'archivio." @@ -969,8 +938,7 @@ msgstr "config.xml non è XML valido." msgid "config.xml must declare a element." msgstr "config.xml deve dichiarare un elemento ." -#: includes/class-styles-service.php:244 -#: includes/class-styles-service.php:293 +#: includes/class-styles-service.php:244 includes/class-styles-service.php:293 msgid "Style not found." msgstr "Stile non trovato." @@ -1025,3 +993,43 @@ msgstr "Servire le risorse del pacchetto tramite il proxy di WordPress" #: admin/class-admin-settings.php:191 msgid "Use this option only if your web server returns incorrect MIME types for package assets, for example JavaScript files served as text/plain. When enabled, CSS, JavaScript, fonts, images and other package files are served through WordPress so the plugin can send explicit Content-Type headers. This can reduce performance because requests are handled by PHP instead of being served directly by the web server." msgstr "Utilizza questa opzione solo se il tuo server web restituisce tipi MIME errati per le risorse del pacchetto, ad esempio file JavaScript serviti come text/plain. Quando è attivata, i file CSS, JavaScript, dei caratteri, delle immagini e altri file del pacchetto vengono serviti tramite WordPress affinché il plugin possa inviare intestazioni Content-Type esplicite. Questo può ridurre le prestazioni perché le richieste vengono gestite da PHP anziché essere servite direttamente dal server web." + +#: admin/class-admin-settings.php:104 +msgid "Settings saved." +msgstr "Impostazioni salvate." + +#: admin/class-admin-settings.php:111 +msgid "Security" +msgstr "Sicurezza" + +#: admin/class-admin-settings.php:117 +msgid "Iframe security mode" +msgstr "Modalità di sicurezza dell'iframe" + +#: admin/class-admin-settings.php:122 +msgid "Secure (opaque-origin sandbox)" +msgstr "Sicura (sandbox con origine opaca)" + +#: admin/class-admin-settings.php:125 +msgid "Legacy (same-origin)" +msgstr "Legacy (stessa origine)" + +#: admin/class-admin-settings.php:129 +msgid "Secure (recommended) isolates embedded content in an opaque-origin iframe. Legacy keeps same-origin behavior, needed only in some environments such as WordPress Playground." +msgstr "La modalità sicura (consigliata) isola il contenuto incorporato in un iframe con origine opaca. La modalità legacy mantiene la stessa origine, necessaria solo in alcuni ambienti come WordPress Playground." + +#: admin/class-admin-settings.php:134 +msgid "Save changes" +msgstr "Salva le modifiche" + +#: admin/class-admin-settings.php:185 +msgid "When enabled, the content loads with teacher mode active." +msgstr "Se attivato, il contenuto viene caricato con la modalità docente attiva." + +#: admin/class-admin-settings.php:190 +msgid "Whether the teacher-mode toggle button is shown." +msgstr "Indica se viene mostrato il pulsante di attivazione della modalità docente." + +#: assets/js/elp-upload.js:413 +msgid "Show Teacher Mode toggler" +msgstr "Mostra il commutatore Modalità Docente" diff --git a/languages/exelearning-pt_PT.mo b/languages/exelearning-pt_PT.mo index e9a1810..a27e45f 100644 Binary files a/languages/exelearning-pt_PT.mo and b/languages/exelearning-pt_PT.mo differ diff --git a/languages/exelearning-pt_PT.po b/languages/exelearning-pt_PT.po index 4fd2ace..87f7d41 100644 --- a/languages/exelearning-pt_PT.po +++ b/languages/exelearning-pt_PT.po @@ -14,8 +14,7 @@ msgstr "" "X-Domain: exelearning\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: admin/class-admin-settings.php:45 -#: public/views/elp-list.php:63 +#: admin/class-admin-settings.php:45 public/views/elp-list.php:63 #: assets/js/elp-upload.js:401 msgid "Settings" msgstr "Definições" @@ -24,20 +23,17 @@ msgstr "Definições" msgid "Date" msgstr "Data" -#: admin/class-admin-settings.php:350 -#: includes/class-elp-list-table.php:109 +#: admin/class-admin-settings.php:350 includes/class-elp-list-table.php:109 msgid "Delete" msgstr "Eliminar" -#: admin/class-admin-settings.php:325 -#: admin/class-admin-settings.php:368 +#: admin/class-admin-settings.php:325 admin/class-admin-settings.php:368 #: includes/class-elp-list-table.php:49 msgid "Title" msgstr "Título" #. Plugin Name of the plugin -#: exelearning.php -#: admin/class-admin-settings.php:57 +#: exelearning.php admin/class-admin-settings.php:57 #: includes/class-mime-types.php:73 #: includes/integrations/class-media-library.php:381 msgid "eXeLearning" @@ -58,8 +54,7 @@ msgstr "Plugin para gerir ficheiros .elp do eXeLearning no WordPress. Carregue, msgid "INTEF" msgstr "INTEF" -#: admin/class-admin-settings.php:56 -#: admin/class-admin-settings.php:70 +#: admin/class-admin-settings.php:56 admin/class-admin-settings.php:70 msgid "eXeLearning Settings" msgstr "Definições do eXeLearning" @@ -120,8 +115,7 @@ msgstr "Usar este ficheiro" msgid "Invalid nonce." msgstr "Nonce inválido." -#: admin/class-admin-settings.php:247 -#: admin/class-admin-styles.php:164 +#: admin/class-admin-settings.php:247 admin/class-admin-styles.php:164 #: admin/class-admin-upload.php:41 msgid "Insufficient permissions." msgstr "Permissões insuficientes." @@ -171,8 +165,7 @@ msgstr "Caminho de ficheiro inválido." msgid "File not found." msgstr "Ficheiro não encontrado." -#: includes/class-content-proxy.php:176 -#: includes/class-content-proxy.php:184 +#: includes/class-content-proxy.php:176 includes/class-content-proxy.php:184 msgid "Access denied." msgstr "Acesso negado." @@ -180,8 +173,7 @@ msgstr "Acesso negado." msgid "This eXeLearning content is a source file and cannot be previewed directly." msgstr "Este conteúdo eXeLearning é um ficheiro fonte e não pode ser pré-visualizado diretamente." -#: includes/class-elp-upload-block.php:239 -#: public/class-shortcodes.php:190 +#: includes/class-elp-upload-block.php:239 public/class-shortcodes.php:190 msgid "Download file" msgstr "Descarregar ficheiro" @@ -217,8 +209,7 @@ msgstr "Não tem permissão para editar este ficheiro." #: includes/class-exelearning-editor.php:208 #: includes/integrations/class-media-library.php:195 #: includes/integrations/class-media-library.php:349 -#: assets/js/elp-upload.js:423 -#: assets/js/elp-upload.js:463 +#: assets/js/elp-upload.js:423 assets/js/elp-upload.js:463 msgid "Edit in eXeLearning" msgstr "Editar no eXeLearning" @@ -232,14 +223,12 @@ msgstr "Fechar" msgid "You do not have permission to read this file." msgstr "Não tem permissão para ler este ficheiro." -#: admin/class-admin-styles.php:42 -#: includes/class-exelearning-rest-api.php:227 +#: admin/class-admin-styles.php:42 includes/class-exelearning-rest-api.php:227 #: includes/class-exelearning-rest-api.php:581 msgid "No file uploaded." msgstr "Nenhum ficheiro carregado." -#: admin/class-admin-styles.php:50 -#: includes/class-exelearning-rest-api.php:239 +#: admin/class-admin-styles.php:50 includes/class-exelearning-rest-api.php:239 #: includes/class-exelearning-rest-api.php:592 msgid "File upload failed." msgstr "Erro ao carregar o ficheiro." @@ -312,8 +301,7 @@ msgstr "Ficheiro eXeLearning não encontrado." msgid "This is a source file that cannot be previewed directly. Download it to open with eXeLearning." msgstr "Este é um ficheiro fonte que não pode ser pré-visualizado diretamente. Descarregue-o para abrir com o eXeLearning." -#: public/class-shortcodes.php:277 -#: public/class-shortcodes.php:321 +#: public/class-shortcodes.php:277 public/class-shortcodes.php:321 msgid "Download source file" msgstr "Descarregar ficheiro fonte" @@ -338,8 +326,7 @@ msgstr "Tipo:" msgid "Preview in new tab" msgstr "Pré-visualizar num novo separador" -#: assets/js/elp-upload.js:332 -#: assets/js/elp-upload.js:362 +#: assets/js/elp-upload.js:332 assets/js/elp-upload.js:362 #: assets/js/elp-upload.js:521 msgid "eXeLearning Content" msgstr "Conteúdo eXeLearning" @@ -418,13 +405,11 @@ msgstr "O editor integrado é necessário para editar ficheiros eXeLearning." msgid "Please install it using the button below." msgstr "Por favor, instale-o usando o botão abaixo." -#: admin/class-admin-settings.php:557 -#: admin/class-admin-settings.php:580 +#: admin/class-admin-settings.php:557 admin/class-admin-settings.php:580 msgid "Status:" msgstr "Estado:" -#: admin/class-admin-settings.php:328 -#: admin/class-admin-settings.php:558 +#: admin/class-admin-settings.php:328 admin/class-admin-settings.php:558 msgid "Installed" msgstr "Instalado" @@ -655,8 +640,7 @@ msgstr "Altura da pré-visualização, em pixels." msgid "When enabled, the teacher layer selector is offered so viewers can reveal teacher content." msgstr "Quando ativado, o seletor de camada docente é disponibilizado para que os visitantes possam revelar o conteúdo para docentes." -#: admin/class-admin-settings.php:130 -#: assets/js/elp-upload.js:414 +#: admin/class-admin-settings.php:130 assets/js/elp-upload.js:414 msgid "If disabled, the teacher layer selector is hidden in the embedded eXeLearning content." msgstr "Se desativado, o seletor de camada docente fica oculto no conteúdo eXeLearning incorporado." @@ -734,20 +718,16 @@ msgstr "Estilos carregados" msgid "No uploaded styles yet." msgstr "Ainda não há estilos carregados." -#: admin/class-admin-settings.php:326 -#: admin/class-admin-settings.php:369 +#: admin/class-admin-settings.php:326 admin/class-admin-settings.php:369 msgid "Id" msgstr "ID" -#: admin/class-admin-settings.php:327 -#: admin/class-admin-settings.php:370 +#: admin/class-admin-settings.php:327 admin/class-admin-settings.php:370 msgid "Version" msgstr "Versão" -#: admin/class-admin-settings.php:329 -#: admin/class-admin-settings.php:345 -#: admin/class-admin-settings.php:371 -#: admin/class-admin-settings.php:386 +#: admin/class-admin-settings.php:329 admin/class-admin-settings.php:345 +#: admin/class-admin-settings.php:371 admin/class-admin-settings.php:386 msgid "Enabled" msgstr "Ativado" @@ -771,8 +751,7 @@ msgstr "Os estilos incorporados desativados ficam ocultos no editor. Os estilos msgid "Uploading…" msgstr "A carregar…" -#: admin/class-admin-settings.php:445 -#: admin/class-admin-styles.php:71 +#: admin/class-admin-settings.php:445 admin/class-admin-styles.php:71 msgid "Style installed." msgstr "Estilo instalado." @@ -780,16 +759,13 @@ msgstr "Estilo instalado." msgid "Upload failed." msgstr "Falha no carregamento." -#: admin/class-admin-settings.php:231 -#: admin/class-admin-settings.php:451 -#: admin/class-admin-settings.php:473 -#: admin/class-admin-settings.php:494 +#: admin/class-admin-settings.php:231 admin/class-admin-settings.php:451 +#: admin/class-admin-settings.php:473 admin/class-admin-settings.php:494 #: admin/class-admin-settings.php:519 msgid "Network error." msgstr "Erro de rede." -#: admin/class-admin-settings.php:225 -#: admin/class-admin-settings.php:469 +#: admin/class-admin-settings.php:225 admin/class-admin-settings.php:469 #: admin/class-admin-settings.php:490 msgid "Update failed." msgstr "Falha na atualização." @@ -810,25 +786,21 @@ msgstr "Falha ao eliminar." msgid "Uploaded file is not accessible." msgstr "O ficheiro carregado não está acessível." -#: admin/class-admin-styles.php:87 -#: admin/class-admin-styles.php:106 +#: admin/class-admin-styles.php:87 admin/class-admin-styles.php:106 #: admin/class-admin-styles.php:130 msgid "Missing style id." msgstr "ID de estilo em falta." -#: admin/class-admin-settings.php:251 -#: admin/class-admin-styles.php:168 +#: admin/class-admin-settings.php:251 admin/class-admin-styles.php:168 msgid "Invalid or missing security token." msgstr "Token de segurança inválido ou em falta." -#: includes/class-download-button-renderer.php:107 -#: assets/js/elp-upload.js:180 +#: includes/class-download-button-renderer.php:107 assets/js/elp-upload.js:180 msgid "More download formats" msgstr "Mais formatos de transferência" #: includes/class-download-button-renderer.php:138 -#: includes/class-download-button-renderer.php:209 -#: assets/js/elp-upload.js:79 +#: includes/class-download-button-renderer.php:209 assets/js/elp-upload.js:79 msgid "Install the eXeLearning editor from the plugin settings page to enable this format." msgstr "Instale o editor eXeLearning na página de definições do plugin para ativar este formato." @@ -836,13 +808,11 @@ msgstr "Instale o editor eXeLearning na página de definições do plugin para a msgid "Preparing download…" msgstr "A preparar a transferência…" -#: includes/class-download-button-renderer.php:208 -#: assets/js/elp-upload.js:136 +#: includes/class-download-button-renderer.php:208 assets/js/elp-upload.js:136 msgid "Download failed. Please try again." msgstr "A transferência falhou. Tente novamente." -#: includes/class-download-formats.php:31 -#: assets/js/elp-upload.js:36 +#: includes/class-download-formats.php:31 assets/js/elp-upload.js:36 msgid "Download .elpx" msgstr "Transferir .elpx" @@ -948,8 +918,7 @@ msgstr "Entrada de arquivo insegura recusada durante a extração." msgid "Refused path traversal during extraction." msgstr "Travessia de caminho recusada durante a extração." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:297 includes/class-style-package.php:301 msgid "Failed to create a directory from the archive." msgstr "Falha ao criar um diretório a partir do arquivo." @@ -969,8 +938,7 @@ msgstr "config.xml não é XML válido." msgid "config.xml must declare a element." msgstr "config.xml deve declarar um elemento ." -#: includes/class-styles-service.php:244 -#: includes/class-styles-service.php:293 +#: includes/class-styles-service.php:244 includes/class-styles-service.php:293 msgid "Style not found." msgstr "Estilo não encontrado." @@ -1025,3 +993,43 @@ msgstr "Servir os recursos do pacote através do proxy do WordPress" #: admin/class-admin-settings.php:191 msgid "Use this option only if your web server returns incorrect MIME types for package assets, for example JavaScript files served as text/plain. When enabled, CSS, JavaScript, fonts, images and other package files are served through WordPress so the plugin can send explicit Content-Type headers. This can reduce performance because requests are handled by PHP instead of being served directly by the web server." msgstr "Utilize esta opção apenas se o seu servidor web devolver tipos MIME incorretos para os recursos do pacote, por exemplo ficheiros JavaScript servidos como text/plain. Quando ativada, os ficheiros CSS, JavaScript, tipos de letra, imagens e outros ficheiros do pacote são servidos através do WordPress para que o plugin possa enviar cabeçalhos Content-Type explícitos. Isto pode reduzir o desempenho porque os pedidos são processados pelo PHP em vez de serem servidos diretamente pelo servidor web." + +#: admin/class-admin-settings.php:104 +msgid "Settings saved." +msgstr "Definições guardadas." + +#: admin/class-admin-settings.php:111 +msgid "Security" +msgstr "Segurança" + +#: admin/class-admin-settings.php:117 +msgid "Iframe security mode" +msgstr "Modo de segurança do iframe" + +#: admin/class-admin-settings.php:122 +msgid "Secure (opaque-origin sandbox)" +msgstr "Seguro (sandbox de origem opaca)" + +#: admin/class-admin-settings.php:125 +msgid "Legacy (same-origin)" +msgstr "Legado (mesma origem)" + +#: admin/class-admin-settings.php:129 +msgid "Secure (recommended) isolates embedded content in an opaque-origin iframe. Legacy keeps same-origin behavior, needed only in some environments such as WordPress Playground." +msgstr "O modo seguro (recomendado) isola o conteúdo incorporado num iframe de origem opaca. O modo legado mantém a mesma origem, necessário apenas em alguns ambientes como o WordPress Playground." + +#: admin/class-admin-settings.php:134 +msgid "Save changes" +msgstr "Guardar alterações" + +#: admin/class-admin-settings.php:185 +msgid "When enabled, the content loads with teacher mode active." +msgstr "Quando ativado, o conteúdo carrega com o modo professor ativo." + +#: admin/class-admin-settings.php:190 +msgid "Whether the teacher-mode toggle button is shown." +msgstr "Define se o botão de alternância do modo professor é mostrado." + +#: assets/js/elp-upload.js:413 +msgid "Show Teacher Mode toggler" +msgstr "Mostrar o comutador do Modo Professor" diff --git a/languages/exelearning-ro_RO.mo b/languages/exelearning-ro_RO.mo index 99776f9..0b914c4 100644 Binary files a/languages/exelearning-ro_RO.mo and b/languages/exelearning-ro_RO.mo differ diff --git a/languages/exelearning-ro_RO.po b/languages/exelearning-ro_RO.po index a0b33da..ffcfd63 100644 --- a/languages/exelearning-ro_RO.po +++ b/languages/exelearning-ro_RO.po @@ -14,8 +14,7 @@ msgstr "" "X-Domain: exelearning\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2);\n" -#: admin/class-admin-settings.php:45 -#: public/views/elp-list.php:63 +#: admin/class-admin-settings.php:45 public/views/elp-list.php:63 #: assets/js/elp-upload.js:401 msgid "Settings" msgstr "Setări" @@ -24,20 +23,17 @@ msgstr "Setări" msgid "Date" msgstr "Data" -#: admin/class-admin-settings.php:350 -#: includes/class-elp-list-table.php:109 +#: admin/class-admin-settings.php:350 includes/class-elp-list-table.php:109 msgid "Delete" msgstr "Șterge" -#: admin/class-admin-settings.php:325 -#: admin/class-admin-settings.php:368 +#: admin/class-admin-settings.php:325 admin/class-admin-settings.php:368 #: includes/class-elp-list-table.php:49 msgid "Title" msgstr "Titlu" #. Plugin Name of the plugin -#: exelearning.php -#: admin/class-admin-settings.php:57 +#: exelearning.php admin/class-admin-settings.php:57 #: includes/class-mime-types.php:73 #: includes/integrations/class-media-library.php:381 msgid "eXeLearning" @@ -58,8 +54,7 @@ msgstr "Plugin pentru gestionarea fișierelor .elp eXeLearning în WordPress. Î msgid "INTEF" msgstr "INTEF" -#: admin/class-admin-settings.php:56 -#: admin/class-admin-settings.php:70 +#: admin/class-admin-settings.php:56 admin/class-admin-settings.php:70 msgid "eXeLearning Settings" msgstr "Setări eXeLearning" @@ -120,8 +115,7 @@ msgstr "Folosește acest fișier" msgid "Invalid nonce." msgstr "Nonce invalid." -#: admin/class-admin-settings.php:247 -#: admin/class-admin-styles.php:164 +#: admin/class-admin-settings.php:247 admin/class-admin-styles.php:164 #: admin/class-admin-upload.php:41 msgid "Insufficient permissions." msgstr "Permisiuni insuficiente." @@ -171,8 +165,7 @@ msgstr "Cale de fișier invalidă." msgid "File not found." msgstr "Fișier negăsit." -#: includes/class-content-proxy.php:176 -#: includes/class-content-proxy.php:184 +#: includes/class-content-proxy.php:176 includes/class-content-proxy.php:184 msgid "Access denied." msgstr "Acces refuzat." @@ -180,8 +173,7 @@ msgstr "Acces refuzat." msgid "This eXeLearning content is a source file and cannot be previewed directly." msgstr "Acest conținut eXeLearning este un fișier sursă și nu poate fi previzualizat direct." -#: includes/class-elp-upload-block.php:239 -#: public/class-shortcodes.php:190 +#: includes/class-elp-upload-block.php:239 public/class-shortcodes.php:190 msgid "Download file" msgstr "Descarcă fișierul" @@ -217,8 +209,7 @@ msgstr "Nu aveți permisiunea de a edita acest fișier." #: includes/class-exelearning-editor.php:208 #: includes/integrations/class-media-library.php:195 #: includes/integrations/class-media-library.php:349 -#: assets/js/elp-upload.js:423 -#: assets/js/elp-upload.js:463 +#: assets/js/elp-upload.js:423 assets/js/elp-upload.js:463 msgid "Edit in eXeLearning" msgstr "Editează în eXeLearning" @@ -232,14 +223,12 @@ msgstr "Închide" msgid "You do not have permission to read this file." msgstr "Nu aveți permisiunea de a citi acest fișier." -#: admin/class-admin-styles.php:42 -#: includes/class-exelearning-rest-api.php:227 +#: admin/class-admin-styles.php:42 includes/class-exelearning-rest-api.php:227 #: includes/class-exelearning-rest-api.php:581 msgid "No file uploaded." msgstr "Niciun fișier încărcat." -#: admin/class-admin-styles.php:50 -#: includes/class-exelearning-rest-api.php:239 +#: admin/class-admin-styles.php:50 includes/class-exelearning-rest-api.php:239 #: includes/class-exelearning-rest-api.php:592 msgid "File upload failed." msgstr "Încărcarea fișierului a eșuat." @@ -313,8 +302,7 @@ msgstr "Fișier eXeLearning negăsit." msgid "This is a source file that cannot be previewed directly. Download it to open with eXeLearning." msgstr "Acesta este un fișier sursă care nu poate fi previzualizat direct. Descărcați-l pentru a-l deschide cu eXeLearning." -#: public/class-shortcodes.php:277 -#: public/class-shortcodes.php:321 +#: public/class-shortcodes.php:277 public/class-shortcodes.php:321 msgid "Download source file" msgstr "Descarcă fișierul sursă" @@ -339,8 +327,7 @@ msgstr "Tip:" msgid "Preview in new tab" msgstr "Previzualizare într-o filă nouă" -#: assets/js/elp-upload.js:332 -#: assets/js/elp-upload.js:362 +#: assets/js/elp-upload.js:332 assets/js/elp-upload.js:362 #: assets/js/elp-upload.js:521 msgid "eXeLearning Content" msgstr "Conținut eXeLearning" @@ -419,13 +406,11 @@ msgstr "Editorul integrat este necesar pentru editarea fișierelor eXeLearning." msgid "Please install it using the button below." msgstr "Vă rugăm să îl instalați folosind butonul de mai jos." -#: admin/class-admin-settings.php:557 -#: admin/class-admin-settings.php:580 +#: admin/class-admin-settings.php:557 admin/class-admin-settings.php:580 msgid "Status:" msgstr "Stare:" -#: admin/class-admin-settings.php:328 -#: admin/class-admin-settings.php:558 +#: admin/class-admin-settings.php:328 admin/class-admin-settings.php:558 msgid "Installed" msgstr "Instalat" @@ -659,8 +644,7 @@ msgstr "Înălțimea previzualizării, în pixeli." msgid "When enabled, the teacher layer selector is offered so viewers can reveal teacher content." msgstr "Când este activat, selectorul stratului pentru profesori este oferit pentru ca vizitatorii să poată afișa conținutul pentru profesori." -#: admin/class-admin-settings.php:130 -#: assets/js/elp-upload.js:414 +#: admin/class-admin-settings.php:130 assets/js/elp-upload.js:414 msgid "If disabled, the teacher layer selector is hidden in the embedded eXeLearning content." msgstr "Dacă este dezactivat, selectorul stratului pentru profesori este ascuns în conținutul eXeLearning încorporat." @@ -738,20 +722,16 @@ msgstr "Stiluri încărcate" msgid "No uploaded styles yet." msgstr "Încă nu există stiluri încărcate." -#: admin/class-admin-settings.php:326 -#: admin/class-admin-settings.php:369 +#: admin/class-admin-settings.php:326 admin/class-admin-settings.php:369 msgid "Id" msgstr "Id" -#: admin/class-admin-settings.php:327 -#: admin/class-admin-settings.php:370 +#: admin/class-admin-settings.php:327 admin/class-admin-settings.php:370 msgid "Version" msgstr "Versiune" -#: admin/class-admin-settings.php:329 -#: admin/class-admin-settings.php:345 -#: admin/class-admin-settings.php:371 -#: admin/class-admin-settings.php:386 +#: admin/class-admin-settings.php:329 admin/class-admin-settings.php:345 +#: admin/class-admin-settings.php:371 admin/class-admin-settings.php:386 msgid "Enabled" msgstr "Activat" @@ -775,8 +755,7 @@ msgstr "Stilurile integrate dezactivate sunt ascunse în editor. Stilurile înc msgid "Uploading…" msgstr "Se încarcă…" -#: admin/class-admin-settings.php:445 -#: admin/class-admin-styles.php:71 +#: admin/class-admin-settings.php:445 admin/class-admin-styles.php:71 msgid "Style installed." msgstr "Stil instalat." @@ -784,16 +763,13 @@ msgstr "Stil instalat." msgid "Upload failed." msgstr "Încărcarea a eșuat." -#: admin/class-admin-settings.php:231 -#: admin/class-admin-settings.php:451 -#: admin/class-admin-settings.php:473 -#: admin/class-admin-settings.php:494 +#: admin/class-admin-settings.php:231 admin/class-admin-settings.php:451 +#: admin/class-admin-settings.php:473 admin/class-admin-settings.php:494 #: admin/class-admin-settings.php:519 msgid "Network error." msgstr "Eroare de rețea." -#: admin/class-admin-settings.php:225 -#: admin/class-admin-settings.php:469 +#: admin/class-admin-settings.php:225 admin/class-admin-settings.php:469 #: admin/class-admin-settings.php:490 msgid "Update failed." msgstr "Actualizarea a eșuat." @@ -814,25 +790,21 @@ msgstr "Ștergerea a eșuat." msgid "Uploaded file is not accessible." msgstr "Fișierul încărcat nu este accesibil." -#: admin/class-admin-styles.php:87 -#: admin/class-admin-styles.php:106 +#: admin/class-admin-styles.php:87 admin/class-admin-styles.php:106 #: admin/class-admin-styles.php:130 msgid "Missing style id." msgstr "Lipsește ID-ul stilului." -#: admin/class-admin-settings.php:251 -#: admin/class-admin-styles.php:168 +#: admin/class-admin-settings.php:251 admin/class-admin-styles.php:168 msgid "Invalid or missing security token." msgstr "Token de securitate invalid sau lipsă." -#: includes/class-download-button-renderer.php:107 -#: assets/js/elp-upload.js:180 +#: includes/class-download-button-renderer.php:107 assets/js/elp-upload.js:180 msgid "More download formats" msgstr "Mai multe formate de descărcare" #: includes/class-download-button-renderer.php:138 -#: includes/class-download-button-renderer.php:209 -#: assets/js/elp-upload.js:79 +#: includes/class-download-button-renderer.php:209 assets/js/elp-upload.js:79 msgid "Install the eXeLearning editor from the plugin settings page to enable this format." msgstr "Instalează editorul eXeLearning din pagina de setări a pluginului pentru a activa acest format." @@ -840,13 +812,11 @@ msgstr "Instalează editorul eXeLearning din pagina de setări a pluginului pent msgid "Preparing download…" msgstr "Se pregătește descărcarea…" -#: includes/class-download-button-renderer.php:208 -#: assets/js/elp-upload.js:136 +#: includes/class-download-button-renderer.php:208 assets/js/elp-upload.js:136 msgid "Download failed. Please try again." msgstr "Descărcarea a eșuat. Încercați din nou." -#: includes/class-download-formats.php:31 -#: assets/js/elp-upload.js:36 +#: includes/class-download-formats.php:31 assets/js/elp-upload.js:36 msgid "Download .elpx" msgstr "Descarcă .elpx" @@ -952,8 +922,7 @@ msgstr "Intrare de arhivă nesigură refuzată în timpul extragerii." msgid "Refused path traversal during extraction." msgstr "Traversare de cale refuzată în timpul extragerii." -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:297 includes/class-style-package.php:301 msgid "Failed to create a directory from the archive." msgstr "Crearea unui director din arhivă a eșuat." @@ -973,8 +942,7 @@ msgstr "config.xml nu este XML valid." msgid "config.xml must declare a element." msgstr "config.xml trebuie să declare un element ." -#: includes/class-styles-service.php:244 -#: includes/class-styles-service.php:293 +#: includes/class-styles-service.php:244 includes/class-styles-service.php:293 msgid "Style not found." msgstr "Stilul nu a fost găsit." @@ -1029,3 +997,43 @@ msgstr "Servește resursele pachetului prin proxy-ul WordPress" #: admin/class-admin-settings.php:191 msgid "Use this option only if your web server returns incorrect MIME types for package assets, for example JavaScript files served as text/plain. When enabled, CSS, JavaScript, fonts, images and other package files are served through WordPress so the plugin can send explicit Content-Type headers. This can reduce performance because requests are handled by PHP instead of being served directly by the web server." msgstr "Folosiți această opțiune numai dacă serverul dvs. web returnează tipuri MIME incorecte pentru resursele pachetului, de exemplu fișiere JavaScript servite ca text/plain. Când este activată, fișierele CSS, JavaScript, fonturile, imaginile și alte fișiere ale pachetului sunt servite prin WordPress pentru ca pluginul să poată trimite anteturi Content-Type explicite. Acest lucru poate reduce performanța deoarece cererile sunt gestionate de PHP în loc să fie servite direct de serverul web." + +#: admin/class-admin-settings.php:104 +msgid "Settings saved." +msgstr "Setări salvate." + +#: admin/class-admin-settings.php:111 +msgid "Security" +msgstr "Securitate" + +#: admin/class-admin-settings.php:117 +msgid "Iframe security mode" +msgstr "Modul de securitate al iframe-ului" + +#: admin/class-admin-settings.php:122 +msgid "Secure (opaque-origin sandbox)" +msgstr "Securizat (sandbox cu origine opacă)" + +#: admin/class-admin-settings.php:125 +msgid "Legacy (same-origin)" +msgstr "Moștenit (aceeași origine)" + +#: admin/class-admin-settings.php:129 +msgid "Secure (recommended) isolates embedded content in an opaque-origin iframe. Legacy keeps same-origin behavior, needed only in some environments such as WordPress Playground." +msgstr "Modul securizat (recomandat) izolează conținutul încorporat într-un iframe cu origine opacă. Modul moștenit păstrează aceeași origine, fiind necesar doar în anumite medii precum WordPress Playground." + +#: admin/class-admin-settings.php:134 +msgid "Save changes" +msgstr "Salvează modificările" + +#: admin/class-admin-settings.php:185 +msgid "When enabled, the content loads with teacher mode active." +msgstr "Când este activat, conținutul se încarcă cu modul profesor activ." + +#: admin/class-admin-settings.php:190 +msgid "Whether the teacher-mode toggle button is shown." +msgstr "Dacă este afișat butonul de comutare a modului profesor." + +#: assets/js/elp-upload.js:413 +msgid "Show Teacher Mode toggler" +msgstr "Afișează comutatorul Mod Profesor" diff --git a/languages/exelearning.pot b/languages/exelearning.pot index 7381a5a..4989d32 100644 --- a/languages/exelearning.pot +++ b/languages/exelearning.pot @@ -14,8 +14,7 @@ msgstr "" "X-Domain: exelearning\n" #. Plugin Name of the plugin -#: exelearning.php -#: admin/class-admin-settings.php:57 +#: exelearning.php admin/class-admin-settings.php:57 #: includes/class-mime-types.php:73 #: includes/integrations/class-media-library.php:381 msgid "eXeLearning" @@ -41,14 +40,12 @@ msgstr "" msgid "https://exelearning.net/" msgstr "" -#: admin/class-admin-settings.php:45 -#: public/views/elp-list.php:63 +#: admin/class-admin-settings.php:45 public/views/elp-list.php:63 #: assets/js/elp-upload.js:401 msgid "Settings" msgstr "" -#: admin/class-admin-settings.php:56 -#: admin/class-admin-settings.php:70 +#: admin/class-admin-settings.php:56 admin/class-admin-settings.php:70 msgid "eXeLearning Settings" msgstr "" @@ -92,8 +89,7 @@ msgstr "" msgid "When enabled, the teacher layer selector is offered so viewers can reveal teacher content." msgstr "" -#: admin/class-admin-settings.php:130 -#: assets/js/elp-upload.js:414 +#: admin/class-admin-settings.php:130 assets/js/elp-upload.js:414 msgid "If disabled, the teacher layer selector is hidden in the embedded eXeLearning content." msgstr "" @@ -141,28 +137,23 @@ msgstr "" msgid "Use this option only if your web server returns incorrect MIME types for package assets, for example JavaScript files served as text/plain. When enabled, CSS, JavaScript, fonts, images and other package files are served through WordPress so the plugin can send explicit Content-Type headers. This can reduce performance because requests are handled by PHP instead of being served directly by the web server." msgstr "" -#: admin/class-admin-settings.php:225 -#: admin/class-admin-settings.php:469 +#: admin/class-admin-settings.php:225 admin/class-admin-settings.php:469 #: admin/class-admin-settings.php:490 msgid "Update failed." msgstr "" -#: admin/class-admin-settings.php:231 -#: admin/class-admin-settings.php:451 -#: admin/class-admin-settings.php:473 -#: admin/class-admin-settings.php:494 +#: admin/class-admin-settings.php:231 admin/class-admin-settings.php:451 +#: admin/class-admin-settings.php:473 admin/class-admin-settings.php:494 #: admin/class-admin-settings.php:519 msgid "Network error." msgstr "" -#: admin/class-admin-settings.php:247 -#: admin/class-admin-styles.php:164 +#: admin/class-admin-settings.php:247 admin/class-admin-styles.php:164 #: admin/class-admin-upload.php:41 msgid "Insufficient permissions." msgstr "" -#: admin/class-admin-settings.php:251 -#: admin/class-admin-styles.php:168 +#: admin/class-admin-settings.php:251 admin/class-admin-styles.php:168 msgid "Invalid or missing security token." msgstr "" @@ -208,31 +199,25 @@ msgstr "" msgid "No uploaded styles yet." msgstr "" -#: admin/class-admin-settings.php:325 -#: admin/class-admin-settings.php:368 +#: admin/class-admin-settings.php:325 admin/class-admin-settings.php:368 #: includes/class-elp-list-table.php:49 msgid "Title" msgstr "" -#: admin/class-admin-settings.php:326 -#: admin/class-admin-settings.php:369 +#: admin/class-admin-settings.php:326 admin/class-admin-settings.php:369 msgid "Id" msgstr "" -#: admin/class-admin-settings.php:327 -#: admin/class-admin-settings.php:370 +#: admin/class-admin-settings.php:327 admin/class-admin-settings.php:370 msgid "Version" msgstr "" -#: admin/class-admin-settings.php:328 -#: admin/class-admin-settings.php:558 +#: admin/class-admin-settings.php:328 admin/class-admin-settings.php:558 msgid "Installed" msgstr "" -#: admin/class-admin-settings.php:329 -#: admin/class-admin-settings.php:345 -#: admin/class-admin-settings.php:371 -#: admin/class-admin-settings.php:386 +#: admin/class-admin-settings.php:329 admin/class-admin-settings.php:345 +#: admin/class-admin-settings.php:371 admin/class-admin-settings.php:386 msgid "Enabled" msgstr "" @@ -240,8 +225,7 @@ msgstr "" msgid "Actions" msgstr "" -#: admin/class-admin-settings.php:350 -#: includes/class-elp-list-table.php:109 +#: admin/class-admin-settings.php:350 includes/class-elp-list-table.php:109 msgid "Delete" msgstr "" @@ -261,8 +245,7 @@ msgstr "" msgid "Uploading…" msgstr "" -#: admin/class-admin-settings.php:445 -#: admin/class-admin-styles.php:71 +#: admin/class-admin-settings.php:445 admin/class-admin-styles.php:71 msgid "Style installed." msgstr "" @@ -294,8 +277,7 @@ msgstr "" msgid "Please install it using the button below." msgstr "" -#: admin/class-admin-settings.php:557 -#: admin/class-admin-settings.php:580 +#: admin/class-admin-settings.php:557 admin/class-admin-settings.php:580 msgid "Status:" msgstr "" @@ -354,14 +336,12 @@ msgstr "" msgid "Network error. Please check your connection and try again." msgstr "" -#: admin/class-admin-styles.php:42 -#: includes/class-exelearning-rest-api.php:227 +#: admin/class-admin-styles.php:42 includes/class-exelearning-rest-api.php:227 #: includes/class-exelearning-rest-api.php:581 msgid "No file uploaded." msgstr "" -#: admin/class-admin-styles.php:50 -#: includes/class-exelearning-rest-api.php:239 +#: admin/class-admin-styles.php:50 includes/class-exelearning-rest-api.php:239 #: includes/class-exelearning-rest-api.php:592 msgid "File upload failed." msgstr "" @@ -370,8 +350,7 @@ msgstr "" msgid "Uploaded file is not accessible." msgstr "" -#: admin/class-admin-styles.php:87 -#: admin/class-admin-styles.php:106 +#: admin/class-admin-styles.php:87 admin/class-admin-styles.php:106 #: admin/class-admin-styles.php:130 msgid "Missing style id." msgstr "" @@ -425,19 +404,16 @@ msgstr "" msgid "File not found." msgstr "" -#: includes/class-content-proxy.php:176 -#: includes/class-content-proxy.php:184 +#: includes/class-content-proxy.php:176 includes/class-content-proxy.php:184 msgid "Access denied." msgstr "" -#: includes/class-download-button-renderer.php:107 -#: assets/js/elp-upload.js:180 +#: includes/class-download-button-renderer.php:107 assets/js/elp-upload.js:180 msgid "More download formats" msgstr "" #: includes/class-download-button-renderer.php:138 -#: includes/class-download-button-renderer.php:209 -#: assets/js/elp-upload.js:79 +#: includes/class-download-button-renderer.php:209 assets/js/elp-upload.js:79 msgid "Install the eXeLearning editor from the plugin settings page to enable this format." msgstr "" @@ -445,13 +421,11 @@ msgstr "" msgid "Preparing download…" msgstr "" -#: includes/class-download-button-renderer.php:208 -#: assets/js/elp-upload.js:136 +#: includes/class-download-button-renderer.php:208 assets/js/elp-upload.js:136 msgid "Download failed. Please try again." msgstr "" -#: includes/class-download-formats.php:31 -#: assets/js/elp-upload.js:36 +#: includes/class-download-formats.php:31 assets/js/elp-upload.js:36 msgid "Download .elpx" msgstr "" @@ -516,8 +490,7 @@ msgstr "" msgid "Error: eXeLearning content not found" msgstr "" -#: includes/class-elp-upload-block.php:239 -#: public/class-shortcodes.php:190 +#: includes/class-elp-upload-block.php:239 public/class-shortcodes.php:190 msgid "Download file" msgstr "" @@ -557,8 +530,7 @@ msgstr "" #: includes/class-exelearning-editor.php:208 #: includes/integrations/class-media-library.php:195 #: includes/integrations/class-media-library.php:349 -#: assets/js/elp-upload.js:423 -#: assets/js/elp-upload.js:463 +#: assets/js/elp-upload.js:423 assets/js/elp-upload.js:463 msgid "Edit in eXeLearning" msgstr "" @@ -788,8 +760,7 @@ msgstr "" msgid "Refused path traversal during extraction." msgstr "" -#: includes/class-style-package.php:297 -#: includes/class-style-package.php:301 +#: includes/class-style-package.php:297 includes/class-style-package.php:301 msgid "Failed to create a directory from the archive." msgstr "" @@ -809,8 +780,7 @@ msgstr "" msgid "config.xml must declare a element." msgstr "" -#: includes/class-styles-service.php:244 -#: includes/class-styles-service.php:293 +#: includes/class-styles-service.php:244 includes/class-styles-service.php:293 msgid "Style not found." msgstr "" @@ -938,8 +908,7 @@ msgstr "" msgid "This is a source file that cannot be previewed directly. Download it to open with eXeLearning." msgstr "" -#: public/class-shortcodes.php:277 -#: public/class-shortcodes.php:321 +#: public/class-shortcodes.php:277 public/class-shortcodes.php:321 msgid "Download source file" msgstr "" @@ -975,8 +944,7 @@ msgstr "" msgid "EPUB3 (.epub)" msgstr "" -#: assets/js/elp-upload.js:332 -#: assets/js/elp-upload.js:362 +#: assets/js/elp-upload.js:332 assets/js/elp-upload.js:362 #: assets/js/elp-upload.js:521 msgid "eXeLearning Content" msgstr "" @@ -1024,3 +992,43 @@ msgstr "" #: assets/js/elp-upload.js:546 msgid "This is an eXeLearning v2 source file. The content will be displayed on the frontend if exported HTML is available." msgstr "" + +#: admin/class-admin-settings.php:104 +msgid "Settings saved." +msgstr "" + +#: admin/class-admin-settings.php:111 +msgid "Security" +msgstr "" + +#: admin/class-admin-settings.php:117 +msgid "Iframe security mode" +msgstr "" + +#: admin/class-admin-settings.php:122 +msgid "Secure (opaque-origin sandbox)" +msgstr "" + +#: admin/class-admin-settings.php:125 +msgid "Legacy (same-origin)" +msgstr "" + +#: admin/class-admin-settings.php:129 +msgid "Secure (recommended) isolates embedded content in an opaque-origin iframe. Legacy keeps same-origin behavior, needed only in some environments such as WordPress Playground." +msgstr "" + +#: admin/class-admin-settings.php:134 +msgid "Save changes" +msgstr "" + +#: admin/class-admin-settings.php:185 +msgid "When enabled, the content loads with teacher mode active." +msgstr "" + +#: admin/class-admin-settings.php:190 +msgid "Whether the teacher-mode toggle button is shown." +msgstr "" + +#: assets/js/elp-upload.js:413 +msgid "Show Teacher Mode toggler" +msgstr "" diff --git a/package-lock.json b/package-lock.json index 016e821..36f0341 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,43 @@ "devDependencies": { "@playwright/test": "^1.61.0", "@wordpress/env": "^11.8.1", - "nunjucks": "^3.2.4" + "happy-dom": "^20.10.2", + "nunjucks": "^3.2.4", + "vitest": "^4.1.8" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@inquirer/ansi": { @@ -442,6 +478,13 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, "node_modules/@kwsites/file-exists": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", @@ -484,6 +527,25 @@ "dev": true, "license": "MIT" }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, "node_modules/@nodable/entities": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", @@ -1057,6 +1119,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@oxc-project/types": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", + "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@php-wasm/cli-util": { "version": "3.1.13", "resolved": "https://registry.npmjs.org/@php-wasm/cli-util/-/cli-util-3.1.13.tgz", @@ -1385,6 +1457,288 @@ "node": ">=18" } }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", + "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", + "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", + "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", + "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", + "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", + "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", + "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", + "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", + "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", + "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", + "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", + "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", + "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", + "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", + "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, "node_modules/@simple-git/args-pathspec": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@simple-git/args-pathspec/-/args-pathspec-1.0.3.tgz", @@ -1415,6 +1769,13 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -1428,6 +1789,17 @@ "node": ">=10" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/aws-lambda": { "version": "8.10.161", "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.161.tgz", @@ -1455,6 +1827,31 @@ "@types/responselike": "^1.0.0" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", @@ -1510,6 +1907,136 @@ "@types/node": "*" } }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", + "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", + "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", + "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", + "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.9", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", + "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.9", + "@vitest/utils": "4.1.9", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", + "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", + "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.9", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@wordpress/env": { "version": "11.8.1", "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-11.8.1.tgz", @@ -1879,6 +2406,16 @@ "dev": true, "license": "MIT" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/async-lock": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", @@ -1985,6 +2522,19 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/buffer-image-size": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/buffer-image-size/-/buffer-image-size-0.6.4.tgz", + "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -2074,6 +2624,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2282,6 +2842,13 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -2442,6 +3009,16 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/diff3": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.4.tgz", @@ -2528,6 +3105,19 @@ "once": "^1.4.0" } }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2548,6 +3138,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -2602,6 +3199,16 @@ "node": ">=4" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2612,6 +3219,16 @@ "node": ">= 0.6" } }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "4.22.0", "resolved": "https://registry.npmjs.org/express/-/express-4.22.0.tgz", @@ -2705,6 +3322,24 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/finalhandler": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", @@ -2964,6 +3599,47 @@ "dev": true, "license": "ISC" }, + "node_modules/happy-dom": { + "version": "20.10.6", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.10.6.tgz", + "integrity": "sha512-6QD0ilzDDt93tX44y8tbmZdAcdTRYDhUP+Asgi6pC8Pp5IA3cvaZGyoVN/EGtlq9ziT65iPuBBn3ASLr6hCgVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": ">=20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "@types/ws": "^8.18.1", + "buffer-image-size": "^0.6.4", + "entities": "^7.0.1", + "whatwg-mimetype": "^3.0.0", + "ws": "^8.21.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/happy-dom/node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3304,25 +3980,298 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/lodash.includes": { @@ -3476,6 +4425,16 @@ "node": "18 >=18.20 || 20 || >=22" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3642,6 +4601,25 @@ "dev": true, "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -3704,6 +4682,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/octokit": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.1.2.tgz", @@ -3900,6 +4892,33 @@ "dev": true, "license": "MIT" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -3952,6 +4971,35 @@ "node": ">= 0.4" } }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -4147,6 +5195,40 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rolldown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", + "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.137.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.3", + "@rolldown/binding-darwin-arm64": "1.1.3", + "@rolldown/binding-darwin-x64": "1.1.3", + "@rolldown/binding-freebsd-x64": "1.1.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", + "@rolldown/binding-linux-arm64-gnu": "1.1.3", + "@rolldown/binding-linux-arm64-musl": "1.1.3", + "@rolldown/binding-linux-ppc64-gnu": "1.1.3", + "@rolldown/binding-linux-s390x-gnu": "1.1.3", + "@rolldown/binding-linux-x64-gnu": "1.1.3", + "@rolldown/binding-linux-x64-musl": "1.1.3", + "@rolldown/binding-openharmony-arm64": "1.1.3", + "@rolldown/binding-wasm32-wasi": "1.1.3", + "@rolldown/binding-win32-arm64-msvc": "1.1.3", + "@rolldown/binding-win32-x64-msvc": "1.1.3" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4391,6 +5473,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -4494,6 +5583,16 @@ "dev": true, "license": "MIT" }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -4501,6 +5600,13 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -4511,6 +5617,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -4644,6 +5757,50 @@ "node": ">=8" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tmp": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", @@ -4689,6 +5846,14 @@ "node": ">=0.6" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -4800,6 +5965,189 @@ "node": ">= 0.8" } }, + "node_modules/vite": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz", + "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "~1.1.2", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/vitest": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", + "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.9", + "@vitest/mocker": "4.1.9", + "@vitest/pretty-format": "4.1.9", + "@vitest/runner": "4.1.9", + "@vitest/snapshot": "4.1.9", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.9", + "@vitest/browser-preview": "4.1.9", + "@vitest/browser-webdriverio": "4.1.9", + "@vitest/coverage-istanbul": "4.1.9", + "@vitest/coverage-v8": "4.1.9", + "@vitest/ui": "4.1.9", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, "node_modules/wasm-feature-detect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", @@ -4817,6 +6165,16 @@ "defaults": "^1.0.3" } }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4855,6 +6213,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/package.json b/package.json index 4a960b4..b4fa1de 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,18 @@ "devDependencies": { "@playwright/test": "^1.61.0", "@wordpress/env": "^11.8.1", - "nunjucks": "^3.2.4" + "happy-dom": "^20.10.2", + "nunjucks": "^3.2.4", + "vitest": "^4.1.8" }, "scripts": { "wp-env": "wp-env", "composer": "wp-env run cli --env-cwd=wp-content/plugins/exelearning composer", "composer:tests-cli": "wp-env run tests-cli --env-cwd=wp-content/plugins/exelearning composer", "test:unit": "wp-env run tests-cli --env-cwd=wp-content/plugins/exelearning ./vendor/bin/phpunit", + "test:js": "vitest run", "test:e2e": "playwright test", + "test:e2e:embed": "playwright test -c playwright-embed.config.cjs", "build": "echo 'Use make build-editor instead for static editor build'", "package": "npm run package:zip", "package:zip": "git archive --format=zip --output=wp-exelearning.zip HEAD", diff --git a/playwright-embed.config.cjs b/playwright-embed.config.cjs new file mode 100644 index 0000000..deb429f --- /dev/null +++ b/playwright-embed.config.cjs @@ -0,0 +1,11 @@ +const { defineConfig, devices } = require('@playwright/test'); +const PORT = 8127; +module.exports = defineConfig({ + testDir: 'tests/e2e', + testMatch: 'embed.spec.cjs', + timeout: 30000, + fullyParallel: false, + use: { baseURL: 'http://localhost:' + PORT }, + webServer: { command: 'python3 -m http.server ' + PORT, port: PORT, reuseExistingServer: false, timeout: 30000 }, + projects: [{ name: 'firefox', use: { ...devices['Desktop Firefox'] } }], +}); diff --git a/playwright.config.js b/playwright.config.js index 0563ec5..9ed8545 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -7,6 +7,10 @@ /** @type {import('@playwright/test').PlaywrightTestConfig} */ const config = { testDir: './tests/e2e', + // The external-embed e2e runs in Firefox against its own static harness via + // playwright-embed.config.cjs (npm run test:e2e:embed); it must not be picked up + // by this WordPress (chromium / wp-env) config. + testIgnore: '**/embed.spec.cjs', timeout: 30000, expect: { timeout: 5000, diff --git a/public/class-shortcodes.php b/public/class-shortcodes.php index ddba178..6ad0691 100644 --- a/public/class-shortcodes.php +++ b/public/class-shortcodes.php @@ -313,13 +313,18 @@ private function render_preview( $title, $preview_url, $height, $file_url, $teac $unique_id = 'exelearning-' . wp_unique_id(); $is_poster = '' !== $poster_url; + // In secure mode the content is opaque, so whitelisted external embeds are + // promoted to this page (no-op in legacy, where they already work inline). + ExeLearning_Iframe_Sandbox::enqueue_embed_relay(); + // Teacher-mode visibility is owned by eXeLearning core: exported packages hide // teacher-only content by default and expose an in-page "teacher layer" selector // through ?exe-teacher=1 (the selector appears but stays off until the viewer // turns it on). No host-side CSS/JS injection is needed — we carry the request on // the iframe src whenever this embed should offer the selector. The legacy // teacher_mode attribute (activate-on-load) folds into the same opt-in, since - // core deliberately no longer auto-reveals from the URL. + // core deliberately no longer auto-reveals from the URL. This rides through the + // secure-mode proxy too (the package reads its own location.search). if ( $teacher_mode_visible || $teacher_mode ) { $preview_url .= ( false === strpos( $preview_url, '?' ) ? '?' : '&' ) . 'exe-teacher=1'; } @@ -361,13 +366,14 @@ class="exelearning-iframe" title="%s" loading="lazy" allow="fullscreen" - sandbox="allow-scripts allow-same-origin allow-popups" + sandbox="%s" referrerpolicy="no-referrer" >', $iframe_src_attr, $height, $is_poster ? ' display: none;' : '', - esc_attr( $title ) + esc_attr( $title ), + esc_attr( ExeLearning_Iframe_Sandbox::sandbox_tokens() ) ); return sprintf( diff --git a/tests/e2e/embed.spec.cjs b/tests/e2e/embed.spec.cjs new file mode 100644 index 0000000..c1544b9 --- /dev/null +++ b/tests/e2e/embed.spec.cjs @@ -0,0 +1,20 @@ +// Cross-browser (Firefox) e2e for the secure-mode external embeds (DEC-0061): the real +// in-iframe shim + parent relay promote every cross-origin / PDF iframe to a sandboxed +// inline player on the parent page (open mode, the default). Mirrors the canonical +// mod_exelearning e2e. +const { test, expect } = require('@playwright/test'); +test('promotes every cross-origin/PDF iframe to a sandboxed inline player (open mode, Firefox)', async ({ page }) => { + await page.goto('/tests/e2e/embed/parent.html'); + const players = page.locator('.exe-embed-overlay iframe'); + await expect.poll(() => players.count(), { timeout: 15000 }).toBe(3); + const srcs = await players.evaluateAll((els) => els.map((e) => e.src)); + // Open mode: cross-origin https iframes are promoted VERBATIM (no host list). + expect(srcs.some((s) => /^https:\/\/www\.youtube-nocookie\.com\/embed\/aqz-KE-bpKQ\b/.test(s))).toBe(true); + // An arbitrary cross-origin provider is promoted too (the structural invariant). + expect(srcs.some((s) => /^https:\/\/example\.com\//.test(s))).toBe(true); + // The relative local PDF is reported absolute and rendered. + expect(srcs.some((s) => /\/local\.pdf$/.test(s))).toBe(true); + // The promoted video players are sandboxed (allow-same-origin to render, no top-navigation). + const sandboxes = await players.evaluateAll((els) => els.map((e) => e.getAttribute('sandbox') || '')); + expect(sandboxes.some((s) => s.includes('allow-same-origin') && !s.includes('allow-top-navigation'))).toBe(true); +}); diff --git a/tests/e2e/embed/content.html b/tests/e2e/embed/content.html new file mode 100644 index 0000000..42c3d92 --- /dev/null +++ b/tests/e2e/embed/content.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tests/e2e/embed/local.pdf b/tests/e2e/embed/local.pdf new file mode 100644 index 0000000..314e46d Binary files /dev/null and b/tests/e2e/embed/local.pdf differ diff --git a/tests/e2e/embed/parent.html b/tests/e2e/embed/parent.html new file mode 100644 index 0000000..0a7ac5d --- /dev/null +++ b/tests/e2e/embed/parent.html @@ -0,0 +1,8 @@ + + + + + +
+ + diff --git a/tests/js/exe_embed.test.js b/tests/js/exe_embed.test.js new file mode 100644 index 0000000..2be6177 --- /dev/null +++ b/tests/js/exe_embed.test.js @@ -0,0 +1,243 @@ +// Unit tests for the secure-mode external-embed relay (DEC-0061). The relay (parent) is +// the authoritative gate: in 'open' mode the structural invariant (https + cross-origin +// to the host), in 'strict' mode the maintained host allowlist. This file MIRRORS the +// RELAY describe-blocks of the canonical mod_exelearning suite (tests/js/exe_embed.test.js) +// so drift in the validate()/makePlayer()/sync() logic is caught here too. The relay is a +// require()-able dual-export module; globals come from vitest.config.mts. +const relay = require( '../../assets/js/exe-embed-relay.js' ); + +const HOSTS = [ + 'www.youtube.com', 'youtube.com', 'www.youtube-nocookie.com', + 'youtube-nocookie.com', 'player.vimeo.com', 'vimeo.com', + 'www.dailymotion.com', 'dailymotion.com', 'geo.dailymotion.com', + 'mediateca.educa.madrid.org', +]; +const STRICT = { strict: true, whitelist: relay.buildWhitelist( HOSTS ) }; +const ORIGIN = window.location.origin; // happy-dom default (the "host" origin here). +const CONTENT_SRC = ORIGIN + '/wp-json/exelearning/v1/content/' + 'a'.repeat( 40 ) + '/index.html'; + +describe( 'exe_embed_relay validate() — open mode (default): structural invariant', () => { + it( 'accepts any cross-origin https video iframe verbatim (no host list, no reconstruction)', () => { + expect( relay.validate( 'https://www.youtube.com/embed/aqz-KE-bpKQ', CONTENT_SRC ) ) + .toEqual( { url: 'https://www.youtube.com/embed/aqz-KE-bpKQ', kind: 'video' } ); + expect( relay.validate( 'https://some-new-provider.example/player/42', CONTENT_SRC ) ) + .toEqual( { url: 'https://some-new-provider.example/player/42', kind: 'video' } ); + } ); + + it( 'rejects same-origin (the host page itself)', () => { + expect( relay.validate( ORIGIN + '/wp-admin/index.php', CONTENT_SRC ) ).toBeNull(); + } ); + + it( 'rejects non-https', () => { + expect( relay.validate( 'http://www.youtube.com/embed/aqz-KE-bpKQ', CONTENT_SRC ) ).toBeNull(); + } ); + + it( 'rejects userinfo (https://evil.com@youtube.com/...)', () => { + expect( relay.validate( 'https://evil.com@www.youtube.com/embed/aqz-KE-bpKQ', CONTENT_SRC ) ).toBeNull(); + } ); + + it( 'rejects IP-literal and loopback/local hosts', () => { + expect( relay.validate( 'https://1.2.3.4/player', CONTENT_SRC ) ).toBeNull(); + expect( relay.validate( 'https://[2001:db8::1]/player', CONTENT_SRC ) ).toBeNull(); + expect( relay.validate( 'https://localhost/player', CONTENT_SRC ) ).toBeNull(); + expect( relay.validate( 'https://intranet.local/player', CONTENT_SRC ) ).toBeNull(); + } ); + + it( 'rejects non-http(s) schemes (data:/javascript:/blob:)', () => { + expect( relay.validate( 'data:text/html,

x

', CONTENT_SRC ) ).toBeNull(); + expect( relay.validate( 'javascript:alert(1)', CONTENT_SRC ) ).toBeNull(); + expect( relay.validate( 'blob:https://x.test/uuid', CONTENT_SRC ) ).toBeNull(); + } ); + + it( 'rejects a relative URL (the shim must report absolute)', () => { + expect( relay.validate( 'files/local.pdf', CONTENT_SRC ) ).toBeNull(); + expect( relay.validate( '/admin/secret', CONTENT_SRC ) ).toBeNull(); + } ); +} ); + +describe( 'exe_embed_relay validate() — PDFs (always allowed by structure)', () => { + it( 'accepts any cross-origin https PDF (no sameorigin flag)', () => { + expect( relay.validate( 'https://example.com/docs/report.pdf', CONTENT_SRC ) ) + .toEqual( { url: 'https://example.com/docs/report.pdf', kind: 'pdf' } ); + } ); + + it( 'accepts a same-origin PDF under the content directory (flagged sameorigin)', () => { + const pdf = ORIGIN + '/wp-json/exelearning/v1/content/' + 'a'.repeat( 40 ) + '/files/local.pdf'; + expect( relay.validate( pdf, CONTENT_SRC ) ).toEqual( { url: pdf, kind: 'pdf', sameorigin: true } ); + } ); + + it( 'rejects a same-origin PDF outside the package (e.g. an admin route)', () => { + expect( relay.validate( ORIGIN + '/wp-admin/secret.pdf', CONTENT_SRC ) ).toBeNull(); + } ); + + it( 'rejects an http PDF', () => { + expect( relay.validate( 'http://example.com/x.pdf', CONTENT_SRC ) ).toBeNull(); + } ); +} ); + +describe( 'exe_embed_relay validate() — strict mode (opt-in allowlist)', () => { + it( 'rebuilds the canonical youtube-nocookie URL from a youtube.com embed', () => { + expect( relay.validate( 'https://www.youtube.com/embed/aqz-KE-bpKQ', CONTENT_SRC, STRICT ) ) + .toEqual( { url: 'https://www.youtube-nocookie.com/embed/aqz-KE-bpKQ', kind: 'video' } ); + } ); + + it( 'rebuilds the canonical Vimeo / Dailymotion / EducaMadrid URLs', () => { + expect( relay.validate( 'https://player.vimeo.com/video/76979871', CONTENT_SRC, STRICT ).url ) + .toBe( 'https://player.vimeo.com/video/76979871' ); + expect( relay.validate( 'https://www.dailymotion.com/embed/video/x8abc12', CONTENT_SRC, STRICT ).url ) + .toBe( 'https://www.dailymotion.com/embed/video/x8abc12' ); + expect( relay.validate( 'https://mediateca.educa.madrid.org/video/u555bvi3bk5wsabh', CONTENT_SRC, STRICT ).url ) + .toBe( 'https://mediateca.educa.madrid.org/video/u555bvi3bk5wsabh/fs' ); + } ); + + it( 'rejects a non-whitelisted cross-origin https host (unlike open mode)', () => { + expect( relay.validate( 'https://some-new-provider.example/player/42', CONTENT_SRC, STRICT ) ).toBeNull(); + expect( relay.validate( 'https://example.com/', CONTENT_SRC, STRICT ) ).toBeNull(); + } ); + + it( 'rejects look-alike hosts and malformed ids', () => { + expect( relay.validate( 'https://www.youtube.com.evil.com/embed/aqz-KE-bpKQ', CONTENT_SRC, STRICT ) ).toBeNull(); + expect( relay.validate( 'https://www.youtube.com/embed/', CONTENT_SRC, STRICT ) ).toBeNull(); + expect( relay.validate( 'https://player.vimeo.com/video/not-a-number', CONTENT_SRC, STRICT ) ).toBeNull(); + } ); + + it( 'still accepts cross-origin PDFs in strict mode', () => { + expect( relay.validate( 'https://example.com/x.pdf', CONTENT_SRC, STRICT ) ) + .toEqual( { url: 'https://example.com/x.pdf', kind: 'pdf' } ); + } ); +} ); + +describe( 'exe_embed_relay structural helpers', () => { + it( 'isIpOrLocalHost flags IP literals and loopback/local names', () => { + [ '1.2.3.4', '255.0.0.1', '[::1]', '[2001:db8::1]', 'localhost', 'x.localhost', 'host.local', '' ].forEach( + ( h ) => expect( relay.isIpOrLocalHost( h ) ).toBe( true ) + ); + [ 'youtube.com', 'player.vimeo.com', 'example.org' ].forEach( + ( h ) => expect( relay.isIpOrLocalHost( h ) ).toBe( false ) + ); + } ); + + it( 'isRelatedToLms flags the host, its subdomains and superdomains (dotted boundary)', () => { + expect( relay.isRelatedToLms( 'host.example.org', 'host.example.org' ) ).toBe( true ); // equal + expect( relay.isRelatedToLms( 'cdn.host.example.org', 'host.example.org' ) ).toBe( true ); // subdomain + expect( relay.isRelatedToLms( 'example.org', 'host.example.org' ) ).toBe( true ); // superdomain + expect( relay.isRelatedToLms( 'evil-host.example.org', 'host.example.org' ) ).toBe( false ); // look-alike + expect( relay.isRelatedToLms( 'youtube.com', 'host.example.org' ) ).toBe( false ); + } ); + + it( 'isRelatedToLms normalises the trailing-dot FQDN-root form (no host. bypass)', () => { + // 'host.example.org.' resolves to the same vhost but compares unequal as a raw + // string; without normalisation it would slip past the related-to-host gate and + // be promoted as a cross-origin player with allow-same-origin. + expect( relay.isRelatedToLms( 'host.example.org.', 'host.example.org' ) ).toBe( true ); // dotted host + expect( relay.isRelatedToLms( 'host.example.org', 'host.example.org.' ) ).toBe( true ); // dotted lmsHost + expect( relay.isRelatedToLms( 'cdn.host.example.org.', 'host.example.org' ) ).toBe( true ); // dotted subdomain + expect( relay.normalizeHost( 'Host.Example.ORG.' ) ).toBe( 'host.example.org' ); + } ); +} ); + +describe( 'exe_embed_relay makePlayer() — sandboxed players', () => { + it( 'video player is sandboxed with allow-same-origin but NOT top-navigation/modals', () => { + const frame = relay.makePlayer( { url: 'https://www.youtube.com/embed/abc123', kind: 'video' } ); + const sb = frame.getAttribute( 'sandbox' ); + expect( sb ).toContain( 'allow-scripts' ); + expect( sb ).toContain( 'allow-same-origin' ); // cross-origin src keeps its own origin; renders. + expect( sb ).not.toContain( 'allow-top-navigation' ); + expect( sb ).not.toContain( 'allow-modals' ); + expect( frame.getAttribute( 'data-exe-embed-player' ) ).toBe( '1' ); // excluded from message auth + expect( frame.getAttribute( 'allow' ) ).toContain( 'autoplay' ); + expect( frame.getAttribute( 'referrerpolicy' ) ).toBe( 'strict-origin-when-cross-origin' ); + } ); + + it( 'PDF player is NOT sandboxed (the browser PDF viewer fails inside a sandbox)', () => { + const frame = relay.makePlayer( { url: 'https://files.test/manual.pdf', kind: 'pdf' } ); + expect( frame.hasAttribute( 'sandbox' ) ).toBe( false ); + expect( frame.getAttribute( 'referrerpolicy' ) ).toBe( 'no-referrer' ); + } ); +} ); + +describe( 'exe_embed_relay createRelay() overlays players from messages', () => { + let iframe; + beforeEach( () => { + document.body.innerHTML = ''; + iframe = document.createElement( 'iframe' ); + document.body.appendChild( iframe ); + } ); + + it( 'creates an inline overlay player for a valid embed and removes it when no longer reported', () => { + const r = relay.createRelay( { mode: 'open' } ); + r.onMessage( { + source: iframe.contentWindow, + data: { + type: 'exe-embed', action: 'sync', + embeds: [ { id: 'e1', url: 'https://www.youtube.com/embed/abc123', x: 0, y: 0, w: 480, h: 270 } ], + }, + } ); + const players = document.querySelectorAll( '.exe-embed-overlay iframe' ); + expect( players.length ).toBe( 1 ); + expect( players[ 0 ].src ).toMatch( /www\.youtube\.com\/embed\/abc123$/ ); // verbatim in open mode + + r.onMessage( { source: iframe.contentWindow, data: { type: 'exe-embed', action: 'sync', embeds: [] } } ); + expect( document.querySelectorAll( '.exe-embed-overlay iframe' ).length ).toBe( 0 ); + } ); + + it( 'replaces the player when a reused embed id navigates to a different URL (no lingering video)', () => { + const r = relay.createRelay( { mode: 'open' } ); + r.onMessage( { + source: iframe.contentWindow, + data: { + type: 'exe-embed', action: 'sync', + embeds: [ { id: 'exe-embed-1', url: 'https://www.youtube.com/embed/abc123', x: 0, y: 0, w: 480, h: 270 } ], + }, + } ); + expect( document.querySelector( '.exe-embed-overlay iframe' ).src ).toMatch( /www\.youtube\.com\/embed\/abc123$/ ); + + r.onMessage( { + source: iframe.contentWindow, + data: { + type: 'exe-embed', action: 'sync', + embeds: [ { id: 'exe-embed-1', url: 'https://player.vimeo.com/video/12345', x: 0, y: 0, w: 425, h: 350 } ], + }, + } ); + const players = document.querySelectorAll( '.exe-embed-overlay iframe' ); + expect( players.length ).toBe( 1 ); + expect( players[ 0 ].src ).toMatch( /player\.vimeo\.com\/video\/12345$/ ); + expect( players[ 0 ].src ).not.toMatch( /youtube/ ); + } ); + + it( 'never treats a promoted player as a content source (forged-message defence)', () => { + const r = relay.createRelay( { mode: 'open' } ); + // A sandboxed player with allow-same-origin must not be able to impersonate the + // content iframe and inject embeds: tag an iframe like a player and verify a + // message from it is ignored. + const player = document.createElement( 'iframe' ); + player.setAttribute( 'data-exe-embed-player', '1' ); + document.body.appendChild( player ); + r.onMessage( { + source: player.contentWindow, + data: { + type: 'exe-embed', action: 'sync', + embeds: [ { id: 'x', url: 'https://evil.example/phish', x: 0, y: 0, w: 100, h: 100 } ], + }, + } ); + expect( document.querySelectorAll( '.exe-embed-overlay iframe' ).length ).toBe( 0 ); + } ); + + it( 'ignores a message whose source is not a known content iframe', () => { + const r = relay.createRelay( { mode: 'open' } ); + r.onMessage( { + source: {}, + data: { + type: 'exe-embed', action: 'sync', + embeds: [ { id: 'x', url: 'https://www.youtube.com/embed/abc123', x: 0, y: 0, w: 1, h: 1 } ], + }, + } ); + expect( document.querySelectorAll( '.exe-embed-overlay iframe' ).length ).toBe( 0 ); + } ); + + it( 'ignores non-embed messages', () => { + const r = relay.createRelay( { mode: 'open' } ); + r.onMessage( { source: iframe.contentWindow, data: { type: 'scorm', action: 'track', cmi: {} } } ); + expect( document.querySelectorAll( '.exe-embed-overlay iframe' ).length ).toBe( 0 ); + } ); +} ); diff --git a/tests/unit/ContentProxyTest.php b/tests/unit/ContentProxyTest.php index d714de4..696fc22 100644 --- a/tests/unit/ContentProxyTest.php +++ b/tests/unit/ContentProxyTest.php @@ -1041,6 +1041,68 @@ public function test_content_origin_rejects_non_bare_origin() { remove_filter( 'exelearning_content_origin', $cb ); } + /** + * Secure mode appends a `sandbox` directive so the document stays opaque even when + * opened outside the iframe (new tab / raw URL navigation). + */ + public function test_build_html_csp_secure_adds_sandbox() { + $method = new ReflectionMethod( ExeLearning_Content_Proxy::class, 'build_html_csp' ); + $method->setAccessible( true ); + + $csp = $method->invoke( $this->proxy, "'self'", true ); + + // The CSP sandbox must mirror the secure iframe tokens, incl. allow-forms, or + // form-based iDevices are blocked by the CSP even though the iframe allows them. + $this->assertStringContainsString( 'sandbox allow-scripts allow-popups allow-forms', $csp ); + $this->assertStringContainsString( "default-src 'self'", $csp ); + } + + /** + * Legacy mode keeps the previous policy with no sandbox directive. + */ + public function test_build_html_csp_legacy_has_no_sandbox() { + $method = new ReflectionMethod( ExeLearning_Content_Proxy::class, 'build_html_csp' ); + $method->setAccessible( true ); + + $csp = $method->invoke( $this->proxy, "'self'", false ); + + $this->assertStringNotContainsString( 'sandbox', $csp ); + $this->assertStringContainsString( "default-src 'self'", $csp ); + } + + /** + * In secure mode the served HTML gets the external-embed shim inlined. + */ + public function test_inject_embed_shim_adds_shim_in_secure_mode() { + $method = new ReflectionMethod( ExeLearning_Content_Proxy::class, 'inject_embed_shim' ); + $method->setAccessible( true ); + + $html = '

content

'; + $out = $method->invoke( $this->proxy, $html ); + + $this->assertStringContainsString( 'id="exelearning-embed-shim"', $out ); + // The shim source itself is inlined. + $this->assertStringContainsString( 'data-exe-embed-id', $out ); + // Injected before the closing body tag. + $this->assertStringContainsString( '', $out ); + } + + /** + * Legacy mode leaves the served HTML untouched (no shim). + */ + public function test_inject_embed_shim_is_noop_in_legacy_mode() { + update_option( ExeLearning_Iframe_Sandbox::OPTION, 'legacy' ); + + $method = new ReflectionMethod( ExeLearning_Content_Proxy::class, 'inject_embed_shim' ); + $method->setAccessible( true ); + + $html = '

content

'; + $out = $method->invoke( $this->proxy, $html ); + + $this->assertSame( $html, $out ); + $this->assertStringNotContainsString( 'exelearning-embed-shim', $out ); + } + /** * The MIME map serves ES modules (.mjs) as JavaScript so module scripts * execute under strict MIME checking (issue #53). diff --git a/tests/unit/ElpUploadBlockTest.php b/tests/unit/ElpUploadBlockTest.php index 10b73cf..aa41bf2 100644 --- a/tests/unit/ElpUploadBlockTest.php +++ b/tests/unit/ElpUploadBlockTest.php @@ -179,6 +179,61 @@ public function test_iframe_has_sandbox() { $this->assertStringContainsString( 'allow-scripts', $result ); } + /** + * By default (secure mode) the block iframe is opaque-origin (no allow-same-origin). + */ + public function test_block_sandbox_secure_default() { + $attachment_id = $this->factory->attachment->create(); + $hash = str_repeat( 'e', 40 ); + update_post_meta( $attachment_id, '_exelearning_extracted', $hash ); + update_post_meta( $attachment_id, '_exelearning_has_preview', '1' ); + + $result = $this->block->render_block( array( 'attachmentId' => $attachment_id ) ); + + $this->assertStringNotContainsString( 'allow-same-origin', $result ); + } + + /** + * Legacy mode keeps the same-origin sandbox token on the block iframe. + */ + public function test_block_sandbox_legacy_mode() { + update_option( ExeLearning_Iframe_Sandbox::OPTION, ExeLearning_Iframe_Sandbox::MODE_LEGACY ); + + $attachment_id = $this->factory->attachment->create(); + $hash = str_repeat( 'f', 40 ); + update_post_meta( $attachment_id, '_exelearning_extracted', $hash ); + update_post_meta( $attachment_id, '_exelearning_has_preview', '1' ); + + $result = $this->block->render_block( array( 'attachmentId' => $attachment_id ) ); + + $this->assertStringContainsString( 'allow-same-origin', $result ); + } + + /** + * In secure mode the teacher selector is offered on the iframe src via ?exe-teacher=1 + * (read by the package from its own URL), with no contentDocument injection and no + * legacy exe-teacher-toggler parameter. + */ + public function test_block_secure_offers_selector_via_query() { + update_option( ExeLearning_Iframe_Sandbox::OPTION, ExeLearning_Iframe_Sandbox::MODE_SECURE ); + + $attachment_id = $this->factory->attachment->create(); + $hash = str_repeat( 'a', 40 ); + update_post_meta( $attachment_id, '_exelearning_extracted', $hash ); + update_post_meta( $attachment_id, '_exelearning_has_preview', '1' ); + + $result = $this->block->render_block( + array( + 'attachmentId' => $attachment_id, + 'teacherModeVisible' => true, + ) + ); + + $this->assertStringContainsString( 'exe-teacher=1', $result ); + $this->assertStringNotContainsString( 'exe-teacher-toggler', $result ); + $this->assertStringNotContainsString( 'contentDocument', $result ); + } + /** * Test iframe has referrerpolicy. */ diff --git a/tests/unit/IframeSandboxTest.php b/tests/unit/IframeSandboxTest.php new file mode 100644 index 0000000..200b5d6 --- /dev/null +++ b/tests/unit/IframeSandboxTest.php @@ -0,0 +1,131 @@ +assertSame( 'secure', ExeLearning_Iframe_Sandbox::mode() ); + $this->assertTrue( ExeLearning_Iframe_Sandbox::is_secure() ); + $this->assertSame( 'allow-scripts allow-popups allow-forms', ExeLearning_Iframe_Sandbox::sandbox_tokens() ); + $this->assertStringNotContainsString( 'allow-same-origin', ExeLearning_Iframe_Sandbox::sandbox_tokens() ); + // allow-forms is required so the form-based iDevices can submit in the sandbox. + $this->assertStringContainsString( 'allow-forms', ExeLearning_Iframe_Sandbox::sandbox_tokens() ); + } + + /** + * Legacy mode restores the same-origin token. + */ + public function test_legacy_mode() { + update_option( ExeLearning_Iframe_Sandbox::OPTION, 'legacy' ); + + $this->assertSame( 'legacy', ExeLearning_Iframe_Sandbox::mode() ); + $this->assertFalse( ExeLearning_Iframe_Sandbox::is_secure() ); + $this->assertStringContainsString( 'allow-same-origin', ExeLearning_Iframe_Sandbox::sandbox_tokens() ); + $this->assertStringContainsString( 'allow-forms', ExeLearning_Iframe_Sandbox::sandbox_tokens() ); + $this->assertStringContainsString( 'allow-popups-to-escape-sandbox', ExeLearning_Iframe_Sandbox::sandbox_tokens() ); + } + + /** + * Any value other than "legacy" fails safe to secure. + */ + public function test_invalid_mode_falls_back_to_secure() { + update_option( ExeLearning_Iframe_Sandbox::OPTION, 'garbage' ); + + $this->assertSame( 'secure', ExeLearning_Iframe_Sandbox::mode() ); + $this->assertTrue( ExeLearning_Iframe_Sandbox::is_secure() ); + } + + /** + * With no option set, the embed policy defaults to open (DEC-0061): any + * cross-origin https iframe is promoted, no host list required. + */ + public function test_default_embed_mode_is_open() { + $this->assertSame( 'open', ExeLearning_Iframe_Sandbox::embed_mode() ); + $this->assertSame( + ExeLearning_Iframe_Sandbox::EMBED_OPEN, + ExeLearning_Iframe_Sandbox::embed_mode() + ); + } + + /** + * Setting the embed policy to strict is honored. + */ + public function test_embed_mode_strict() { + update_option( ExeLearning_Iframe_Sandbox::EMBED_OPTION, 'strict' ); + + $this->assertSame( 'strict', ExeLearning_Iframe_Sandbox::embed_mode() ); + $this->assertSame( + ExeLearning_Iframe_Sandbox::EMBED_STRICT, + ExeLearning_Iframe_Sandbox::embed_mode() + ); + } + + /** + * Any value other than "open" fails safe to strict (toward the more restrictive + * policy), so a tampered option never silently weakens the gate. + */ + public function test_invalid_embed_mode_falls_back_to_strict() { + update_option( ExeLearning_Iframe_Sandbox::EMBED_OPTION, 'garbage' ); + + $this->assertSame( 'strict', ExeLearning_Iframe_Sandbox::embed_mode() ); + } + + /** + * The default whitelist covers the YouTube and Vimeo embed hosts. + */ + public function test_embed_whitelist_contains_default_video_hosts() { + $hosts = ExeLearning_Iframe_Sandbox::embed_whitelist(); + + $this->assertContains( 'www.youtube.com', $hosts ); + $this->assertContains( 'youtube-nocookie.com', $hosts ); + $this->assertContains( 'player.vimeo.com', $hosts ); + $this->assertContains( 'www.dailymotion.com', $hosts ); + $this->assertContains( 'mediateca.educa.madrid.org', $hosts ); + } + + /** + * The whitelist is filterable and the result is lowercased, trimmed and de-duplicated. + */ + public function test_embed_whitelist_is_filterable_and_normalized() { + $callback = function ( $hosts ) { + $hosts[] = ' Example.ORG '; + $hosts[] = 'player.vimeo.com'; // Duplicate of a default. + return $hosts; + }; + add_filter( 'exelearning_embed_whitelist', $callback ); + $hosts = ExeLearning_Iframe_Sandbox::embed_whitelist(); + remove_filter( 'exelearning_embed_whitelist', $callback ); + + $this->assertContains( 'example.org', $hosts ); + $this->assertNotContains( ' Example.ORG ', $hosts ); + $this->assertSame( array_values( array_unique( $hosts ) ), $hosts ); + } + + /** + * The embed relay is enqueued in secure mode only. + */ + public function test_enqueue_embed_relay_only_in_secure() { + ExeLearning_Iframe_Sandbox::enqueue_embed_relay(); + $this->assertTrue( wp_script_is( ExeLearning_Iframe_Sandbox::HANDLE_RELAY, 'enqueued' ) ); + + // Reset, switch to legacy, and confirm it is not enqueued. + wp_dequeue_script( ExeLearning_Iframe_Sandbox::HANDLE_RELAY ); + wp_deregister_script( ExeLearning_Iframe_Sandbox::HANDLE_RELAY ); + update_option( ExeLearning_Iframe_Sandbox::OPTION, 'legacy' ); + + ExeLearning_Iframe_Sandbox::enqueue_embed_relay(); + $this->assertFalse( wp_script_is( ExeLearning_Iframe_Sandbox::HANDLE_RELAY, 'enqueued' ) ); + } +} diff --git a/tests/unit/ShortcodesTest.php b/tests/unit/ShortcodesTest.php index 4f1db95..cc053d7 100644 --- a/tests/unit/ShortcodesTest.php +++ b/tests/unit/ShortcodesTest.php @@ -101,7 +101,7 @@ public function test_display_exelearning_renders_iframe() { } /** - * Test iframe has sandbox attribute for security. + * Test the iframe is sandboxed and, by default (secure mode), opaque-origin. */ public function test_iframe_has_sandbox_attribute() { $attachment_id = $this->factory->attachment->create(); @@ -113,14 +113,79 @@ public function test_iframe_has_sandbox_attribute() { $this->assertStringContainsString( 'sandbox=', $result ); $this->assertStringContainsString( 'allow-scripts', $result ); - // allow-same-origin is required for the eXeLearning viewer (a same-origin - // app) to render inside the iframe. - $this->assertStringContainsString( 'allow-same-origin', $result ); + // Secure is the default: no allow-same-origin, so the content runs in an + // opaque origin and cannot reach this page. + $this->assertStringNotContainsString( 'allow-same-origin', $result ); // allow-modals is intentionally NOT granted so the preview cannot raise // "Leave site?" dialogs. $this->assertStringNotContainsString( 'allow-modals', $result ); } + /** + * Legacy mode keeps the same-origin sandbox token. + */ + public function test_iframe_sandbox_legacy_mode_keeps_same_origin() { + update_option( ExeLearning_Iframe_Sandbox::OPTION, ExeLearning_Iframe_Sandbox::MODE_LEGACY ); + + $attachment_id = $this->factory->attachment->create(); + $hash = str_repeat( 'c', 40 ); + update_post_meta( $attachment_id, '_exelearning_extracted', $hash ); + update_post_meta( $attachment_id, '_exelearning_has_preview', '1' ); + + $result = $this->shortcodes->display_exelearning( array( 'id' => $attachment_id ) ); + + $this->assertStringContainsString( 'allow-same-origin', $result ); + } + + /** + * In secure mode the teacher selector is offered on the iframe src via ?exe-teacher=1 + * (read by the package from its own URL), with no contentDocument injection and no + * legacy exe-teacher-toggler parameter. + */ + public function test_secure_mode_carries_teacher_params_without_contentdocument() { + $attachment_id = $this->factory->attachment->create(); + $hash = str_repeat( 'd', 40 ); + update_post_meta( $attachment_id, '_exelearning_extracted', $hash ); + update_post_meta( $attachment_id, '_exelearning_has_preview', '1' ); + + $result = $this->shortcodes->display_exelearning( + array( + 'id' => $attachment_id, + 'teacher_mode' => '1', + 'teacher_mode_visible' => '0', + ) + ); + + $this->assertStringContainsString( 'exe-teacher=1', $result ); + $this->assertStringNotContainsString( 'exe-teacher-toggler', $result ); + $this->assertStringNotContainsString( 'contentDocument', $result ); + } + + /** + * In legacy mode the teacher selector is offered the same way as secure mode — + * through ?exe-teacher=1 on the iframe src — and the former same-origin + * contentDocument injection has been retired (core owns teacher mode now). + */ + public function test_legacy_mode_carries_teacher_param_without_contentdocument() { + update_option( ExeLearning_Iframe_Sandbox::OPTION, ExeLearning_Iframe_Sandbox::MODE_LEGACY ); + + $attachment_id = $this->factory->attachment->create(); + $hash = str_repeat( 'e', 40 ); + update_post_meta( $attachment_id, '_exelearning_extracted', $hash ); + update_post_meta( $attachment_id, '_exelearning_has_preview', '1' ); + + $result = $this->shortcodes->display_exelearning( + array( + 'id' => $attachment_id, + 'teacher_mode' => '1', + 'teacher_mode_visible' => '0', + ) + ); + + $this->assertStringContainsString( 'exe-teacher=1', $result ); + $this->assertStringNotContainsString( 'contentDocument', $result ); + } + /** * Test iframe has referrerpolicy attribute for security. */ diff --git a/vitest.config.mts b/vitest.config.mts new file mode 100644 index 0000000..dfd3e4d --- /dev/null +++ b/vitest.config.mts @@ -0,0 +1,16 @@ +import { defineConfig } from 'vitest/config'; + +// Vitest config for the plugin's JavaScript unit tests. Scope: the secure-mode +// external-embed relay (assets/js/exe-embed-relay.js). The relay is a MIRROR of the +// canonical mod_exelearning source; these tests mirror its RELAY describe-blocks so +// drift in the validate()/makePlayer()/sync() logic is caught here too. The auto-running +// shim (assets/js/exe-embed-shim.js) is not unit-tested here. +export default defineConfig( { + test: { + globals: true, + // happy-dom gives the relay a window/document for createRelay() and the overlay + // DOM work, matching the canonical embedder's own setup. + environment: 'happy-dom', + include: [ 'tests/js/**/*.test.js' ], + }, +} );