Merge pull request #28521 from woocommerce/fix/26475
Create additional download permissions on product save if needed
This commit is contained in:
commit
4c548951f4
|
@ -8,6 +8,7 @@
|
|||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
|
||||
use Automattic\WooCommerce\Proxies\LegacyProxy;
|
||||
|
||||
/**
|
||||
|
@ -202,6 +203,9 @@ final class WooCommerce {
|
|||
add_action( 'switch_blog', array( $this, 'wpdb_table_fix' ), 0 );
|
||||
add_action( 'activated_plugin', array( $this, 'activated_plugin' ) );
|
||||
add_action( 'deactivated_plugin', array( $this, 'deactivated_plugin' ) );
|
||||
|
||||
// These classes set up hooks on instantiation.
|
||||
wc_get_container()->get( DownloadPermissionsAdjuster::class );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -901,7 +905,7 @@ final class WooCommerce {
|
|||
'https://wordpress.org/plugins/woocommerce/',
|
||||
'https://github.com/woocommerce/woocommerce/releases'
|
||||
);
|
||||
printf( '<div class="error"><p>%s %s</p></div>', $message_one, $message_two ); /* WPCS: xss ok. */
|
||||
printf( '<div class="error"><p>%s %s</p></div>', $message_one, $message_two ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,6 +16,38 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
*/
|
||||
class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store_Interface {
|
||||
|
||||
/**
|
||||
* Names of the database fields for the download permissions table.
|
||||
*/
|
||||
const DOWNLOAD_PERMISSION_DB_FIELDS = array(
|
||||
'download_id',
|
||||
'product_id',
|
||||
'user_id',
|
||||
'user_email',
|
||||
'order_id',
|
||||
'order_key',
|
||||
'downloads_remaining',
|
||||
'access_granted',
|
||||
'download_count',
|
||||
'access_expires',
|
||||
);
|
||||
|
||||
/**
|
||||
* Create download permission for a user, from an array of data.
|
||||
*
|
||||
* @param array $data Data to create the permission for.
|
||||
* @returns int The database id of the created permission, or false if the permission creation failed.
|
||||
*/
|
||||
public function create_from_data( $data ) {
|
||||
$data = array_intersect_key( $data, array_flip( self::DOWNLOAD_PERMISSION_DB_FIELDS ) );
|
||||
|
||||
$id = $this->insert_new_download_permission( $data );
|
||||
|
||||
do_action( 'woocommerce_grant_product_download_access', $data );
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create download permission for a user.
|
||||
*
|
||||
|
@ -29,18 +61,41 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
|
|||
$download->set_access_granted( time() );
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'download_id' => $download->get_download_id( 'edit' ),
|
||||
'product_id' => $download->get_product_id( 'edit' ),
|
||||
'user_id' => $download->get_user_id( 'edit' ),
|
||||
'user_email' => $download->get_user_email( 'edit' ),
|
||||
'order_id' => $download->get_order_id( 'edit' ),
|
||||
'order_key' => $download->get_order_key( 'edit' ),
|
||||
'downloads_remaining' => $download->get_downloads_remaining( 'edit' ),
|
||||
'access_granted' => date( 'Y-m-d', $download->get_access_granted( 'edit' )->getTimestamp() ),
|
||||
'download_count' => $download->get_download_count( 'edit' ),
|
||||
'access_expires' => ! is_null( $download->get_access_expires( 'edit' ) ) ? date( 'Y-m-d', $download->get_access_expires( 'edit' )->getTimestamp() ) : null,
|
||||
);
|
||||
$data = array();
|
||||
foreach ( self::DOWNLOAD_PERMISSION_DB_FIELDS as $db_field_name ) {
|
||||
$value = call_user_func( array( $download, 'get_' . $db_field_name ), 'edit' );
|
||||
$data[ $db_field_name ] = $value;
|
||||
}
|
||||
|
||||
$inserted_id = $this->insert_new_download_permission( $data );
|
||||
if ( $inserted_id ) {
|
||||
$download->set_id( $inserted_id );
|
||||
$download->apply_changes();
|
||||
}
|
||||
|
||||
do_action( 'woocommerce_grant_product_download_access', $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create download permission for a user, from an array of data.
|
||||
* Assumes that all the keys in the passed data are valid.
|
||||
*
|
||||
* @param array $data Data to create the permission for.
|
||||
* @return int The database id of the created permission, or false if the permission creation failed.
|
||||
*/
|
||||
private function insert_new_download_permission( $data ) {
|
||||
global $wpdb;
|
||||
|
||||
// Always set a access granted date.
|
||||
if ( ! isset( $data['access_granted'] ) ) {
|
||||
$data['access_granted'] = time();
|
||||
}
|
||||
|
||||
$data['access_granted'] = $this->adjust_date_for_db( $data['access_granted'] );
|
||||
|
||||
if ( isset( $data['access_expires'] ) ) {
|
||||
$data['access_expires'] = $this->adjust_date_for_db( $data['access_expires'] );
|
||||
}
|
||||
|
||||
$format = array(
|
||||
'%s',
|
||||
|
@ -61,12 +116,29 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
|
|||
apply_filters( 'woocommerce_downloadable_file_permission_format', $format, $data )
|
||||
);
|
||||
|
||||
if ( $result ) {
|
||||
$download->set_id( $wpdb->insert_id );
|
||||
$download->apply_changes();
|
||||
return $result ? $wpdb->insert_id : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust a date value to be inserted in the database.
|
||||
*
|
||||
* @param mixed $date The date value. Can be a WC_DateTime, a timestamp, or anything else that "date" recognizes.
|
||||
* @return string The date converted to 'Y-m-d' format.
|
||||
* @throws Exception The passed value can't be converted to a date.
|
||||
*/
|
||||
private function adjust_date_for_db( $date ) {
|
||||
if ( 'WC_DateTime' === get_class( $date ) ) {
|
||||
$date = $date->getTimestamp();
|
||||
}
|
||||
|
||||
do_action( 'woocommerce_grant_product_download_access', $data );
|
||||
$adjusted_date = date( 'Y-m-d', $date ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
|
||||
|
||||
if ( $adjusted_date ) {
|
||||
return $adjusted_date;
|
||||
}
|
||||
|
||||
$msg = sprintf( __( "I don't know how to get a date from a %s", 'woocommerce' ), is_object( $date ) ? get_class( $date ) : gettype( $date ) );
|
||||
throw new Exception( $msg );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -128,8 +200,10 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
|
|||
'order_id' => $download->get_order_id( 'edit' ),
|
||||
'order_key' => $download->get_order_key( 'edit' ),
|
||||
'downloads_remaining' => $download->get_downloads_remaining( 'edit' ),
|
||||
// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
|
||||
'access_granted' => date( 'Y-m-d', $download->get_access_granted( 'edit' )->getTimestamp() ),
|
||||
'download_count' => $download->get_download_count( 'edit' ),
|
||||
// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
|
||||
'access_expires' => ! is_null( $download->get_access_expires( 'edit' ) ) ? date( 'Y-m-d', $download->get_access_expires( 'edit' )->getTimestamp() ) : null,
|
||||
);
|
||||
|
||||
|
@ -412,7 +486,7 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
|
|||
)
|
||||
ORDER BY permissions.order_id, permissions.product_id, permissions.permission_id;",
|
||||
$customer_id,
|
||||
date( 'Y-m-d', current_time( 'timestamp' ) )
|
||||
date( 'Y-m-d', current_time( 'timestamp' ) ) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
|
||||
use Automattic\WooCommerce\Utilities\NumberUtil;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
|
@ -265,6 +266,10 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
|
|||
$this->handle_updated_props( $product );
|
||||
$this->clear_caches( $product );
|
||||
|
||||
wc_get_container()
|
||||
->get( DownloadPermissionsAdjuster::class )
|
||||
->maybe_schedule_adjust_download_permissions( $product );
|
||||
|
||||
$product->apply_changes();
|
||||
|
||||
do_action( 'woocommerce_update_product', $product->get_id(), $product );
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
|
||||
namespace Automattic\WooCommerce;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProxiesServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\DownloadPermissionsAdjusterServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProxiesServiceProvider;
|
||||
|
||||
/**
|
||||
* PSR11 compliant dependency injection container for WooCommerce.
|
||||
|
@ -33,6 +34,7 @@ final class Container implements \Psr\Container\ContainerInterface {
|
|||
*/
|
||||
private $service_providers = array(
|
||||
ProxiesServiceProvider::class,
|
||||
DownloadPermissionsAdjusterServiceProvider::class,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
/**
|
||||
* DownloadPermissionsAdjusterServiceProvider class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
|
||||
|
||||
/**
|
||||
* Service provider for the DownloadPermissionsAdjuster class.
|
||||
*/
|
||||
class DownloadPermissionsAdjusterServiceProvider extends AbstractServiceProvider {
|
||||
|
||||
/**
|
||||
* The classes/interfaces that are serviced by this service provider.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $provides = array(
|
||||
DownloadPermissionsAdjuster::class,
|
||||
);
|
||||
|
||||
/**
|
||||
* Register the classes.
|
||||
*/
|
||||
public function register() {
|
||||
$this->share( DownloadPermissionsAdjuster::class );
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Proxies class file.
|
||||
* ProxiesServiceProvider class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
/**
|
||||
* DownloadPermissionsAdjuster class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal;
|
||||
|
||||
use Automattic\WooCommerce\Proxies\LegacyProxy;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Class to adjust download permissions on product save.
|
||||
*/
|
||||
class DownloadPermissionsAdjuster {
|
||||
|
||||
/**
|
||||
* The downloads data store to use.
|
||||
*
|
||||
* @var WC_Data_Store
|
||||
*/
|
||||
private $downloads_data_store;
|
||||
|
||||
/**
|
||||
* Class initialization, to be executed when the class is resolved by the container.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final public function init() {
|
||||
$this->downloads_data_store = wc_get_container()->get( LegacyProxy::class )->get_instance_of( \WC_Data_Store::class, 'customer-download' );
|
||||
add_action( 'adjust_download_permissions', array( $this, 'adjust_download_permissions' ), 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a download permissions adjustment for a product if necessary.
|
||||
* This should be executed whenever a product is saved.
|
||||
*
|
||||
* @param \WC_Product $product The product to schedule a download permission adjustments for.
|
||||
*/
|
||||
public function maybe_schedule_adjust_download_permissions( \WC_Product $product ) {
|
||||
$children_ids = $product->get_children();
|
||||
if ( ! $children_ids ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scheduled_action_args = array( $product->get_id() );
|
||||
|
||||
$already_scheduled_actions =
|
||||
WC()->call_function(
|
||||
'as_get_scheduled_actions',
|
||||
array(
|
||||
'hook' => 'adjust_download_permissions',
|
||||
'args' => $scheduled_action_args,
|
||||
'status' => \ActionScheduler_Store::STATUS_PENDING,
|
||||
),
|
||||
'ids'
|
||||
);
|
||||
|
||||
if ( empty( $already_scheduled_actions ) ) {
|
||||
WC()->call_function(
|
||||
'as_schedule_single_action',
|
||||
WC()->call_function( 'time' ) + 1,
|
||||
'adjust_download_permissions',
|
||||
$scheduled_action_args
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create additional download permissions for variations if necessary.
|
||||
*
|
||||
* When a simple downloadable product is converted to a variable product,
|
||||
* existing download permissions are still present in the database but they don't apply anymore.
|
||||
* This method creates additional download permissions for the variations based on
|
||||
* the old existing ones for the main product.
|
||||
*
|
||||
* The procedure is as follows. For each existing download permission for the parent product,
|
||||
* check if there's any variation offering the same file for download (the file URL, not name, is checked).
|
||||
* If that is found, check if an equivalent permission exists (equivalent means for the same file and with
|
||||
* the same order id and customer id). If no equivalent permission exists, create it.
|
||||
*
|
||||
* @param int $product_id The id of the product to check permissions for.
|
||||
*/
|
||||
public function adjust_download_permissions( int $product_id ) {
|
||||
$product = wc_get_product( $product_id );
|
||||
|
||||
$children_ids = $product->get_children();
|
||||
if ( ! $children_ids ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parent_downloads = $this->get_download_files_and_permissions( $product );
|
||||
if ( ! $parent_downloads ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$children_with_downloads = array();
|
||||
foreach ( $children_ids as $child_id ) {
|
||||
$child = wc_get_product( $child_id );
|
||||
$children_with_downloads[ $child_id ] = $this->get_download_files_and_permissions( $child );
|
||||
}
|
||||
|
||||
foreach ( $parent_downloads['permission_data_by_file_order_user'] as $parent_file_order_and_user => $parent_download_data ) {
|
||||
foreach ( $children_with_downloads as $child_id => $child_download_data ) {
|
||||
$file_url = $parent_download_data['file'];
|
||||
|
||||
$must_create_permission =
|
||||
// The variation offers the same file as the parent for download...
|
||||
in_array( $file_url, array_keys( $child_download_data['download_ids_by_file_url'] ), true ) &&
|
||||
// ...but no equivalent download permission (same file URL, order id and user id) exists.
|
||||
! array_key_exists( $parent_file_order_and_user, $child_download_data['permission_data_by_file_order_user'] );
|
||||
|
||||
if ( $must_create_permission ) {
|
||||
// The new child download permission is a copy of the parent's,
|
||||
// but with the product and download ids changed to match those of the variation.
|
||||
$new_download_data = $parent_download_data['data'];
|
||||
$new_download_data['product_id'] = $child_id;
|
||||
$new_download_data['download_id'] = $child_download_data['download_ids_by_file_url'][ $file_url ];
|
||||
$this->downloads_data_store->create_from_data( $new_download_data );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the existing downloadable files and download permissions for a given product.
|
||||
* The returned value is an array with two keys:
|
||||
*
|
||||
* - download_ids_by_file_url: an associative array of file url => download_id.
|
||||
* - permission_data_by_file_order_user: an associative array where key is "file_url:customer_id:order_id" and value is the full permission data set.
|
||||
*
|
||||
* @param \WC_Product $product The product to get the downloadable files and permissions for.
|
||||
* @return array[] Information about the downloadable files and permissions for the product.
|
||||
*/
|
||||
private function get_download_files_and_permissions( \WC_Product $product ) {
|
||||
$result = array(
|
||||
'permission_data_by_file_order_user' => array(),
|
||||
'download_ids_by_file_url' => array(),
|
||||
);
|
||||
$downloads = $product->get_downloads();
|
||||
foreach ( $downloads as $download ) {
|
||||
$result['download_ids_by_file_url'][ $download->get_file() ] = $download->get_id();
|
||||
}
|
||||
|
||||
$permissions = $this->downloads_data_store->get_downloads( array( 'product_id' => $product->get_id() ) );
|
||||
foreach ( $permissions as $permission ) {
|
||||
$permission_data = (array) $permission->data;
|
||||
if ( array_key_exists( $permission_data['download_id'], $downloads ) ) {
|
||||
$file = $downloads[ $permission_data['download_id'] ]->get_file();
|
||||
$data = array(
|
||||
'file' => $file,
|
||||
'data' => (array) $permission->data,
|
||||
);
|
||||
$result['permission_data_by_file_order_user'][ "${file}:${permission_data['user_id']}:${permission_data['order_id']}" ] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -52,6 +52,11 @@ class LegacyProxy {
|
|||
return $class_name::instance( ...$args );
|
||||
}
|
||||
|
||||
// If the class has a "load" method, use it.
|
||||
if ( method_exists( $class_name, 'load' ) ) {
|
||||
return $class_name::load( ...$args );
|
||||
}
|
||||
|
||||
// Fallback to simply creating a new instance of the class.
|
||||
return new $class_name( ...$args );
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
/**
|
||||
* ClassWithSingleton class file.
|
||||
*/
|
||||
|
||||
// This class is in the root namespace on purpose, since it simulates being a legacy class in the 'includes' directory.
|
||||
|
||||
/**
|
||||
* An example of a class that holds a singleton instance.
|
||||
*/
|
||||
class ClassWithLoadMethod {
|
||||
|
||||
/**
|
||||
* @var ClassWithLoadMethod The last instance of the class that has been loaded.
|
||||
*/
|
||||
public static $loaded;
|
||||
|
||||
/**
|
||||
* @var array The arguments supplied to 'load'.
|
||||
*/
|
||||
public static $loaded_args;
|
||||
|
||||
/**
|
||||
* Load an instance of the class.
|
||||
*
|
||||
* @param mixed ...$args Any arguments required by the method.
|
||||
*
|
||||
* @return ClassWithLoadMethod The singleton instance of the class.
|
||||
*/
|
||||
public static function load( ...$args ) {
|
||||
self::$loaded = new ClassWithLoadMethod();
|
||||
self::$loaded_args = $args;
|
||||
return self::$loaded;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,358 @@
|
|||
<?php
|
||||
/**
|
||||
* DownloadPermissionsAdjusterTest class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Tests\Internal;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
|
||||
use Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper;
|
||||
|
||||
/**
|
||||
* Tests for DownloadPermissionsAdjuster.
|
||||
*/
|
||||
class DownloadPermissionsAdjusterTest extends \WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* The system under test.
|
||||
*
|
||||
* @var DownloadPermissionsAdjuster
|
||||
*/
|
||||
private $sut;
|
||||
|
||||
/**
|
||||
* Runs before each test.
|
||||
*/
|
||||
public function setUp() {
|
||||
$this->sut = new DownloadPermissionsAdjuster();
|
||||
$this->sut->init();
|
||||
|
||||
// This is needed for "product->set_downloads" to work without actual files.
|
||||
add_filter(
|
||||
'woocommerce_downloadable_file_allowed_mime_types',
|
||||
function() {
|
||||
return array( 'foo' => 'nonsense/foo' );
|
||||
}
|
||||
);
|
||||
add_filter(
|
||||
'woocommerce_downloadable_file_exists',
|
||||
function( $exists, $filename ) {
|
||||
return true;
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox DownloadPermissionsAdjuster class hooks on 'adjust_download_permissions' on initialization.
|
||||
*/
|
||||
public function test_class_hooks_on_adjust_download_permissions() {
|
||||
remove_all_actions( 'adjust_download_permissions' );
|
||||
$this->assertFalse( has_action( 'adjust_download_permissions' ) );
|
||||
$this->setUp();
|
||||
$this->assertTrue( has_action( 'adjust_download_permissions' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'maybe_schedule_adjust_download_permissions' does nothing if the product has no children.
|
||||
*/
|
||||
public function test_no_adjustment_is_scheduled_if_product_has_no_children() {
|
||||
$as_get_scheduled_actions_invoked = false;
|
||||
$as_schedule_single_action_invoked = false;
|
||||
|
||||
$this->register_legacy_proxy_function_mocks(
|
||||
array(
|
||||
'as_get_scheduled_actions' => function( $args, $return_format ) use ( &$as_get_scheduled_actions_invoked ) {
|
||||
$as_get_scheduled_actions_invoked = true;
|
||||
},
|
||||
'as_schedule_single_action' => function( $timestamp, $hook, $args ) use ( &$as_schedule_single_action_invoked ) {
|
||||
$as_schedule_single_action_invoked = true;
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
$product = ProductHelper::create_simple_product();
|
||||
$this->sut->maybe_schedule_adjust_download_permissions( $product );
|
||||
|
||||
$this->assertFalse( $as_get_scheduled_actions_invoked );
|
||||
$this->assertFalse( $as_schedule_single_action_invoked );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'maybe_schedule_adjust_download_permissions' does nothing if the an adjustment is already pending.
|
||||
*/
|
||||
public function test_no_adjustment_is_scheduled_if_already_scheduled() {
|
||||
$as_get_scheduled_actions_args = null;
|
||||
$as_schedule_single_action_invoked = false;
|
||||
|
||||
$this->register_legacy_proxy_function_mocks(
|
||||
array(
|
||||
'as_get_scheduled_actions' => function( $args, $return_format ) use ( &$as_get_scheduled_actions_args ) {
|
||||
$as_get_scheduled_actions_args = $args;
|
||||
return array( 1 );
|
||||
},
|
||||
'as_schedule_single_action' => function( $timestamp, $hook, $args ) use ( &$as_schedule_single_action_invoked ) {
|
||||
$as_schedule_single_action_invoked = true;
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
$product = ProductHelper::create_variation_product();
|
||||
$this->sut->maybe_schedule_adjust_download_permissions( $product );
|
||||
|
||||
$expected_get_scheduled_actions_args = array(
|
||||
'hook' => 'adjust_download_permissions',
|
||||
'args' => array( $product->get_id() ),
|
||||
'status' => \ActionScheduler_Store::STATUS_PENDING,
|
||||
);
|
||||
$this->assertEquals( $expected_get_scheduled_actions_args, $as_get_scheduled_actions_args );
|
||||
$this->assertFalse( $as_schedule_single_action_invoked );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'maybe_schedule_adjust_download_permissions' schedules an adjustment if not scheduled already.
|
||||
*/
|
||||
public function test_no_adjustment_is_scheduled_if_not_yet_scheduled() {
|
||||
$as_get_scheduled_actions_args = null;
|
||||
$as_schedule_single_action_args = null;
|
||||
|
||||
$this->register_legacy_proxy_function_mocks(
|
||||
array(
|
||||
'as_get_scheduled_actions' => function( $params, $return_format ) use ( &$as_get_scheduled_actions_args ) {
|
||||
$as_get_scheduled_actions_args = $params;
|
||||
return array();
|
||||
},
|
||||
'as_schedule_single_action' => function( $timestamp, $hook, $args ) use ( &$as_schedule_single_action_args ) {
|
||||
$as_schedule_single_action_args = array( $timestamp, $hook, $args );
|
||||
},
|
||||
'time' => function() {
|
||||
return 0; },
|
||||
)
|
||||
);
|
||||
|
||||
$product = ProductHelper::create_variation_product();
|
||||
$this->sut->maybe_schedule_adjust_download_permissions( $product );
|
||||
|
||||
$expected_get_scheduled_actions_args = array(
|
||||
'hook' => 'adjust_download_permissions',
|
||||
'args' => array( $product->get_id() ),
|
||||
'status' => \ActionScheduler_Store::STATUS_PENDING,
|
||||
);
|
||||
$this->assertEquals( $expected_get_scheduled_actions_args, $as_get_scheduled_actions_args );
|
||||
|
||||
$expected_as_schedule_single_action_args = array(
|
||||
1,
|
||||
'adjust_download_permissions',
|
||||
array( $product->get_id() ),
|
||||
);
|
||||
$this->assertEquals( $expected_as_schedule_single_action_args, $as_schedule_single_action_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'adjust_download_permissions' creates child download permissions when they are missing (see method comment for details).
|
||||
*/
|
||||
public function test_adjust_download_permissions_creates_additional_permissions_if_not_exist() {
|
||||
$download = array(
|
||||
'name' => 'the_file',
|
||||
'file' => 'the_file.foo',
|
||||
);
|
||||
|
||||
$product = ProductHelper::create_variation_product();
|
||||
$product->set_downloads( array( $download ) );
|
||||
$product->save();
|
||||
$parent_download_id = current( $product->get_downloads() )->get_id();
|
||||
|
||||
$child = wc_get_product( current( $product->get_children() ) );
|
||||
$child->set_downloads( array( $download ) );
|
||||
$child->save();
|
||||
$child_download_id = current( $child->get_downloads() )->get_id();
|
||||
|
||||
$data_for_data_store =
|
||||
array(
|
||||
$product->get_id() =>
|
||||
array(
|
||||
(object) array(
|
||||
'data' => array(
|
||||
'download_id' => $parent_download_id,
|
||||
'user_id' => 1234,
|
||||
'order_id' => 5678,
|
||||
'downloads_remaining' => 34,
|
||||
'access_granted' => '2000-01-01',
|
||||
'access_expires' => '2034-02-27',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$data_store = $this->create_mock_data_store( $data_for_data_store );
|
||||
|
||||
$this->setUp();
|
||||
$this->sut->adjust_download_permissions( $product->get_id() );
|
||||
|
||||
$expected_created_data = array(
|
||||
'download_id' => $child_download_id,
|
||||
'user_id' => 1234,
|
||||
'order_id' => 5678,
|
||||
'product_id' => $child->get_id(),
|
||||
'downloads_remaining' => 34,
|
||||
'access_granted' => '2000-01-01',
|
||||
'access_expires' => '2034-02-27',
|
||||
);
|
||||
|
||||
$this->assertEquals( $expected_created_data, $data_store->created_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'adjust_download_permissions' doesn't create child download permissions that already exist.
|
||||
*/
|
||||
public function test_adjust_download_permissions_dont_create_additional_permissions_if_already_exists() {
|
||||
$download = array(
|
||||
'name' => 'the_file',
|
||||
'file' => 'the_file.foo',
|
||||
);
|
||||
|
||||
$product = ProductHelper::create_variation_product();
|
||||
$product->set_downloads( array( $download ) );
|
||||
$product->save();
|
||||
$parent_download_id = current( $product->get_downloads() )->get_id();
|
||||
|
||||
$child = wc_get_product( current( $product->get_children() ) );
|
||||
$child->set_downloads( array( $download ) );
|
||||
$child->save();
|
||||
$child_download_id = current( $child->get_downloads() )->get_id();
|
||||
|
||||
$data_for_data_store =
|
||||
array(
|
||||
$product->get_id() =>
|
||||
array(
|
||||
(object) array(
|
||||
'data' => array(
|
||||
'download_id' => $parent_download_id,
|
||||
'user_id' => 1234,
|
||||
'order_id' => 5678,
|
||||
),
|
||||
),
|
||||
),
|
||||
$child->get_id() =>
|
||||
array(
|
||||
(object) array(
|
||||
'data' => array(
|
||||
'download_id' => $child_download_id,
|
||||
'user_id' => 1234,
|
||||
'order_id' => 5678,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$data_store = $this->create_mock_data_store( $data_for_data_store );
|
||||
|
||||
$this->setUp();
|
||||
$this->sut->adjust_download_permissions( $product->get_id() );
|
||||
|
||||
$this->assertEmpty( $data_store->created_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'adjust_download_permissions' creates child download permissions when one exists but for a different order or customer id.
|
||||
*
|
||||
* @testWith [9999, 5678]
|
||||
* [1234, 9999]
|
||||
* @param int $user_id User id the child download permission exists for.
|
||||
* @param int $order_id Order id the child download permission exists for.
|
||||
*/
|
||||
public function test_adjust_download_permissions_creates_additional_permissions_if_exists_but_not_matching( $user_id, $order_id ) {
|
||||
$download = array(
|
||||
'name' => 'the_file',
|
||||
'file' => 'the_file.foo',
|
||||
);
|
||||
|
||||
$product = ProductHelper::create_variation_product();
|
||||
$product->set_downloads( array( $download ) );
|
||||
$product->save();
|
||||
$parent_download_id = current( $product->get_downloads() )->get_id();
|
||||
|
||||
$child = wc_get_product( current( $product->get_children() ) );
|
||||
$child->set_downloads( array( $download ) );
|
||||
$child->save();
|
||||
$child_download_id = current( $child->get_downloads() )->get_id();
|
||||
|
||||
$data_for_data_store =
|
||||
array(
|
||||
$product->get_id() =>
|
||||
array(
|
||||
(object) array(
|
||||
'data' => array(
|
||||
'download_id' => $parent_download_id,
|
||||
'user_id' => 1234,
|
||||
'order_id' => 5678,
|
||||
),
|
||||
),
|
||||
),
|
||||
$child->get_id() =>
|
||||
array(
|
||||
(object) array(
|
||||
'data' => array(
|
||||
'download_id' => $child_download_id,
|
||||
'user_id' => $user_id,
|
||||
'order_id' => $order_id,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$data_store = $this->create_mock_data_store( $data_for_data_store );
|
||||
|
||||
$this->setUp();
|
||||
$this->sut->adjust_download_permissions( $product->get_id() );
|
||||
|
||||
$expected = array(
|
||||
'download_id' => $child_download_id,
|
||||
'user_id' => 1234,
|
||||
'order_id' => 5678,
|
||||
'product_id' => $child->get_id(),
|
||||
);
|
||||
|
||||
$this->assertEquals( $expected, $data_store->created_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and register a mock customer downloads data store.
|
||||
*
|
||||
* @param array $data An array where keys are product ids, and values are what 'get_downloads' will return for that input.
|
||||
* @return object An object that mocks the customer downloads data store.
|
||||
*/
|
||||
private function create_mock_data_store( $data ) {
|
||||
// phpcs:disable Squiz.Commenting
|
||||
$data_store = new class($data) {
|
||||
private $data;
|
||||
public $created_data = null;
|
||||
|
||||
public function __construct( $data ) {
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function get_downloads( $params ) {
|
||||
if ( array_key_exists( $params['product_id'], $this->data ) ) {
|
||||
return $this->data[ $params['product_id'] ];
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
public function create_from_data( $data ) {
|
||||
$this->created_data = $data;
|
||||
}
|
||||
};
|
||||
// phpcs:enable Squiz.Commenting
|
||||
|
||||
$this->register_legacy_proxy_class_mocks(
|
||||
array(
|
||||
'WC_Data_Store' => $data_store,
|
||||
)
|
||||
);
|
||||
|
||||
return $data_store;
|
||||
}
|
||||
}
|
|
@ -60,6 +60,18 @@ class LegacyProxyTest extends \WC_Unit_Test_Case {
|
|||
$this->assertEquals( array( 'foo', 'bar' ), \ClassWithSingleton::$instance_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'get_instance_of' uses the 'load' static method in classes that implement it, passing the supplied arguments.
|
||||
*/
|
||||
public function test_get_instance_of_class_with_load_method_gets_an_instance_of_the_appropriate_class() {
|
||||
// ClassWithLoadMethod is in the root namespace and thus can't be autoloaded.
|
||||
require_once dirname( __DIR__ ) . '/Internal/DependencyManagement/ExampleClasses/ClassWithLoadMethod.php';
|
||||
|
||||
$loaded = $this->sut->get_instance_of( \ClassWithLoadMethod::class, 'foo', 'bar' );
|
||||
$this->assertSame( \ClassWithLoadMethod::$loaded, $loaded );
|
||||
$this->assertEquals( array( 'foo', 'bar' ), \ClassWithLoadMethod::$loaded_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'get_instance_of' can be used to get an instance of a class implementing WC_Queue_Interface.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue