Files
wc-licensed-product/templates/admin/licenses.html.twig
magdev 45531f86d6 Implement version 0.0.11 features
- Add Created date column to admin license overview
- Add License Statistics page under WooCommerce menu
- Add REST API endpoints for analytics data with time-series support
- WooCommerce Analytics integration via submenu page

New files:
- src/Admin/AnalyticsController.php
- templates/admin/statistics.html.twig

REST API endpoints:
- GET /wc-licensed-product/v1/analytics/stats
- GET /wc-licensed-product/v1/analytics/products

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 23:50:57 +01:00

354 lines
19 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>{{ __('Created') }}</th>
<th>{{ __('Expires') }}</th>
<th>{{ __('Actions') }}</th>
</tr>
</thead>
<tbody>
{% if licenses is empty %}
<tr>
<td colspan="9">{{ __('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 class="wclp-license-key">{{ item.license.licenseKey }}</code>
<button type="button" class="wclp-copy-btn button-link" data-license-key="{{ esc_attr(item.license.licenseKey) }}" title="{{ __('Copy to clipboard') }}">
<span class="dashicons dashicons-clipboard"></span>
</button>
</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 class="wclp-editable-cell" data-field="domain" data-license-id="{{ item.license.id }}">
<span class="wclp-display-value">{{ esc_html(item.license.domain) }}</span>
<button type="button" class="wclp-edit-btn button-link" title="{{ __('Edit') }}">
<span class="dashicons dashicons-edit"></span>
</button>
<div class="wclp-edit-form" style="display:none;">
<input type="text" class="wclp-edit-input" value="{{ esc_attr(item.license.domain) }}">
<button type="button" class="wclp-save-btn button button-small button-primary">{{ __('Save') }}</button>
<button type="button" class="wclp-cancel-btn button button-small">{{ __('Cancel') }}</button>
</div>
</td>
<td class="wclp-editable-cell" data-field="status" data-license-id="{{ item.license.id }}">
<span class="wclp-display-value">
<span class="license-status license-status-{{ item.license.status }}">
{{ item.license.status|capitalize }}
</span>
</span>
<button type="button" class="wclp-edit-btn button-link" title="{{ __('Edit') }}">
<span class="dashicons dashicons-edit"></span>
</button>
<div class="wclp-edit-form" style="display:none;">
<select class="wclp-edit-input">
<option value="active" {{ item.license.status == 'active' ? 'selected' : '' }}>{{ __('Active') }}</option>
<option value="inactive" {{ item.license.status == 'inactive' ? 'selected' : '' }}>{{ __('Inactive') }}</option>
<option value="expired" {{ item.license.status == 'expired' ? 'selected' : '' }}>{{ __('Expired') }}</option>
<option value="revoked" {{ item.license.status == 'revoked' ? 'selected' : '' }}>{{ __('Revoked') }}</option>
</select>
<button type="button" class="wclp-save-btn button button-small button-primary">{{ __('Save') }}</button>
<button type="button" class="wclp-cancel-btn button button-small">{{ __('Cancel') }}</button>
</div>
</td>
<td class="wclp-created-cell">
{{ item.license.createdAt|date('Y-m-d') }}
</td>
<td class="wclp-editable-cell" data-field="expiry" data-license-id="{{ item.license.id }}">
<span class="wclp-display-value">
{% if item.license.expiresAt %}
{{ item.license.expiresAt|date('Y-m-d') }}
{% else %}
<span class="license-lifetime">{{ __('Lifetime') }}</span>
{% endif %}
</span>
<button type="button" class="wclp-edit-btn button-link" title="{{ __('Edit') }}">
<span class="dashicons dashicons-edit"></span>
</button>
<div class="wclp-edit-form" style="display:none;">
<input type="date" class="wclp-edit-input" value="{{ item.license.expiresAt ? item.license.expiresAt|date('Y-m-d') : '' }}" placeholder="{{ __('Leave empty for lifetime') }}">
<button type="button" class="wclp-save-btn button button-small button-primary">{{ __('Save') }}</button>
<button type="button" class="wclp-cancel-btn button button-small">{{ __('Cancel') }}</button>
<button type="button" class="wclp-lifetime-btn button button-small" title="{{ __('Set to lifetime') }}">∞</button>
</div>
</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>{{ __('Created') }}</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>