/** * WC Licensed Product - Version Management * * @package Jeremias\WcLicensedProduct */ (function($) { 'use strict'; var WCLicensedProductVersions = { mediaFrame: null, init: function() { this.bindEvents(); }, bindEvents: function() { $('#add-version-btn').on('click', this.addVersion); $(document).on('click', '.delete-version-btn', this.deleteVersion); $(document).on('click', '.toggle-version-btn', this.toggleVersion); // Media uploader events $('#upload-version-file-btn').on('click', this.openMediaUploader.bind(this)); $('#remove-version-file-btn').on('click', this.removeSelectedFile); // Checksum file events $('#select-checksum-file-btn').on('click', this.triggerChecksumFileSelect); $('#new_checksum_file').on('change', this.onChecksumFileSelected); $('#remove-checksum-file-btn').on('click', this.removeChecksumFile); // Listen for product type changes $('#product-type').on('change', this.onProductTypeChange); // Initial check for product type (handles page load for new products) this.onProductTypeChange(); }, onProductTypeChange: function() { var productType = $('#product-type').val(); var $metaBox = $('#wc_licensed_product_versions'); if (productType === 'licensed') { $metaBox.show(); } else { $metaBox.hide(); } }, /** * Open WordPress media uploader */ openMediaUploader: function(e) { e.preventDefault(); var self = this; // If frame already exists, open it if (this.mediaFrame) { this.mediaFrame.open(); return; } // Create media frame this.mediaFrame = wp.media({ title: wcLicensedProductVersions.strings.selectFile, button: { text: wcLicensedProductVersions.strings.useThisFile }, multiple: false, library: { type: ['application/zip', 'application/x-zip-compressed', 'application/octet-stream'] } }); // When file is selected this.mediaFrame.on('select', function() { var attachment = self.mediaFrame.state().get('selection').first().toJSON(); // Set attachment ID $('#new_attachment_id').val(attachment.id); // Show filename $('#selected_file_name').text(attachment.filename); $('#remove-version-file-btn').show(); // Show SHA256 hash field $('#sha256-hash-row').show(); // Try to extract version from filename var extractedVersion = self.extractVersionFromFilename(attachment.filename); if (extractedVersion && !$('#new_version').val().trim()) { $('#new_version').val(extractedVersion); } }); this.mediaFrame.open(); }, /** * Remove selected file */ removeSelectedFile: function(e) { e.preventDefault(); $('#new_attachment_id').val(''); $('#selected_file_name').text(''); $('#remove-version-file-btn').hide(); // Hide and clear checksum file field $('#sha256-hash-row').hide(); $('#new_checksum_file').val(''); $('#selected_checksum_name').text(''); $('#remove-checksum-file-btn').hide(); }, /** * Trigger checksum file input click */ triggerChecksumFileSelect: function(e) { e.preventDefault(); $('#new_checksum_file').trigger('click'); }, /** * Handle checksum file selection */ onChecksumFileSelected: function(e) { var file = e.target.files[0]; if (file) { $('#selected_checksum_name').text(file.name); $('#remove-checksum-file-btn').show(); } else { $('#selected_checksum_name').text(''); $('#remove-checksum-file-btn').hide(); } }, /** * Remove selected checksum file */ removeChecksumFile: function(e) { e.preventDefault(); $('#new_checksum_file').val(''); $('#selected_checksum_name').text(''); $('#remove-checksum-file-btn').hide(); }, /** * Read checksum from uploaded file * Supports formats: "hash filename" or just "hash" */ readChecksumFile: function(file) { return new Promise(function(resolve, reject) { if (!file) { resolve(''); return; } var reader = new FileReader(); reader.onload = function(e) { var content = e.target.result.trim(); // Extract hash from content (format: "hash filename" or just "hash") var match = content.match(/^([a-fA-F0-9]{64})/); if (match) { resolve(match[1].toLowerCase()); } else { reject(new Error(wcLicensedProductVersions.strings.invalidChecksumFile || 'Invalid checksum file format')); } }; reader.onerror = function() { reject(new Error(wcLicensedProductVersions.strings.checksumReadError || 'Failed to read checksum file')); }; reader.readAsText(file); }); }, /** * Extract version from filename * Supports patterns like: plugin-v1.2.3.zip, plugin-1.2.3.zip, v1.2.3.zip */ extractVersionFromFilename: function(filename) { // Remove extension var basename = filename.replace(/\.[^/.]+$/, ''); // Try various patterns var patterns = [ /[_-]v?(\d+\.\d+\.\d+)$/i, // ends with -v1.2.3 or -1.2.3 /[_-]v?(\d+\.\d+\.\d+)[_-]/i, // contains -v1.2.3- or -1.2.3- /^v?(\d+\.\d+\.\d+)$/i // just version number ]; for (var i = 0; i < patterns.length; i++) { var match = basename.match(patterns[i]); if (match) { return match[1]; } } return null; }, addVersion: function(e) { e.preventDefault(); var self = WCLicensedProductVersions; var $btn = $(this); var $spinner = $btn.siblings('.spinner'); var productId = $btn.data('product-id'); var version = $('#new_version').val().trim(); var releaseNotes = $('#new_release_notes').val().trim(); var attachmentId = $('#new_attachment_id').val(); var checksumFile = $('#new_checksum_file')[0].files[0]; // Validate version if (!version) { alert(wcLicensedProductVersions.strings.versionRequired); return; } if (!/^\d+\.\d+\.\d+$/.test(version)) { alert(wcLicensedProductVersions.strings.versionInvalid); return; } $btn.prop('disabled', true); $spinner.addClass('is-active'); // Read checksum file if provided, then submit self.readChecksumFile(checksumFile).then(function(fileHash) { $.ajax({ url: wcLicensedProductVersions.ajaxUrl, type: 'POST', data: { action: 'wc_licensed_product_add_version', nonce: wcLicensedProductVersions.nonce, product_id: productId, version: version, release_notes: releaseNotes, attachment_id: attachmentId, file_hash: fileHash }, success: function(response) { if (response.success) { // Remove "no versions" row if present $('#versions-table tbody .no-versions').remove(); // Add new row to table $('#versions-table tbody').prepend(response.data.html); // Clear form $('#new_version').val(''); $('#new_release_notes').val(''); $('#new_attachment_id').val(''); $('#selected_file_name').text(''); $('#remove-version-file-btn').hide(); $('#sha256-hash-row').hide(); $('#new_checksum_file').val(''); $('#selected_checksum_name').text(''); $('#remove-checksum-file-btn').hide(); } else { alert(response.data.message || wcLicensedProductVersions.strings.error); } }, error: function() { alert(wcLicensedProductVersions.strings.error); }, complete: function() { $btn.prop('disabled', false); $spinner.removeClass('is-active'); } }); }).catch(function(error) { alert(error.message); $btn.prop('disabled', false); $spinner.removeClass('is-active'); }); }, deleteVersion: function(e) { e.preventDefault(); if (!confirm(wcLicensedProductVersions.strings.confirmDelete)) { return; } var $btn = $(this); var $row = $btn.closest('tr'); var versionId = $btn.data('version-id'); $btn.prop('disabled', true); $.ajax({ url: wcLicensedProductVersions.ajaxUrl, type: 'POST', data: { action: 'wc_licensed_product_delete_version', nonce: wcLicensedProductVersions.nonce, version_id: versionId }, success: function(response) { if (response.success) { $row.fadeOut(300, function() { $(this).remove(); // Show "no versions" message if table is empty if ($('#versions-table tbody tr').length === 0) { $('#versions-table tbody').append( '