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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 122 additions & 5 deletions assets/js/exelearning-media-modal.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
/* eXeLearning Media Modal - Updated 2026-02-02 22:50 */
/* eXeLearning Media Modal - Updated 2026-06-02 (process-as-eXeLearning button) */
jQuery( document ).ready( function( $ ) {

// Localized strings from PHP (via wp_localize_script)
var strings = window.exelearningMediaStrings || {};

// REST settings for the one-click "Process as eXeLearning" action.
var settings = window.exelearningMediaSettings || {};

// Cache buster to avoid stale iframe content
var cacheBuster = Date.now();

Expand Down Expand Up @@ -153,6 +156,11 @@ jQuery( document ).ready( function( $ ) {
}

if ( ! attachment || ! attachment.get( 'exelearning' ) ) {
// Unprocessed eXeLearning candidate (e.g. a .zip or a not-yet-processed
// .elpx): offer a one-click "Process as eXeLearning" button.
if ( attachment && attachment.get( 'exelearningReprocessable' ) ) {
addProcessButtonToDetails( attachment, $detailsThumbnail );
}
return;
}

Expand Down Expand Up @@ -307,6 +315,112 @@ jQuery( document ).ready( function( $ ) {
$container.after( $editButton );
}

// Build a "Process as eXeLearning" button element.
function makeProcessButton( extraClass, extraStyle ) {
var $btn = $( '<button type="button"></button>' )
.addClass( 'button button-primary ' + extraClass )
.attr( 'style', extraStyle )
.text( strings.processAsExe || 'Process as eXeLearning' );
$( '<span class="dashicons dashicons-update" style="vertical-align: middle; margin-right: 5px;"></span>' ).prependTo( $btn );
return $btn;
}

// Show an inline error under an anchor element (never use blocking dialogs).
function showProcessError( $anchor, message ) {
$anchor.siblings( '.exelearning-process-error' ).remove();
$( '<div class="exelearning-process-error" style="margin-top: 8px; color: #b32d2e; font-size: 12px;"></div>' )
.text( message )
.insertAfter( $anchor );
}

// Call the REST reprocess endpoint for an attachment, then refresh the modal.
function reprocessAttachment( attachmentId, $button ) {
if ( ! settings.restUrl || ! window.fetch ) {
return;
}

var originalHtml = $button.html();
$button.prop( 'disabled', true ).text( strings.processing || 'Processing…' );

fetch( settings.restUrl + '/reprocess/' + attachmentId, {
method: 'POST',
headers: { 'X-WP-Nonce': settings.nonce || '' },
credentials: 'same-origin'
} ).then( function( resp ) {
return resp.json().then( function( body ) {
return { ok: resp.ok, body: body };
} );
} ).then( function( res ) {
var failed = ! res.ok || ( res.body && res.body.code && true !== res.body.success );
if ( failed ) {
var msg = ( res.body && res.body.message ) ? res.body.message : ( strings.processFailed || 'This file could not be processed as eXeLearning.' );
$button.prop( 'disabled', false ).html( originalHtml );
showProcessError( $button, msg );
return;
}

// Success: refresh the attachment so its prepared data now carries the
// eXeLearning preview/edit info, then re-render the modal.
var attachment = wp.media.attachment( attachmentId );
attachment.set( 'exelearningReprocessable', false );
attachment.fetch().always( function() {
$( '.exelearning-process-button, .exelearning-process-button-actions, .exelearning-process-hint, .exelearning-process-error' ).remove();
$( '.attachment-details .thumbnail' ).removeClass( 'exelearning-details-preview-added exelearning-details-no-preview' );
$( '.attachment-preview.type-application .thumbnail' ).removeClass( 'exelearning-preview-added exelearning-no-preview' );
runAllUpdates();
} );
} ).catch( function() {
$button.prop( 'disabled', false ).html( originalHtml );
showProcessError( $button, strings.processFailed || 'This file could not be processed as eXeLearning.' );
} );
}

// Add the process button + hint under the single-column details thumbnail.
function addProcessButtonToDetails( attachment, $container ) {
if ( $container.siblings( '.exelearning-process-button' ).length > 0 ) {
return;
}

var attachmentId = attachment.get( 'id' );
var $button = makeProcessButton( 'exelearning-process-button', 'margin-top: 10px; width: 100%;' );
var $hint = $( '<div class="exelearning-process-hint" style="margin-top: 8px; font-size: 12px; color: #646970;"></div>' )
.text( strings.notProcessed || 'eXeLearning file (not processed yet)' );

$button.on( 'click', function( e ) {
e.preventDefault();
reprocessAttachment( attachmentId, $button );
} );

$container.after( $button );
$button.after( $hint );
}

// Add the process button into the two-column attachment-info actions row.
function insertProcessButtonInActions( attachment, $attachmentInfo ) {
if ( attachment.get( 'exelearning' ) || ! attachment.get( 'exelearningReprocessable' ) ) {
return;
}

if ( $attachmentInfo.find( '.exelearning-process-button-actions' ).length > 0 ) {
return;
}

var $actions = $attachmentInfo.find( '.actions' );
if ( $actions.length === 0 ) {
return;
}

var attachmentId = attachment.get( 'id' );
var $button = makeProcessButton( 'exelearning-process-button-actions', 'display: inline-block; margin-bottom: 10px; padding: 6px 12px; font-size: 13px;' );

$button.on( 'click', function( e ) {
e.preventDefault();
reprocessAttachment( attachmentId, $button );
} );

$actions.prepend( $button );
}

// Function to add "Edit in eXeLearning" button to the two-column attachment details view
function addEditButtonToAttachmentInfo() {
var $attachmentInfo = $( '.attachment-info' );
Expand Down Expand Up @@ -385,12 +499,15 @@ jQuery( document ).ready( function( $ ) {
var attachment = wp.media.attachment( attachmentId );

// Wait for the attachment to be fetched if needed
var applyButtons = function() {
insertEditButtonInActions( attachment, $attachmentInfo );
insertProcessButtonInActions( attachment, $attachmentInfo );
};

if ( ! attachment.get( 'id' ) ) {
attachment.fetch().done( function() {
insertEditButtonInActions( attachment, $attachmentInfo );
});
attachment.fetch().done( applyButtons );
} else {
insertEditButtonInActions( attachment, $attachmentInfo );
applyButtons();
}
}

Expand Down
13 changes: 11 additions & 2 deletions exelearning.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,25 @@
// Integration classes.
require_once EXELEARNING_PLUGIN_DIR . 'includes/integrations/class-media-library.php';

// ELP File Service (validates, parses and extracts .elp files).
require_once EXELEARNING_PLUGIN_DIR . 'includes/class-elp-file-service.php';

// Reprocessor for existing attachments (reused by the REST API and entry points).
require_once EXELEARNING_PLUGIN_DIR . 'includes/class-elp-reprocessor.php';

// Editor classes.
require_once EXELEARNING_PLUGIN_DIR . 'includes/class-exelearning-editor.php';
require_once EXELEARNING_PLUGIN_DIR . 'includes/class-export-bootstrap.php';
require_once EXELEARNING_PLUGIN_DIR . 'includes/class-exelearning-rest-api.php';
require_once EXELEARNING_PLUGIN_DIR . 'includes/class-content-proxy.php';
require_once EXELEARNING_PLUGIN_DIR . 'includes/class-static-editor-installer.php';

// ELP File Service (validates, parses and extracts .elp files).
require_once EXELEARNING_PLUGIN_DIR . 'includes/class-elp-file-service.php';

// WP-CLI commands (batch reprocessing for large sites).
if ( defined( 'WP_CLI' ) && WP_CLI ) {
require_once EXELEARNING_PLUGIN_DIR . 'includes/class-cli-command.php';
WP_CLI::add_command( 'exelearning', 'ExeLearning_CLI_Command' );
}

// Register activation and deactivation hooks.
register_activation_hook( __FILE__, array( 'ExeLearning_Activator', 'activate' ) );
Expand Down
106 changes: 106 additions & 0 deletions includes/class-cli-command.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php
/**
* WP-CLI commands for the eXeLearning plugin.
*
* @package Exelearning
*/

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

/**
* Class ExeLearning_CLI_Command.
*
* Provides `wp exelearning <command>` for batch maintenance on large sites.
* Registered only when WP-CLI is loaded (see exelearning.php).
*/
class ExeLearning_CLI_Command {

/**
* (Re)process existing .elpx attachments so they become previewable.
*
* Useful when the plugin is installed after .elpx files were already
* uploaded, or when files were stored through a flow (e.g. Formidable Forms)
* that bypassed the upload handler.
*
* ## OPTIONS
*
* [--id=<id>]
* : Reprocess a single attachment by ID.
*
* [--all]
* : Reprocess every .elpx attachment that still needs it (no extraction yet,
* or a missing extraction directory).
*
* [--force]
* : With --all, reprocess every .elpx attachment even if it already has a
* valid extraction.
*
* ## EXAMPLES
*
* wp exelearning reprocess --id=123
* wp exelearning reprocess --all
* wp exelearning reprocess --all --force
*
* @param array $args Positional arguments (unused).
* @param array $assoc_args Associative arguments.
*/
public function reprocess( $args, $assoc_args ) {
unset( $args );

$reprocessor = new ExeLearning_Reprocessor();

$id = isset( $assoc_args['id'] ) ? absint( $assoc_args['id'] ) : 0;
$all = isset( $assoc_args['all'] );
$force = isset( $assoc_args['force'] );

if ( ! $id && ! $all ) {
WP_CLI::error( 'Specify --id=<id> or --all.' );
}

if ( $id ) {
$ids = array( $id );
} elseif ( $force ) {
$ids = $reprocessor->get_candidate_attachment_ids();
} else {
$ids = $reprocessor->get_reprocessable_attachment_ids();
}

if ( empty( $ids ) ) {
WP_CLI::success( 'No eXeLearning files needed reprocessing.' );
return;
}

$ok = 0;
$failed = 0;
$skipped = 0;

foreach ( $ids as $one ) {
if ( ! $reprocessor->is_eligible( $one ) ) {
WP_CLI::warning( sprintf( 'Skipped #%d: not eXeLearning content (.elpx or a .zip containing content.xml).', $one ) );
++$skipped;
continue;
}

$result = $reprocessor->reprocess( $one );

if ( is_wp_error( $result ) ) {
WP_CLI::warning( sprintf( 'Failed #%d: %s', $one, $result->get_error_message() ) );
++$failed;
continue;
}

WP_CLI::log(
sprintf(
'Reprocessed #%d (%s).',
$one,
$result['has_preview'] ? 'previewable' : 'no preview'
)
);
++$ok;
}

WP_CLI::success( sprintf( '%d reprocessed, %d failed, %d skipped.', $ok, $failed, $skipped ) );
}
}
Loading