agentsclimarketplace

Wcs subscription downloads

Skill Lonsdale201/wp-agent-skills/woocommerce/wcs-subscription-downloads

A community-maintained collection of agent skills for WordPress plugin and theme development.

Install
npx -y skills add Lonsdale201/wp-agent-skills --skill wcs-subscription-downloads

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 21 stars21 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Implement, extend, or audit WooCommerce Subscriptions download access. Distinguishes ordinary downloadable subscription line items from the bundled linked Subscription Downloads feature; covers WCS_Download_Handler, shop_subscription-owned permissions, renewal de-duplication, new-file drip behavior, subscription status access gates, the woocommerce_subscription_downloads catalog mapping, active/cancel/expire grants and revokes, counter resets, optional zero-cost line items, projections, switching, emails, and WCS Gifting. Use for missing or duplicate subscription downloads, WC_Subscription_Downloads, linked/shared files, drip downloadable content, subscription renewal access, or downloadable permissions tied to a subscription.

SKILL.md

13.4 KB, as published. Nobody here has run it

WooCommerce Subscriptions downloads

Start by identifying which of WCS's two download models is in use. Both ultimately use WooCommerce's core customer-download permission table and delivery handler, but their product relationships and refresh behavior differ.

Choose the correct model

ModelProduct setupRelationship sourcePermission owner
Ordinary downloadable subscriptionThe subscription product/variation itself is downloadable and has filesActual subscription line itemshop_subscription object
Linked Subscription DownloadsA separate simple/variable downloadable product is linked to a subscription productwoocommerce_subscription_downloads mapping tableshop_subscription object

Do not collapse these models. A normal downloadable subscription product does not need the linked-download feature. A linked product may grant permissions without appearing in $subscription->get_items().

Use wc-downloadable-products first when the question concerns core file objects, stable IDs, storage security, bearer URLs, download methods, limits, expiry, or the permission table itself.

Ordinary downloadable subscription items

WCS_Download_Handler runs after Woo grants files on a parent, renewal, or switch order. For each downloadable subscription item, it:

  1. finds the related WC_Subscription objects;
  2. grants every current product file on the subscription if that exact subscription/product/download row is absent;
  3. removes the same product's temporary permissions from the originating order;
  4. marks the subscription's download permissions as granted.

The durable grant therefore uses the subscription ID and subscription order key. A paid renewal should not create a fresh duplicate row when that file permission already exists.

Do not assume that a permission row means current access. WC_Subscription::is_download_permitted() normally returns true only for active and pending-cancel; account projection and the core download handler call this gate.

Subscription statusRow may remain?Customer access?
activeYesYes
pending-cancelYesYes, until the subscription ends
on-holdYesNo
pendingYesNo
expiredModel-dependent cleanupNo
cancelledModel-dependent cleanupNo

Email rendering has temporary exceptions because WCS may generate an email before the subscription reaches its final active state. Never copy that display-only exception into a custom API authorization decision.

Permanent subscription deletion removes its ordinary permission rows. Switching or removing a subscription product revokes permissions for the replaced product. In WCS 9.0.1 these revocation paths use direct permission-table deletes; see the orphan-log caveat below.

New files and drip behavior

When a new stable download ID is added to an ordinary downloadable subscription product, WCS finds subscriptions for that product and grants the missing file permission. Existing IDs are not re-granted.

The option:

woocommerce_subscriptions_drip_downloadable_content_on_renewal

defaults to no.

  • no: existing subscriptions receive a newly added file immediately.
  • yes: WCS blocks the immediate grant through woocommerce_process_product_file_download_paths_grant_access_to_new_file; the next processed renewal supplies access.

The filter is also used for related orders, so custom callbacks must inspect the supplied WC_Order/WC_Subscription rather than assuming one object type. Drip applies to newly added file IDs, not a general per-file release-date scheduler.

Linked Subscription Downloads feature

The former standalone WooCommerce Subscription Downloads plugin is bundled into WCS. WC_Subscriptions_Plugin::init_downloads() loads the bundled feature unless the standalone implementation is being activated or has already defined WC_Subscription_Downloads. Do not run both.

Settings under WooCommerce > Settings > Subscriptions:

OptionDefaultMeaning
woocommerce_subscriptions_enable_downloadable_file_linkingnoLoads linked-product order/product/AJAX behavior.
woocommerce_subscriptions_downloads_add_line_itemsnoShows linked products as zero-cost subscription line items. Disable for less work on large link sets.

Admin settings still load while linking is disabled, but runtime linked-download subsystems do not.

Linked mapping is catalog data

{$wpdb->prefix}woocommerce_subscription_downloads has:

ColumnMeaning
idAuto-increment primary key.
product_idLinked downloadable product or variation catalog ID.
subscription_idSubscription product or variation catalog ID, not a customer's subscription instance.

There is no unique product/subscription pair constraint and no foreign key. Avoid direct writes: duplicates are possible, and SQL changes skip permission recalculation.

Read relationships with:

$subscription_product_ids = WC_Subscription_Downloads::get_subscriptions( $downloadable_product_id );

$downloadable_product_ids = WC_Subscription_Downloads::get_downloadable_products(
	$subscription_product_id,
	$subscription_variation_id
);

The admin product editor supports both directions. Use its validated save path, or build an explicit service that deduplicates mappings and performs the same revoke/regrant behavior. These WCS classes are bundled implementation APIs, so guard availability and regression-test upgrades.

Linked permission lifecycle

WC_Subscription_Downloads_Order listens to subscription status changes:

New statusLinked behavior
activeRevokes existing linked-product rows, grants every current enabled file again, optionally adds a zero-cost line item.
expiredRevokes linked-product rows.
cancelledRevokes linked-product rows.
other statusDoes not mutate linked rows; the subscription access gate still denies non-permitted statuses.

Two consequences matter:

  • Reactivating a subscription resets linked-file counts and expiry because permissions are deleted and recreated.
  • Saving/updating a linked downloadable product causes the product handler to revoke related permissions and regrant them when the product status is public. This can also reset counts/expiry even if the file list did not materially change.

There is also a confirmed cleanup defect in WCS 9.0.1: WCS_Download_Handler::revoke_downloadable_file_permission(), permanent subscription cleanup, and linked-product revoke handlers delete permission rows with direct SQL. wc_download_log has no foreign key cascade, so tracked permissions can leave orphan log rows after revocation/regrant. A local smoke run reproduced this during linked active refresh.

Do not copy this deletion pattern. Extension code should resolve the affected permission IDs and delete them through the Woo customer-download data store, which removes their logs, or perform an explicitly transactional logs-first cleanup. Audit existing data read-only with:

SELECT COUNT(*)
FROM wp_wc_download_log AS logs
LEFT JOIN wp_woocommerce_downloadable_product_permissions AS permissions
	ON permissions.permission_id = logs.permission_id
WHERE permissions.permission_id IS NULL;

Use the actual table prefix. Do not automatically delete historical rows without a retention decision and backup.

Do not use linked permission rows as an immutable audit ledger. If counters must survive reactivation or product updates, add a separate durable business ledger and a tested restore policy rather than patching the core table ad hoc.

Line items and display projection

Permissions are granted whether or not linked products become line items. The default is no line items. The decision is available through:

WC_Subscription_Downloads_Settings::add_line_items_enabled();

and:

add_filter(
	'woocommerce_subscriptions_add_downloadable_product_line_item',
	function ( bool $add, WC_Product $product, WC_Subscription $subscription ): bool {
		return myplugin_needs_visible_linked_items( $subscription->get_id() ) ? true : $add;
	},
	10,
	3
);

For account/headless display, query the projection instead of line items:

$linked = WC_Subscription_Downloads::get_subscription_linked_downloads( $subscription, 50 );

foreach ( $linked['downloads'] as $download ) {
	my_render_download( $download );
}

$total_linked_products = $linked['total_products']; // Count before the limit.

The projection returns nothing when the subscription does not permit downloads, deduplicates catalog IDs, batch-loads published products, and then resolves actual permission-backed files. The limit counts linked products, not individual files.

Never generate a link from the mapping table or product file URL alone. The actual Woo permission row and subscription access gate remain authoritative.

Switching, email, and gifting

  • A switch removes old linked line items where present, revokes old linked permissions, and re-runs the active grant for the new item.
  • WC_Subscription_Downloads::get_order_downloads() and the linked projection produce Woo-compatible permission URLs for emails/account views.
  • woocommerce_subscription_downloads_my_downloads_title changes the linked downloads heading, not authorization.
  • WCS Gifting alters woocommerce_downloadable_file_permission_data so the recipient can own the permission and, depending on settings, the purchaser may also receive one. Do not equate entitlement owner with $subscription->get_user_id().

Common failure patterns

// WRONG: linked products need not be line items.
foreach ( $subscription->get_items() as $item ) {
	if ( $item->get_product() && $item->get_product()->is_downloadable() ) {
		export_file_url( $item->get_product() );
	}
}

// WRONG: the mapping table's subscription_id is not a WC_Subscription instance ID.
$customer_subscription = wcs_get_subscription( $mapping_row->subscription_id );

// WRONG: a stored permission does not bypass the status gate.
$allowed = ! empty( $permission_row );

// RIGHT: use the order/subscription access gate and a permission-backed projection.
$allowed   = $subscription->is_download_permitted();
$downloads = $allowed
	? WC_Subscription_Downloads::get_subscription_linked_downloads( $subscription, 50 )
	: array( 'downloads' => array(), 'total_products' => 0 );

Regression matrix

Test both models separately:

  1. Initial paid order and first permission ownership.
  2. Successful renewal without duplicate ordinary permissions.
  3. New file with drip off, then with drip on through the next processed renewal.
  4. active, pending-cancel, on-hold, reactivation, expired, and cancelled transitions.
  5. Linked-product save/status/file change and expected counter-reset policy.
  6. Switch from parent/variation A to B.
  7. Zero-cost line items both disabled and enabled.
  8. Gift recipient, purchaser setting, and wrong-user access.
  9. Stable file-ID replacement/removal, limit, expiry, and direct-storage URL security from wc-downloadable-products.
  10. Revocation/regrant and permanent deletion with a wc_download_log orphan check.
  11. High link counts with a finite projection limit.

Cross-references

  • Use wc-downloadable-products for the core file, entitlement, delivery, security, and REST contract.
  • Use wcs-data-model-switching-gifting for switch payloads and gifting ownership data.
  • Use wcs-subscription-hooks for status, renewal, switch, and gifting hooks.
  • Use wcs-renewal-scheduler for renewal payment timing and failure recovery.

Verified sources

  • wp-content/plugins/woocommerce-subscriptions/changelog.txt
  • wp-content/plugins/woocommerce-subscriptions/includes/core/class-wcs-download-handler.php
  • wp-content/plugins/woocommerce-subscriptions/includes/class-wcs-drip-downloads-manager.php
  • wp-content/plugins/woocommerce-subscriptions/includes/core/class-wc-subscription.php
  • wp-content/plugins/woocommerce-subscriptions/includes/downloads/class-wc-subscription-downloads.php
  • wp-content/plugins/woocommerce-subscriptions/includes/downloads/class-wc-subscription-downloads-order.php
  • wp-content/plugins/woocommerce-subscriptions/includes/downloads/class-wc-subscription-downloads-products.php
  • wp-content/plugins/woocommerce-subscriptions/includes/downloads/class-wc-subscription-downloads-settings.php
  • wp-content/plugins/woocommerce-subscriptions/includes/gifting/class-wcsg-download-handler.php

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.