457 lines
14 KiB
PHP
457 lines
14 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Software Distribution Manager
|
|
*
|
|
* Handles software versioning, downloads, and update checking.
|
|
*
|
|
* @package WooNooW\Modules\Software
|
|
*/
|
|
|
|
namespace WooNooW\Modules\Software;
|
|
|
|
if (!defined('ABSPATH')) exit;
|
|
|
|
use WooNooW\Core\ModuleRegistry;
|
|
use WooNooW\Modules\Licensing\LicenseManager;
|
|
|
|
class SoftwareManager
|
|
{
|
|
private static $versions_table = 'woonoow_software_versions';
|
|
private static $downloads_table = 'woonoow_software_downloads';
|
|
|
|
/**
|
|
* Initialize
|
|
*/
|
|
public static function init()
|
|
{
|
|
if (!ModuleRegistry::is_enabled('software')) {
|
|
return;
|
|
}
|
|
|
|
// Nothing to hook yet - API endpoints handle requests
|
|
}
|
|
|
|
/**
|
|
* Create database tables
|
|
*/
|
|
public static function create_tables()
|
|
{
|
|
global $wpdb;
|
|
$charset_collate = $wpdb->get_charset_collate();
|
|
|
|
$versions_table = $wpdb->prefix . self::$versions_table;
|
|
$downloads_table = $wpdb->prefix . self::$downloads_table;
|
|
|
|
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
|
|
|
// Software versions table
|
|
$sql_versions = "CREATE TABLE $versions_table (
|
|
id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
product_id bigint(20) UNSIGNED NOT NULL,
|
|
version varchar(50) NOT NULL,
|
|
changelog longtext,
|
|
release_date datetime NOT NULL,
|
|
is_current tinyint(1) DEFAULT 0,
|
|
download_count int(11) DEFAULT 0,
|
|
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
KEY idx_product (product_id),
|
|
KEY idx_current (product_id, is_current),
|
|
UNIQUE KEY unique_version (product_id, version)
|
|
) $charset_collate;";
|
|
|
|
dbDelta($sql_versions);
|
|
|
|
// Download tokens table (for secure downloads)
|
|
$sql_downloads = "CREATE TABLE $downloads_table (
|
|
id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
token varchar(64) NOT NULL,
|
|
license_id bigint(20) UNSIGNED NOT NULL,
|
|
product_id bigint(20) UNSIGNED NOT NULL,
|
|
version_id bigint(20) UNSIGNED,
|
|
ip_address varchar(45),
|
|
expires_at datetime NOT NULL,
|
|
used_at datetime DEFAULT NULL,
|
|
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY token (token),
|
|
KEY license_id (license_id),
|
|
KEY expires_at (expires_at)
|
|
) $charset_collate;";
|
|
|
|
dbDelta($sql_downloads);
|
|
}
|
|
|
|
/**
|
|
* Get product software configuration
|
|
*/
|
|
public static function get_product_config($product_id)
|
|
{
|
|
$enabled = get_post_meta($product_id, '_woonoow_software_enabled', true) === 'yes';
|
|
|
|
if (!$enabled) {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'enabled' => true,
|
|
'slug' => get_post_meta($product_id, '_woonoow_software_slug', true),
|
|
'current_version' => get_post_meta($product_id, '_woonoow_software_current_version', true),
|
|
'wp_enabled' => get_post_meta($product_id, '_woonoow_software_wp_enabled', true) === 'yes',
|
|
'requires_wp' => get_post_meta($product_id, '_woonoow_software_requires_wp', true),
|
|
'tested_wp' => get_post_meta($product_id, '_woonoow_software_tested_wp', true),
|
|
'requires_php' => get_post_meta($product_id, '_woonoow_software_requires_php', true),
|
|
'icon' => get_post_meta($product_id, '_woonoow_software_icon', true),
|
|
'banner' => get_post_meta($product_id, '_woonoow_software_banner', true),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get product by software slug
|
|
*/
|
|
public static function get_product_by_slug($slug)
|
|
{
|
|
global $wpdb;
|
|
|
|
$product_id = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT post_id FROM {$wpdb->postmeta}
|
|
WHERE meta_key = '_woonoow_software_slug' AND meta_value = %s
|
|
LIMIT 1",
|
|
$slug
|
|
));
|
|
|
|
return $product_id ? wc_get_product($product_id) : null;
|
|
}
|
|
|
|
/**
|
|
* Check for updates
|
|
*/
|
|
public static function check_update($license_key, $slug, $current_version)
|
|
{
|
|
// Validate license
|
|
$license_validation = LicenseManager::validate($license_key);
|
|
|
|
if (!$license_validation['valid']) {
|
|
return [
|
|
'success' => false,
|
|
'error' => $license_validation['error'] ?? 'invalid_license',
|
|
'message' => $license_validation['message'] ?? __('Invalid license key', 'woonoow'),
|
|
];
|
|
}
|
|
|
|
// Get product by slug
|
|
$product = self::get_product_by_slug($slug);
|
|
|
|
if (!$product) {
|
|
return [
|
|
'success' => false,
|
|
'error' => 'product_not_found',
|
|
'message' => __('Software product not found', 'woonoow'),
|
|
];
|
|
}
|
|
|
|
$config = self::get_product_config($product->get_id());
|
|
|
|
if (!$config || !$config['enabled']) {
|
|
return [
|
|
'success' => false,
|
|
'error' => 'software_disabled',
|
|
'message' => __('Software distribution is not enabled for this product', 'woonoow'),
|
|
];
|
|
}
|
|
|
|
$latest_version = $config['current_version'];
|
|
$update_available = version_compare($current_version, $latest_version, '<');
|
|
|
|
// Get changelog for latest version
|
|
$changelog = self::get_version_changelog($product->get_id(), $latest_version);
|
|
|
|
// Build response
|
|
$response = [
|
|
'success' => true,
|
|
'update_available' => $update_available,
|
|
'product' => [
|
|
'name' => $product->get_name(),
|
|
'slug' => $config['slug'],
|
|
],
|
|
'current_version' => $current_version,
|
|
'latest_version' => $latest_version,
|
|
'changelog' => $changelog['changelog'] ?? '',
|
|
'release_date' => $changelog['release_date'] ?? null,
|
|
];
|
|
|
|
// Add download URL if update available
|
|
if ($update_available) {
|
|
$license = LicenseManager::get_license_by_key($license_key);
|
|
$token = self::generate_download_token($license['id'], $product->get_id());
|
|
$response['download_url'] = rest_url('woonoow/v1/software/download') . '?token=' . $token;
|
|
$response['changelog_url'] = rest_url('woonoow/v1/software/changelog') . '?slug=' . $config['slug'];
|
|
}
|
|
|
|
// Add WordPress-specific fields if enabled
|
|
if ($config['wp_enabled']) {
|
|
$response['wordpress'] = [
|
|
'requires' => $config['requires_wp'] ?: null,
|
|
'tested' => $config['tested_wp'] ?: null,
|
|
'requires_php' => $config['requires_php'] ?: null,
|
|
];
|
|
|
|
// Add icons/banners if set
|
|
if ($config['icon']) {
|
|
$icon_url = is_numeric($config['icon'])
|
|
? wp_get_attachment_url($config['icon'])
|
|
: $config['icon'];
|
|
$response['wordpress']['icons'] = [
|
|
'1x' => $icon_url,
|
|
'2x' => $icon_url,
|
|
];
|
|
}
|
|
|
|
if ($config['banner']) {
|
|
$banner_url = is_numeric($config['banner'])
|
|
? wp_get_attachment_url($config['banner'])
|
|
: $config['banner'];
|
|
$response['wordpress']['banners'] = [
|
|
'low' => $banner_url,
|
|
'high' => $banner_url,
|
|
];
|
|
}
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Get version changelog
|
|
*/
|
|
public static function get_version_changelog($product_id, $version = null)
|
|
{
|
|
global $wpdb;
|
|
$table = $wpdb->prefix . self::$versions_table;
|
|
|
|
if ($version) {
|
|
return $wpdb->get_row($wpdb->prepare(
|
|
"SELECT version, changelog, release_date FROM $table
|
|
WHERE product_id = %d AND version = %s",
|
|
$product_id,
|
|
$version
|
|
), ARRAY_A);
|
|
}
|
|
|
|
// Get current version
|
|
return $wpdb->get_row($wpdb->prepare(
|
|
"SELECT version, changelog, release_date FROM $table
|
|
WHERE product_id = %d AND is_current = 1",
|
|
$product_id
|
|
), ARRAY_A);
|
|
}
|
|
|
|
/**
|
|
* Get all versions for a product
|
|
*/
|
|
public static function get_all_versions($product_id)
|
|
{
|
|
global $wpdb;
|
|
$table = $wpdb->prefix . self::$versions_table;
|
|
|
|
return $wpdb->get_results($wpdb->prepare(
|
|
"SELECT * FROM $table WHERE product_id = %d ORDER BY release_date DESC",
|
|
$product_id
|
|
), ARRAY_A);
|
|
}
|
|
|
|
/**
|
|
* Add new version
|
|
*/
|
|
public static function add_version($product_id, $version, $changelog, $set_current = true)
|
|
{
|
|
global $wpdb;
|
|
$table = $wpdb->prefix . self::$versions_table;
|
|
|
|
// Check if version already exists
|
|
$exists = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT id FROM $table WHERE product_id = %d AND version = %s",
|
|
$product_id,
|
|
$version
|
|
));
|
|
|
|
if ($exists) {
|
|
return new \WP_Error('version_exists', __('Version already exists', 'woonoow'));
|
|
}
|
|
|
|
// If setting as current, unset previous current
|
|
if ($set_current) {
|
|
$wpdb->update(
|
|
$table,
|
|
['is_current' => 0],
|
|
['product_id' => $product_id]
|
|
);
|
|
}
|
|
|
|
// Insert new version
|
|
$wpdb->insert($table, [
|
|
'product_id' => $product_id,
|
|
'version' => $version,
|
|
'changelog' => $changelog,
|
|
'release_date' => current_time('mysql'),
|
|
'is_current' => $set_current ? 1 : 0,
|
|
]);
|
|
|
|
// Update product meta
|
|
if ($set_current) {
|
|
update_post_meta($product_id, '_woonoow_software_current_version', $version);
|
|
}
|
|
|
|
do_action('woonoow/software/version_added', $wpdb->insert_id, $product_id, $version);
|
|
|
|
return $wpdb->insert_id;
|
|
}
|
|
|
|
/**
|
|
* Generate secure download token
|
|
*/
|
|
public static function generate_download_token($license_id, $product_id, $version_id = null)
|
|
{
|
|
global $wpdb;
|
|
$table = $wpdb->prefix . self::$downloads_table;
|
|
|
|
$token = bin2hex(random_bytes(32));
|
|
$expires_at = gmdate('Y-m-d H:i:s', time() + 300); // 5 minutes
|
|
|
|
$wpdb->insert($table, [
|
|
'token' => $token,
|
|
'license_id' => $license_id,
|
|
'product_id' => $product_id,
|
|
'version_id' => $version_id,
|
|
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? null,
|
|
'expires_at' => $expires_at,
|
|
]);
|
|
|
|
return $token;
|
|
}
|
|
|
|
/**
|
|
* Validate and consume download token
|
|
*/
|
|
public static function validate_download_token($token)
|
|
{
|
|
global $wpdb;
|
|
$table = $wpdb->prefix . self::$downloads_table;
|
|
|
|
$download = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT * FROM $table WHERE token = %s AND used_at IS NULL AND expires_at > %s",
|
|
$token,
|
|
current_time('mysql')
|
|
), ARRAY_A);
|
|
|
|
if (!$download) {
|
|
return new \WP_Error('invalid_token', __('Invalid or expired download token', 'woonoow'));
|
|
}
|
|
|
|
// Mark as used
|
|
$wpdb->update(
|
|
$table,
|
|
['used_at' => current_time('mysql')],
|
|
['id' => $download['id']]
|
|
);
|
|
|
|
// Increment download count
|
|
$versions_table = $wpdb->prefix . self::$versions_table;
|
|
if ($download['version_id']) {
|
|
$wpdb->query($wpdb->prepare(
|
|
"UPDATE $versions_table SET download_count = download_count + 1 WHERE id = %d",
|
|
$download['version_id']
|
|
));
|
|
}
|
|
|
|
return $download;
|
|
}
|
|
|
|
/**
|
|
* Get downloadable file for product
|
|
* Uses WooCommerce's existing downloadable files
|
|
*/
|
|
public static function get_downloadable_file($product_id)
|
|
{
|
|
$product = wc_get_product($product_id);
|
|
|
|
if (!$product || !$product->is_downloadable()) {
|
|
return null;
|
|
}
|
|
|
|
$downloads = $product->get_downloads();
|
|
|
|
if (empty($downloads)) {
|
|
return null;
|
|
}
|
|
|
|
// Return first downloadable file
|
|
$download = reset($downloads);
|
|
|
|
return [
|
|
'id' => $download->get_id(),
|
|
'name' => $download->get_name(),
|
|
'file' => $download->get_file(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Serve downloadable file
|
|
*/
|
|
public static function serve_file($product_id)
|
|
{
|
|
$file_data = self::get_downloadable_file($product_id);
|
|
|
|
if (!$file_data) {
|
|
wp_die(__('No downloadable file found', 'woonoow'), '', ['response' => 404]);
|
|
}
|
|
|
|
$file_path = $file_data['file'];
|
|
|
|
// Handle different file types
|
|
if (strpos($file_path, home_url()) === 0) {
|
|
// Local file - convert URL to path
|
|
$upload_dir = wp_upload_dir();
|
|
$file_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $file_path);
|
|
}
|
|
|
|
if (!file_exists($file_path)) {
|
|
// Try as attachment
|
|
$file_path = get_attached_file(attachment_url_to_postid($file_data['file']));
|
|
}
|
|
|
|
if (!$file_path || !file_exists($file_path)) {
|
|
wp_die(__('File not found', 'woonoow'), '', ['response' => 404]);
|
|
}
|
|
|
|
// Serve file
|
|
$filename = basename($file_path);
|
|
$mime_type = mime_content_type($file_path) ?: 'application/octet-stream';
|
|
|
|
header('Content-Type: ' . $mime_type);
|
|
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
|
header('Content-Length: ' . filesize($file_path));
|
|
header('Cache-Control: no-cache, must-revalidate');
|
|
header('Pragma: no-cache');
|
|
header('Expires: 0');
|
|
|
|
readfile($file_path);
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Clean up expired tokens
|
|
*/
|
|
public static function cleanup_expired_tokens()
|
|
{
|
|
global $wpdb;
|
|
$table = $wpdb->prefix . self::$downloads_table;
|
|
|
|
$wpdb->query($wpdb->prepare(
|
|
"DELETE FROM $table WHERE expires_at < %s",
|
|
current_time('mysql')
|
|
));
|
|
}
|
|
}
|