mirror of
https://gh.wpcy.net/https://github.com/superdav42/wp-update-server-plugin.git
synced 2026-05-01 11:42:21 +08:00
WooCommerce doesn't auto-grant existing customers permission to download files added to a product after purchase. This caused 404 "Invalid download link" errors when customers tried to download the latest version via Composer, the legacy update API, or the Store API. Added ensure_download_permissions() which backfills missing permission records on access, copying the access_expires from the existing permission to preserve the yearly subscription expiry window. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
170 lines
4.9 KiB
PHP
170 lines
4.9 KiB
PHP
<?php
|
|
/**
|
|
* Update server class.
|
|
*
|
|
* @package WP_Update_Server_Plugin
|
|
*/
|
|
|
|
namespace WP_Update_Server_Plugin;
|
|
|
|
use Automattic\WooCommerce\Proxies\LegacyProxy;
|
|
|
|
/**
|
|
* Update server class.
|
|
*/
|
|
class Update_Server extends \Wpup_UpdateServer {
|
|
|
|
/**
|
|
* Use our prefixed query vars.
|
|
*
|
|
* @param \Wpup_Package $package the package.
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function generateDownloadUrl(\Wpup_Package $package) {
|
|
$user_id = apply_filters('determine_current_user', null);
|
|
if ($user_id) {
|
|
$download_url = $this->getDownloadUrlForUser($user_id, $package->slug);
|
|
if ($download_url) {
|
|
return $download_url;
|
|
}
|
|
}
|
|
$query = [
|
|
'update_action' => 'download',
|
|
'update_slug' => $package->slug,
|
|
];
|
|
|
|
return self::addQueryArg($query, $this->serverUrl);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the download URL for a specific user and product specified by its SKU.
|
|
*
|
|
* @param int $user_id The ID of the user for whom the download URL is being retrieved.
|
|
* @param string $slug The SKU identifying the product for which the download URL is needed.
|
|
*
|
|
* @return string|null The download URL if available, or null if no valid downloads are found.
|
|
*/
|
|
protected function getDownloadUrlForUser($user_id, $slug) {
|
|
/** @var \WC_Product_Data_Store_Interface $product_data_store */
|
|
$product_data_store = wc_get_container()->get(LegacyProxy::class)->get_instance_of(\WC_Data_Store::class, 'product');
|
|
$product_id = $product_data_store->get_product_id_by_sku($slug);
|
|
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
$include_beta = ! empty($_GET['beta']);
|
|
|
|
// Find the appropriate version (stable or beta) to serve
|
|
$target_version = Product_Versions::get_latest_version_by_product_id($product_id, $include_beta);
|
|
|
|
if (! $target_version || ! isset($target_version['file_id'])) {
|
|
return null;
|
|
}
|
|
|
|
/** @var \WC_Customer_Download_Data_Store $downloads_data_store */
|
|
$downloads_data_store = wc_get_container()->get(LegacyProxy::class)->get_instance_of(\WC_Data_Store::class, 'customer-download');
|
|
|
|
$permissions = $downloads_data_store->get_downloads(
|
|
array(
|
|
'product_id' => $product_id,
|
|
'user_id' => $user_id,
|
|
'orderby' => 'permission_id',
|
|
'order' => 'DESC',
|
|
'limit' => 1,
|
|
)
|
|
);
|
|
|
|
if ( ! empty($permissions) ) {
|
|
$permission = $permissions[0];
|
|
|
|
// Ensure the user has permissions for all current files (including newly uploaded versions)
|
|
$product = wc_get_product($product_id);
|
|
|
|
if ($product) {
|
|
Product_Versions::ensure_download_permissions($product, $permission);
|
|
}
|
|
|
|
return add_query_arg(
|
|
array(
|
|
'download_file' => $permission->get_product_id(),
|
|
'order' => $permission->get_order_key(),
|
|
'email' => rawurlencode($permission->get_user_email()),
|
|
'key' => $target_version['file_id'],
|
|
),
|
|
home_url('/')
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Finds the most recent package associated with a given product slug.
|
|
*
|
|
* @param string $slug The product slug to search for the package.
|
|
*
|
|
* @return \Wpup_Package|null The latest package if found, otherwise null.
|
|
*/
|
|
protected function findPackage($slug) {
|
|
$product_id = wc_get_product_id_by_sku($slug);
|
|
$product = wc_get_product($product_id);
|
|
|
|
if (! $product || ! $product->exists() || ! $product->is_downloadable()) {
|
|
return null;
|
|
}
|
|
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
$include_beta = ! empty($_GET['beta']);
|
|
|
|
// Use Product_Versions to find the correct version to serve
|
|
$target = Product_Versions::get_latest_version_by_product_id($product_id, $include_beta);
|
|
|
|
if (! $target || ! isset($target['file_id'])) {
|
|
return null;
|
|
}
|
|
|
|
// Load the package from the target version's file
|
|
$files = $product->get_downloads();
|
|
|
|
if (! isset($files[$target['file_id']])) {
|
|
return null;
|
|
}
|
|
|
|
$file = $files[$target['file_id']];
|
|
$file_info = \WC_Download_Handler::parse_file_path($product->get_file_download_path($target['file_id']));
|
|
$filepath = $file_info['file_path'];
|
|
|
|
if ($file_info['remote_file']) {
|
|
require_once ABSPATH . 'wp-admin/includes/file.php';
|
|
$tmp = wp_tempnam($file['name']);
|
|
file_put_contents($tmp, file_get_contents($filepath));
|
|
$filepath = $tmp;
|
|
}
|
|
|
|
if ($file->get_enabled() && is_file($filepath) && is_readable($filepath)) {
|
|
return call_user_func($this->packageFileLoader, $filepath, $slug, $this->cache);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Handles the download action for a package request.
|
|
*
|
|
* @param \Wpup_Request $request The request object containing package details.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function actionDownload(\Wpup_Request $request) {
|
|
$package = $request->package;
|
|
$user_id = apply_filters('determine_current_user', null);
|
|
if ($user_id) {
|
|
$download_url = $this->getDownloadUrlForUser($user_id, $package->slug);
|
|
if ($download_url) {
|
|
wp_safe_redirect($download_url);
|
|
exit();
|
|
}
|
|
}
|
|
|
|
$this->exitWithError('You do not have a valid order for this package.', 403);
|
|
}
|
|
}
|