Files
wc-licensed-product/templates/admin/licenses.html.twig
magdev 49a0699963 Implement versions 0.0.4-0.0.7 features
v0.0.4:
- Add WooCommerce settings tab for default license settings
- Per-product settings override global defaults

v0.0.5:
- Add bulk license operations (activate, deactivate, revoke, extend, delete)
- Add license renewal/extension and lifetime functionality
- Add quick action buttons per license row

v0.0.6:
- Add license dashboard with statistics and analytics
- Add license transfer functionality (admin)
- Add CSV export for licenses
- Add OpenAPI 3.1 specification
- Remove /deactivate API endpoint

v0.0.7:
- Move license dashboard to WooCommerce Reports section
- Add license search and filtering in admin
- Add customer-facing license transfer with AJAX modal
- Add email notifications for license expiration warnings
- Add bulk import licenses from CSV
- Update README with comprehensive documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 20:32:35 +01:00

308 lines
15 KiB
Twig
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<div class="wrap">
<h1 class="wp-heading-inline">{{ __('Licenses') }}</h1>
<a href="{{ admin_url }}?action=export_csv" class="page-title-action">
<span class="dashicons dashicons-download" style="vertical-align: middle;"></span>
{{ __('Export CSV') }}
</a>
<a href="{{ admin_url }}?action=import_csv" class="page-title-action">
<span class="dashicons dashicons-upload" style="vertical-align: middle;"></span>
{{ __('Import CSV') }}
</a>
<hr class="wp-header-end">
{% for notice in notices %}
<div class="notice notice-{{ notice.type }} is-dismissible">
<p>{{ esc_html(notice.message) }}</p>
</div>
{% endfor %}
<!-- Search and Filter Form -->
<form method="get" action="" class="wclp-filter-form">
<input type="hidden" name="page" value="wc-licenses">
<p class="search-box">
<label class="screen-reader-text" for="license-search-input">{{ __('Search Licenses') }}</label>
<input type="search" id="license-search-input" name="s" value="{{ filters.search|default('') }}"
placeholder="{{ __('Search license key or domain...') }}">
<input type="submit" id="search-submit" class="button" value="{{ __('Search') }}">
</p>
<div class="tablenav top">
<div class="alignleft actions">
<select name="status">
<option value="all">{{ __('All Statuses') }}</option>
<option value="active" {{ filters.status|default('') == 'active' ? 'selected' : '' }}>{{ __('Active') }}</option>
<option value="inactive" {{ filters.status|default('') == 'inactive' ? 'selected' : '' }}>{{ __('Inactive') }}</option>
<option value="expired" {{ filters.status|default('') == 'expired' ? 'selected' : '' }}>{{ __('Expired') }}</option>
<option value="revoked" {{ filters.status|default('') == 'revoked' ? 'selected' : '' }}>{{ __('Revoked') }}</option>
</select>
<select name="product_id">
<option value="">{{ __('All Products') }}</option>
{% for id, name in products %}
<option value="{{ id }}" {{ filters.product_id|default('') == id ? 'selected' : '' }}>{{ esc_html(name) }}</option>
{% endfor %}
</select>
<input type="submit" class="button" value="{{ __('Filter') }}">
{% if filters is not empty %}
<a href="{{ admin_url }}" class="button">{{ __('Clear') }}</a>
{% endif %}
</div>
<div class="tablenav-pages">
<span class="displaying-num">{{ total_licenses }} {{ total_licenses == 1 ? __('item') : __('items') }}</span>
</div>
</div>
</form>
<p class="description">
{{ __('Showing') }} {{ total_licenses }} {{ total_licenses == 1 ? __('license') : __('licenses') }}
{% if filters is not empty %}
({{ __('filtered') }})
{% endif %}
| <a href="{{ constant('admin_url')('admin.php?page=wc-reports&tab=licenses') }}">{{ __('View Dashboard') }}</a>
</p>
<form method="post" action="{{ admin_url }}" id="licenses-bulk-form">
{{ wp_nonce_field('bulk_license_action', '_wpnonce', true, false)|raw }}
<div class="tablenav top">
<div class="alignleft actions bulkactions">
<select name="bulk_action" id="bulk-action-selector">
<option value="">{{ __('Bulk Actions') }}</option>
<option value="activate">{{ __('Activate') }}</option>
<option value="deactivate">{{ __('Deactivate') }}</option>
<option value="revoke">{{ __('Revoke') }}</option>
<option value="extend_30">{{ __('Extend 30 days') }}</option>
<option value="extend_90">{{ __('Extend 90 days') }}</option>
<option value="extend_365">{{ __('Extend 1 year') }}</option>
<option value="delete">{{ __('Delete') }}</option>
</select>
<input type="submit" class="button action" value="{{ __('Apply') }}">
</div>
</div>
<table class="wp-list-table widefat fixed striped licenses-table">
<thead>
<tr>
<td class="manage-column column-cb check-column">
<input type="checkbox" id="cb-select-all-1">
</td>
<th>{{ __('License Key') }}</th>
<th>{{ __('Product') }}</th>
<th>{{ __('Customer') }}</th>
<th>{{ __('Domain') }}</th>
<th>{{ __('Status') }}</th>
<th>{{ __('Expires') }}</th>
<th>{{ __('Actions') }}</th>
</tr>
</thead>
<tbody>
{% if licenses is empty %}
<tr>
<td colspan="8">{{ __('No licenses found.') }}</td>
</tr>
{% else %}
{% for item in licenses %}
<tr>
<th scope="row" class="check-column">
<input type="checkbox" name="license_ids[]" value="{{ item.license.id }}">
</th>
<td><code>{{ item.license.licenseKey }}</code></td>
<td>
{% if item.product_edit_url %}
<a href="{{ esc_url(item.product_edit_url) }}">{{ esc_html(item.product_name) }}</a>
{% else %}
{{ esc_html(item.product_name) }}
{% endif %}
</td>
<td>
{{ esc_html(item.customer_name) }}
{% if item.customer_email %}
<br><small>{{ esc_html(item.customer_email) }}</small>
{% endif %}
</td>
<td>{{ esc_html(item.license.domain) }}</td>
<td>
<span class="license-status license-status-{{ item.license.status }}">
{{ item.license.status|capitalize }}
</span>
</td>
<td>
{% if item.license.expiresAt %}
{{ item.license.expiresAt|date('Y-m-d') }}
{% else %}
<span class="license-lifetime">{{ __('Lifetime') }}</span>
{% endif %}
</td>
<td class="license-actions">
<div class="row-actions">
{% if item.license.status != 'revoked' %}
<span class="transfer">
<a href="#" class="wclp-transfer-link"
data-license-id="{{ item.license.id }}"
data-current-domain="{{ esc_attr(item.license.domain) }}"
title="{{ __('Transfer to new domain') }}">{{ __('Transfer') }}</a> |
</span>
<span class="extend">
<a href="{{ extend_url(item.license.id, 30) }}" title="{{ __('Extend by 30 days') }}">+30d</a> |
</span>
<span class="lifetime">
<a href="{{ lifetime_url(item.license.id) }}" title="{{ __('Set to lifetime') }}">∞</a> |
</span>
<span class="revoke">
<a href="{{ revoke_url(item.license.id) }}"
onclick="return confirm('{{ __('Are you sure?') }}')">{{ __('Revoke') }}</a> |
</span>
{% endif %}
<span class="delete">
<a href="{{ delete_url(item.license.id) }}"
class="submitdelete"
onclick="return confirm('{{ __('Are you sure you want to delete this license?') }}')">{{ __('Delete') }}</a>
</span>
</div>
</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
<tfoot>
<tr>
<td class="manage-column column-cb check-column">
<input type="checkbox" id="cb-select-all-2">
</td>
<th>{{ __('License Key') }}</th>
<th>{{ __('Product') }}</th>
<th>{{ __('Customer') }}</th>
<th>{{ __('Domain') }}</th>
<th>{{ __('Status') }}</th>
<th>{{ __('Expires') }}</th>
<th>{{ __('Actions') }}</th>
</tr>
</tfoot>
</table>
<div class="tablenav bottom">
<div class="alignleft actions bulkactions">
<select name="bulk_action_2" id="bulk-action-selector-bottom">
<option value="">{{ __('Bulk Actions') }}</option>
<option value="activate">{{ __('Activate') }}</option>
<option value="deactivate">{{ __('Deactivate') }}</option>
<option value="revoke">{{ __('Revoke') }}</option>
<option value="extend_30">{{ __('Extend 30 days') }}</option>
<option value="extend_90">{{ __('Extend 90 days') }}</option>
<option value="extend_365">{{ __('Extend 1 year') }}</option>
<option value="delete">{{ __('Delete') }}</option>
</select>
<input type="submit" class="button action" value="{{ __('Apply') }}">
</div>
{% if total_pages > 1 %}
{% set filter_params = '' %}
{% if filters.search is defined and filters.search %}{% set filter_params = filter_params ~ '&s=' ~ filters.search %}{% endif %}
{% if filters.status is defined and filters.status %}{% set filter_params = filter_params ~ '&status=' ~ filters.status %}{% endif %}
{% if filters.product_id is defined and filters.product_id %}{% set filter_params = filter_params ~ '&product_id=' ~ filters.product_id %}{% endif %}
<div class="tablenav-pages">
<span class="pagination-links">
{% if current_page > 1 %}
<a class="prev-page button" href="{{ admin_url ~ '&paged=' ~ (current_page - 1) ~ filter_params }}">
<span aria-hidden="true"></span>
</a>
{% endif %}
<span class="paging-input">
{{ current_page }} {{ __('of') }} {{ total_pages }}
</span>
{% if current_page < total_pages %}
<a class="next-page button" href="{{ admin_url ~ '&paged=' ~ (current_page + 1) ~ filter_params }}">
<span aria-hidden="true"></span>
</a>
{% endif %}
</span>
</div>
{% endif %}
</div>
</form>
</div>
<!-- Transfer Modal -->
<div id="wclp-transfer-modal" class="wclp-modal" style="display:none;">
<div class="wclp-modal-content">
<span class="wclp-modal-close">&times;</span>
<h2>{{ __('Transfer License to New Domain') }}</h2>
<form method="post" action="{{ admin_url }}">
<input type="hidden" name="action" value="transfer_license">
<input type="hidden" name="_wpnonce" value="{{ transfer_nonce() }}">
<input type="hidden" name="license_id" id="transfer-license-id" value="">
<table class="form-table">
<tr>
<th scope="row"><label>{{ __('Current Domain') }}</label></th>
<td><code id="transfer-current-domain"></code></td>
</tr>
<tr>
<th scope="row"><label for="new_domain">{{ __('New Domain') }}</label></th>
<td>
<input type="text" name="new_domain" id="transfer-new-domain" class="regular-text"
placeholder="example.com" required>
<p class="description">{{ __('Enter the new domain without http:// or www.') }}</p>
</td>
</tr>
</table>
<p class="submit">
<button type="submit" class="button button-primary">{{ __('Transfer License') }}</button>
<button type="button" class="button wclp-modal-cancel">{{ __('Cancel') }}</button>
</p>
</form>
</div>
</div>
<script>
(function($) {
// Select all checkboxes
$('#cb-select-all-1, #cb-select-all-2').on('change', function() {
$('input[name="license_ids[]"]').prop('checked', this.checked);
$('#cb-select-all-1, #cb-select-all-2').prop('checked', this.checked);
});
// Sync bulk action selects
$('#bulk-action-selector, #bulk-action-selector-bottom').on('change', function() {
var value = $(this).val();
$('#bulk-action-selector, #bulk-action-selector-bottom').val(value);
});
// Use bottom select value if top is empty
$('form').on('submit', function() {
var topAction = $('#bulk-action-selector').val();
var bottomAction = $('#bulk-action-selector-bottom').val();
if (!topAction && bottomAction) {
$('#bulk-action-selector').val(bottomAction);
}
});
// Transfer modal
var $modal = $('#wclp-transfer-modal');
$('.wclp-transfer-link').on('click', function(e) {
e.preventDefault();
var licenseId = $(this).data('license-id');
var currentDomain = $(this).data('current-domain');
$('#transfer-license-id').val(licenseId);
$('#transfer-current-domain').text(currentDomain);
$('#transfer-new-domain').val('');
$modal.show();
});
$('.wclp-modal-close, .wclp-modal-cancel').on('click', function() {
$modal.hide();
});
$(window).on('click', function(e) {
if ($(e.target).is($modal)) {
$modal.hide();
}
});
})(jQuery);
</script>