Files
WooNooW/includes/Modules/Software/SoftwareManager.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')
));
}
}