versionManager = $versionManager;
$this->registerHooks();
}
/**
* Register WordPress hooks
*/
private function registerHooks(): void
{
// Add versions meta box to licensed products
add_action('add_meta_boxes', [$this, 'addVersionsMetaBox']);
// Handle AJAX actions for version management
add_action('wp_ajax_wc_licensed_product_add_version', [$this, 'ajaxAddVersion']);
add_action('wp_ajax_wc_licensed_product_delete_version', [$this, 'ajaxDeleteVersion']);
add_action('wp_ajax_wc_licensed_product_toggle_version', [$this, 'ajaxToggleVersion']);
// Enqueue scripts for product edit page
add_action('admin_enqueue_scripts', [$this, 'enqueueScripts']);
}
/**
* Add versions meta box to product edit page
*/
public function addVersionsMetaBox(): void
{
global $post;
// Only add meta box for licensed products or new products
if ($post && $post->post_type === 'product') {
$product = wc_get_product($post->ID);
// Show for licensed products or new products (where type might be selected later)
if (!$product || $product->is_type('licensed') || $post->post_status === 'auto-draft') {
add_meta_box(
'wc_licensed_product_versions',
__('Product Versions', 'wc-licensed-product'),
[$this, 'renderVersionsMetaBox'],
'product',
'normal',
'high'
);
}
}
}
/**
* Render versions meta box
*/
public function renderVersionsMetaBox(\WP_Post $post): void
{
$versions = $this->versionManager->getVersionsByProduct($post->ID);
wp_nonce_field('wc_licensed_product_versions', 'wc_licensed_product_versions_nonce');
?>
|
|
|
|
|
|
|
|
| getVersion()); ?> |
getEffectiveDownloadUrl();
$filename = $version->getDownloadFilename();
if ($effectiveUrl):
?>
getAttachmentId()): ?>
|
getFileHash()): ?>
getFileHash(), 0, 12)); ?>...
—
|
getReleaseNotes() ? wp_trim_words($version->getReleaseNotes(), 10) : '—'); ?> |
isActive() ? esc_html__('Active', 'wc-licensed-product') : esc_html__('Inactive', 'wc-licensed-product'); ?>
|
getReleasedAt()->format(get_option('date_format'))); ?> |
|
post_type !== 'product') {
return;
}
// Enqueue WordPress media uploader
wp_enqueue_media();
wp_enqueue_script(
'wc-licensed-product-versions',
WC_LICENSED_PRODUCT_PLUGIN_URL . 'assets/js/versions.js',
['jquery'],
WC_LICENSED_PRODUCT_VERSION,
true
);
wp_localize_script('wc-licensed-product-versions', 'wcLicensedProductVersions', [
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wc_licensed_product_versions'),
'strings' => [
'confirmDelete' => __('Are you sure you want to delete this version?', 'wc-licensed-product'),
'versionRequired' => __('Please enter a version number.', 'wc-licensed-product'),
'versionInvalid' => __('Please enter a valid version number (e.g., 1.0.0).', 'wc-licensed-product'),
'error' => __('An error occurred. Please try again.', 'wc-licensed-product'),
'selectFile' => __('Select Download File', 'wc-licensed-product'),
'useThisFile' => __('Use this file', 'wc-licensed-product'),
'invalidChecksumFile' => __('Invalid checksum file format. File must contain a 64-character SHA256 hash.', 'wc-licensed-product'),
'checksumReadError' => __('Failed to read checksum file.', 'wc-licensed-product'),
],
]);
wp_enqueue_style(
'wc-licensed-product-admin',
WC_LICENSED_PRODUCT_PLUGIN_URL . 'assets/css/admin.css',
[],
WC_LICENSED_PRODUCT_VERSION
);
}
/**
* AJAX handler for adding a version
*/
public function ajaxAddVersion(): void
{
check_ajax_referer('wc_licensed_product_versions', 'nonce');
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(['message' => __('Permission denied.', 'wc-licensed-product')]);
}
$productId = absint($_POST['product_id'] ?? 0);
$version = sanitize_text_field($_POST['version'] ?? '');
$releaseNotes = sanitize_textarea_field($_POST['release_notes'] ?? '');
$attachmentId = absint($_POST['attachment_id'] ?? 0);
$fileHash = sanitize_text_field($_POST['file_hash'] ?? '');
if (!$productId || !$version) {
wp_send_json_error(['message' => __('Product ID and version are required.', 'wc-licensed-product')]);
}
// Validate version format
if (!preg_match('/^\d+\.\d+\.\d+$/', $version)) {
wp_send_json_error(['message' => __('Invalid version format. Use semantic versioning (e.g., 1.0.0).', 'wc-licensed-product')]);
}
// Check if version already exists
if ($this->versionManager->versionExists($productId, $version)) {
wp_send_json_error(['message' => __('This version already exists.', 'wc-licensed-product')]);
}
// Verify product exists and is of type licensed
$product = wc_get_product($productId);
if (!$product) {
wp_send_json_error(['message' => __('Product not found.', 'wc-licensed-product')]);
}
if (!$product->is_type('licensed')) {
wp_send_json_error(['message' => __('This product is not a licensed product.', 'wc-licensed-product')]);
}
try {
$newVersion = $this->versionManager->createVersion(
$productId,
$version,
$releaseNotes ?: null,
$attachmentId ?: null,
$fileHash ?: null
);
} catch (\InvalidArgumentException $e) {
wp_send_json_error(['message' => $e->getMessage()]);
}
if (!$newVersion) {
global $wpdb;
$errorMessage = __('Failed to create version.', 'wc-licensed-product');
if (!empty($wpdb->last_error)) {
error_log('WC Licensed Product: DB error - ' . $wpdb->last_error);
}
wp_send_json_error(['message' => $errorMessage]);
}
wp_send_json_success([
'message' => __('Version added successfully.', 'wc-licensed-product'),
'version' => $newVersion->toArray(),
'html' => $this->getVersionRowHtml($newVersion),
]);
}
/**
* AJAX handler for deleting a version
*/
public function ajaxDeleteVersion(): void
{
check_ajax_referer('wc_licensed_product_versions', 'nonce');
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(['message' => __('Permission denied.', 'wc-licensed-product')]);
}
$versionId = absint($_POST['version_id'] ?? 0);
if (!$versionId) {
wp_send_json_error(['message' => __('Version ID is required.', 'wc-licensed-product')]);
}
$result = $this->versionManager->deleteVersion($versionId);
if (!$result) {
wp_send_json_error(['message' => __('Failed to delete version.', 'wc-licensed-product')]);
}
wp_send_json_success(['message' => __('Version deleted successfully.', 'wc-licensed-product')]);
}
/**
* AJAX handler for toggling version status
*/
public function ajaxToggleVersion(): void
{
check_ajax_referer('wc_licensed_product_versions', 'nonce');
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(['message' => __('Permission denied.', 'wc-licensed-product')]);
}
$versionId = absint($_POST['version_id'] ?? 0);
$currentlyActive = (bool) ($_POST['currently_active'] ?? false);
if (!$versionId) {
wp_send_json_error(['message' => __('Version ID is required.', 'wc-licensed-product')]);
}
$result = $this->versionManager->updateVersion($versionId, null, !$currentlyActive, null);
if (!$result) {
wp_send_json_error(['message' => __('Failed to update version.', 'wc-licensed-product')]);
}
wp_send_json_success([
'message' => __('Version updated successfully.', 'wc-licensed-product'),
'isActive' => !$currentlyActive,
]);
}
/**
* Get HTML for a version table row
*/
private function getVersionRowHtml(\Jeremias\WcLicensedProduct\Product\ProductVersion $version): string
{
ob_start();
?>
| getVersion()); ?> |
getEffectiveDownloadUrl();
$filename = $version->getDownloadFilename();
if ($effectiveUrl):
?>
getAttachmentId()): ?>
|
getFileHash()): ?>
getFileHash(), 0, 12)); ?>...
—
|
getReleaseNotes() ? wp_trim_words($version->getReleaseNotes(), 10) : '—'); ?> |
isActive() ? esc_html__('Active', 'wc-licensed-product') : esc_html__('Inactive', 'wc-licensed-product'); ?>
|
getReleasedAt()->format(get_option('date_format'))); ?> |
|