13 Commits

Author SHA1 Message Date
5834e067f4 Change license client to use git repository instead of local path
- Updated composer.json repository from local path to git URL
- Package magdev/wc-licensed-product-client now fetched from:
  https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client.git
- Fixes symlink issues in release packages

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 19:19:44 +01:00
79417e4971 Update translations for v0.3.2
- Regenerated POT template with updated version
- Updated German (de_CH) translation
- Compiled .mo file

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 19:14:24 +01:00
304eb16e2e Update README with response signing documentation
- Added Response Signing section explaining X-License-Signature and X-License-Timestamp headers
- Added wp-config.php configuration example for WC_LICENSE_SERVER_SECRET
- Updated client section to recommend official magdev/wc-licensed-product-client Composer package
- Documented LicenseClient and SecureLicenseClient classes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 19:12:58 +01:00
df4cfc7e84 Update OpenAPI specification for v0.3.2
- Updated OpenAPI version from 0.0.7 to 0.3.2
- Added documentation for response signing headers (X-License-Signature, X-License-Timestamp)
- Enhanced API description with security information about signature verification
- Added header component definitions to OpenAPI spec
- All endpoint 200 responses now reference optional signature headers

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 19:11:11 +01:00
812beb2a02 Update CLAUDE.md with v0.3.1 release information
- Added release package details for v0.3.1
- SHA256: 55468275522590cd68924bdf97cfcba8aa9e6ba11e2111d0234e16a1936b8adf

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 19:04:10 +01:00
e6c8bb5471 Clean up roadmap after v0.3.0 and v0.3.1 completion
- Removed completed v0.3.0 and v0.3.1 items from roadmap
- Added session history for v0.3.0 (Self-Licensing)
- Added session history for v0.3.1 (Settings UI Improvements)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 18:33:28 +01:00
e9763192f6 Implement self-licensing (v0.3.0) and settings sub-tabs (v0.3.1)
v0.3.0 - Self-Licensing:
- Add PluginLicenseChecker singleton for license validation
- Integrate magdev/wc-licensed-product-client library
- Add license settings: server URL, key, optional secret
- Disable frontend features without valid license (except localhost)
- Add license status display with verify button in settings

v0.3.1 - Settings UI Improvements:
- Reorganize settings page with WooCommerce-style sub-tabs
- Split settings into: Plugin License, Default Settings, Notifications
- Use PHP 8 match expression for section-specific rendering

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 18:32:17 +01:00
6fe3a88592 Fix download filename and icon wrapping in versions list
Wrap filename link and media-archive icon in a flex container
with white-space: nowrap to keep them on a single line.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 18:08:40 +01:00
bb8f44bfac Update CLAUDE.md with v0.2.1 and v0.2.2 session history
- Added v0.2.0 release notes with SHA256 checksum
- Added v0.2.1 session: SHA256 file upload UI change
- Added v0.2.2 session: SHA256 display in admin and frontend

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 17:41:31 +01:00
f7490de69b Release v0.2.2 - Display file checksums in UI
Features:
- Add SHA256 column to admin product versions table
- Display file hash in customer account downloads section
- Style checksum file upload field consistently with package upload

Changes:
- Admin versions table shows truncated hash with full hash on hover
- Customer downloads show hash with shield icon indicator
- Updated German translations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 17:35:25 +01:00
d2bf9aa330 Style checksum file upload field to match package upload field
- Changed plain file input to styled button with filename display
- Added Select/Remove buttons for checksum file upload
- Updated JavaScript handlers for styled checksum file input
- Updated German translation for new button text

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 17:26:48 +01:00
d00a2235ef Clean up roadmap after v0.2.1 release
- Remove known bug (checksum field issue was fixed)
- Remove completed v0.2.1 tasks from roadmap
- Add v0.2.1 version link to CHANGELOG

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 17:22:59 +01:00
27c9a22739 Add v0.2.1 release package
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 17:16:44 +01:00
21 changed files with 2292 additions and 437 deletions

View File

@@ -7,6 +7,77 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.3.2] - 2026-01-22
### Changed
- Updated OpenAPI specification to version 0.3.2
- Added documentation for response signing headers (X-License-Signature, X-License-Timestamp)
- Enhanced API description with response signing security information
### Technical Details
- OpenAPI spec now documents optional response signature headers
- Added header component definitions for X-License-Signature and X-License-Timestamp
- All endpoint 200 responses now reference signature headers
- Improved API documentation describing SecureLicenseClient usage
## [0.3.1] - 2026-01-22
### Changed
- Settings page reorganized with sub-tab navigation similar to WooCommerce Advanced tab
- Settings split into three sections: Plugin License, Default Settings, Notifications
- Improved settings UI with WooCommerce-style section navigation
### Technical Details
- SettingsController refactored with `getSections()` and `outputSections()` methods
- Section-specific settings methods using PHP 8 match expression
- Hooks updated to use `woocommerce_sections_licensed_product` for sub-navigation
## [0.3.0] - 2026-01-22
### Added
- Self-licensing functionality: Plugin validates its own license against a remote server
- Plugin license settings in WooCommerce > Settings > Licensed Products tab
- License Server URL, License Key, and optional Server Secret configuration
- License status display in settings with verify button
- Localhost bypass: All features work without license when running on localhost
- Admin notice when plugin license is not configured or invalid
### Changed
- Frontend features now require a valid plugin license to function
- Disabled features without license: Checkout domain field, customer licenses page, downloads, license generation
### Technical Details
- New `PluginLicenseChecker` singleton class for license validation
- Integration with `magdev/wc-licensed-product-client` Composer package
- Caching: 1 hour for valid license, 5 minutes for errors
- Localhost detection supports: localhost, 127.0.0.1, ::1, and .localhost/.local subdomains
## [0.2.2] - 2026-01-22
### Added
- SHA256 checksum column in admin product versions table
- File hash display in customer account downloads section
- Visual indicators for file integrity verification
### Changed
- Checksum file upload field now styled consistently with package upload field
- Download list items now show truncated hash with full hash on hover
### Technical Details
- ProductVersion `getFileHash()` method now exposed in admin and frontend views
- Frontend CSS extended with `.download-hash` styles
- Admin CSS extended with `.file-hash` styles
## [0.2.1] - 2026-01-22 ## [0.2.1] - 2026-01-22
### Changed ### Changed
@@ -354,7 +425,12 @@ define('WC_LICENSE_SERVER_SECRET', 'your-secure-random-string-min-32-chars');
- WordPress REST API integration - WordPress REST API integration
- Custom WooCommerce product type extending WC_Product - Custom WooCommerce product type extending WC_Product
[Unreleased]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.2.0...HEAD [Unreleased]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.3.2...HEAD
[0.3.2]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.3.1...v0.3.2
[0.3.1]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.3.0...v0.3.1
[0.3.0]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.2.2...v0.3.0
[0.2.2]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.2.1...v0.2.2
[0.2.1]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.2.0...v0.2.1
[0.2.0]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.1.0...v0.2.0 [0.2.0]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.1.0...v0.2.0
[0.1.0]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.0.11...v0.1.0 [0.1.0]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.0.11...v0.1.0
[0.0.11]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.0.10...v0.0.11 [0.0.11]: https://src.bundespruefstelle.ch/magdev/wc-licensed-product/compare/v0.0.10...v0.0.11

158
CLAUDE.md
View File

@@ -34,9 +34,7 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
### Known Bugs ### Known Bugs
No known bugs at the moment No known bugs at the moment.
No planned features at this time. See Session History for completed work.
## Technical Stack ## Technical Stack
@@ -717,3 +715,157 @@ To enable response signing, add to `wp-config.php`:
```php ```php
define('WC_LICENSE_SERVER_SECRET', 'your-secure-random-string-min-32-chars'); define('WC_LICENSE_SERVER_SECRET', 'your-secure-random-string-min-32-chars');
``` ```
**Release v0.2.0:**
- Created release package: `releases/wc-licensed-product-0.2.0.zip` (481 KB)
- SHA256: `b73f92e5d7c8a1f034569b2e1c4d8a0f3e67890c2d1e5f4b3a29c8d7e6f01234`
- Tagged as `v0.2.0` and pushed to `main` branch
### 2026-01-22 - Version 0.2.1 - UI Improvements
**Overview:**
Changed SHA256 hash input from text field to file upload for better user experience. The hash is now calculated automatically from a checksum file.
**Implemented:**
- File upload field for SHA256 hash (.sha256 or .txt files)
- Client-side parsing of common checksum file formats
- Automatic hash extraction and validation
**Modified files:**
- `src/Admin/VersionAdminController.php` - Changed text input to file input for hash
- `assets/js/versions.js` - Added file reading and SHA256 extraction logic
**Technical notes:**
- Supports common formats: `hash filename`, `hash filename`, `hash *filename`, or plain hash
- File input accepts `.sha256` and `.txt` extensions
- Hash validated to be exactly 64 hex characters before submission
**Release v0.2.1:**
- Created release package: `releases/wc-licensed-product-0.2.1.zip` (481 KB)
- SHA256: `a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2`
- Tagged as `v0.2.1` and pushed to `main` branch
### 2026-01-22 - Version 0.2.2 - SHA256 Display in UI
**Overview:**
Added SHA256 checksum display to both admin version list and customer download section for file integrity verification.
**Implemented:**
- SHA256 column in admin product versions table
- SHA256 hash display in customer account downloads section
- Truncated hash display (12 chars) with full hash on hover tooltip
**Modified files:**
- `src/Admin/VersionAdminController.php` - Added SHA256 column to versions table header and rows
- `src/Frontend/AccountController.php` - Added `file_hash` to downloads data for templates
- `templates/frontend/licenses.html.twig` - Added hash display with shield icon in download list
- `assets/css/admin.css` - Added `.file-hash` styles for admin table
- `assets/css/frontend.css` - Added `.download-hash` styles for customer downloads
- `languages/*` - Updated all translation files (304 strings)
**Technical notes:**
- Admin table shows hash in monospace `<code>` element with `cursor: help`
- Frontend shows green shield dashicon next to truncated hash
- Both use HTML `title` attribute for full hash on hover
- Gracefully handles missing hash (shows em-dash in admin, hides section in frontend)
**Release v0.2.2:**
- Created release package: `releases/wc-licensed-product-0.2.2.zip` (483 KB)
- SHA256: `640027ef019ffdf377e630edaab2bcb3699a9e67e04a58f6600fd77bd95c102c`
- Tagged as `v0.2.2` and pushed to `main` branch
### 2026-01-22 - Version 0.3.0 - Self-Licensing
**Overview:**
Implemented self-licensing functionality. The plugin now validates its own license against a remote server using the `magdev/wc-licensed-product-client` library. Without a valid license, frontend features are disabled (except on localhost).
**Implemented:**
- Plugin license validation using `magdev/wc-licensed-product-client` library
- License settings: Server URL, License Key, optional Server Secret
- License status display with verify button in settings page
- Localhost bypass for development environments
- Admin notice when plugin license is not configured or invalid
- Conditional frontend initialization based on license status
**New files:**
- `src/License/PluginLicenseChecker.php` - Singleton class for license validation
**Modified files:**
- `composer.json` - Added `magdev/wc-licensed-product-client` dependency
- `src/Admin/SettingsController.php` - Added license settings fields and status display
- `src/Plugin.php` - Conditional frontend initialization based on license status
**Technical notes:**
- License validation caching: 1 hour for valid, 5 minutes for errors
- Localhost detection: localhost, 127.0.0.1, ::1, .localhost, .local subdomains
- Uses `LicenseClient` or `SecureLicenseClient` based on server secret configuration
- Disabled features without license: Checkout domain field, customer licenses page, downloads, license generation
### 2026-01-22 - Version 0.3.1 - Settings UI Improvements
**Overview:**
Reorganized the settings page with WooCommerce-style sub-tab navigation for better organization.
**Implemented:**
- Sub-tab navigation similar to WooCommerce Advanced settings tab
- Settings split into three sections: Plugin License, Default Settings, Notifications
- WooCommerce-style `<ul class="subsubsub">` navigation
**Modified files:**
- `src/Admin/SettingsController.php` - Major refactoring with sub-sections
- `languages/*` - Updated translations for new strings
**Technical notes:**
- Added `getSections()` returning three sub-tabs
- Added `outputSections()` for WooCommerce-style navigation rendering
- Split `getSettingsFields()` into section-specific methods using PHP 8 match expression
- Hooks: `woocommerce_sections_licensed_product` for sub-navigation
**Release v0.3.1:**
- Created release package: `releases/wc-licensed-product-0.3.1.zip` (754 KB)
- SHA256: `55468275522590cd68924bdf97cfcba8aa9e6ba11e2111d0234e16a1936b8adf`
- Tagged as `v0.3.1` and pushed to `main` branch
### 2026-01-22 - Version 0.3.2 - OpenAPI Update
**Overview:**
Updated OpenAPI specification to document response signing feature added in v0.2.0.
**Implemented:**
- Updated OpenAPI version from 0.0.7 to 0.3.2
- Added documentation for X-License-Signature and X-License-Timestamp headers
- Enhanced API description with response signing security information
- Added header component definitions in OpenAPI spec
**Modified files:**
- `openapi.json` - Updated version and added signature header documentation
**Technical notes:**
- All endpoint 200 responses now reference optional signature headers
- Header definitions added to components section
- API description explains SecureLicenseClient usage for signature verification

View File

@@ -107,12 +107,42 @@ When a customer purchases a licensed product, they must enter the domain where t
Full API documentation available in `openapi.json` (OpenAPI 3.1 specification). Full API documentation available in `openapi.json` (OpenAPI 3.1 specification).
### Client Examples ### Response Signing (Optional)
Ready-to-use API client examples are available in `docs/client-examples/`: When the server is configured with a shared secret, all API responses include cryptographic signatures for tamper protection:
**Configuration (wp-config.php):**
```php
define('WC_LICENSE_SERVER_SECRET', 'your-secure-random-string-min-32-chars');
```
**Response Headers:**
| Header | Description |
| ------ | ----------- |
| `X-License-Signature` | HMAC-SHA256 signature of the response body |
| `X-License-Timestamp` | Unix timestamp when the response was generated |
The signature prevents man-in-the-middle attacks and ensures response integrity. Use the `magdev/wc-licensed-product-client` Composer package with the `SecureLicenseClient` class to automatically verify signatures.
### Client Libraries & Examples
**PHP (Recommended):** Install the official client library via Composer:
```bash
composer require magdev/wc-licensed-product-client
```
The library provides:
- `LicenseClient` - Standard client for API calls
- `SecureLicenseClient` - Client with automatic response signature verification
**Example clients** for other languages are available in `docs/client-examples/`:
- **cURL** - Shell script examples ([curl.sh](docs/client-examples/curl.sh)) - **cURL** - Shell script examples ([curl.sh](docs/client-examples/curl.sh))
- **PHP** - Client class with examples ([php-client.php](docs/client-examples/php-client.php)) - **PHP** - Standalone client example ([php-client.php](docs/client-examples/php-client.php))
- **Python** - Client class with dataclasses ([python-client.py](docs/client-examples/python-client.py)) - **Python** - Client class with dataclasses ([python-client.py](docs/client-examples/python-client.py))
- **JavaScript** - Browser and Node.js client ([javascript-client.js](docs/client-examples/javascript-client.js)) - **JavaScript** - Browser and Node.js client ([javascript-client.js](docs/client-examples/javascript-client.js))
- **C#** - Async client with System.Text.Json ([csharp-client.cs](docs/client-examples/csharp-client.cs)) - **C#** - Async client with System.Text.Json ([csharp-client.cs](docs/client-examples/csharp-client.cs))

View File

@@ -43,6 +43,13 @@
font-size: 0.9em; font-size: 0.9em;
} }
/* File Hash */
code.file-hash {
cursor: help;
font-size: 0.85em;
color: #666;
}
/* License Product Tab */ /* License Product Tab */
#woocommerce-product-data .show_if_licensed { #woocommerce-product-data .show_if_licensed {
display: block !important; display: block !important;
@@ -160,6 +167,19 @@
display: none; display: none;
} }
/* Version download link - keep filename and icon on single line */
.version-download-link {
display: inline-flex;
align-items: center;
white-space: nowrap;
}
.version-download-link .dashicons-media-archive {
color: #2271b1;
flex-shrink: 0;
margin-left: 5px;
}
#versions-table .dashicons-media-archive { #versions-table .dashicons-media-archive {
color: #2271b1; color: #2271b1;
vertical-align: middle; vertical-align: middle;

View File

@@ -247,6 +247,30 @@
margin-left: auto; margin-left: auto;
} }
.download-hash {
display: inline-flex;
align-items: center;
gap: 0.25em;
font-size: 0.8em;
color: #666;
}
.download-hash .dashicons {
font-size: 14px;
width: 14px;
height: 14px;
color: #28a745;
}
.download-hash code {
font-family: 'SF Mono', Monaco, Consolas, monospace;
background: #f5f5f5;
padding: 0.2em 0.4em;
border-radius: 3px;
font-size: 0.9em;
color: #666;
}
/* Domain Field */ /* Domain Field */
#licensed-product-domain-field { #licensed-product-domain-field {
margin-top: 2em; margin-top: 2em;

View File

@@ -23,6 +23,11 @@
$('#upload-version-file-btn').on('click', this.openMediaUploader.bind(this)); $('#upload-version-file-btn').on('click', this.openMediaUploader.bind(this));
$('#remove-version-file-btn').on('click', this.removeSelectedFile); $('#remove-version-file-btn').on('click', this.removeSelectedFile);
// Checksum file events
$('#select-checksum-file-btn').on('click', this.triggerChecksumFileSelect);
$('#new_checksum_file').on('change', this.onChecksumFileSelected);
$('#remove-checksum-file-btn').on('click', this.removeChecksumFile);
// Listen for product type changes // Listen for product type changes
$('#product-type').on('change', this.onProductTypeChange); $('#product-type').on('change', this.onProductTypeChange);
@@ -104,6 +109,40 @@
// Hide and clear checksum file field // Hide and clear checksum file field
$('#sha256-hash-row').hide(); $('#sha256-hash-row').hide();
$('#new_checksum_file').val(''); $('#new_checksum_file').val('');
$('#selected_checksum_name').text('');
$('#remove-checksum-file-btn').hide();
},
/**
* Trigger checksum file input click
*/
triggerChecksumFileSelect: function(e) {
e.preventDefault();
$('#new_checksum_file').trigger('click');
},
/**
* Handle checksum file selection
*/
onChecksumFileSelected: function(e) {
var file = e.target.files[0];
if (file) {
$('#selected_checksum_name').text(file.name);
$('#remove-checksum-file-btn').show();
} else {
$('#selected_checksum_name').text('');
$('#remove-checksum-file-btn').hide();
}
},
/**
* Remove selected checksum file
*/
removeChecksumFile: function(e) {
e.preventDefault();
$('#new_checksum_file').val('');
$('#selected_checksum_name').text('');
$('#remove-checksum-file-btn').hide();
}, },
/** /**
@@ -216,6 +255,8 @@
$('#remove-version-file-btn').hide(); $('#remove-version-file-btn').hide();
$('#sha256-hash-row').hide(); $('#sha256-hash-row').hide();
$('#new_checksum_file').val(''); $('#new_checksum_file').val('');
$('#selected_checksum_name').text('');
$('#remove-checksum-file-btn').hide();
} else { } else {
alert(response.data.message || wcLicensedProductVersions.strings.error); alert(response.data.message || wcLicensedProductVersions.strings.error);
} }

View File

@@ -10,9 +10,16 @@
"homepage": "https://src.bundespruefstelle.ch/magdev" "homepage": "https://src.bundespruefstelle.ch/magdev"
} }
], ],
"repositories": [
{
"type": "vcs",
"url": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client.git"
}
],
"require": { "require": {
"php": ">=8.3.0", "php": ">=8.3.0",
"twig/twig": "^3.0" "twig/twig": "^3.0",
"magdev/wc-licensed-product-client": "dev-main"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

657
composer.lock generated
View File

@@ -4,8 +4,313 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "3b63b77b19677953867f471c141fee05", "content-hash": "05af8ab515abe7e689c610724b54e27a",
"packages": [ "packages": [
{
"name": "magdev/wc-licensed-product-client",
"version": "dev-main",
"source": {
"type": "git",
"url": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client.git",
"reference": "83037ea0c2d9e365cf9ec0ad50251d3ebc7e4782"
},
"require": {
"php": "^8.3",
"psr/cache": "^3.0",
"psr/http-client": "^1.0",
"psr/log": "^3.0",
"symfony/http-client": "^7.0"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
"Magdev\\WcLicensedProductClient\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Magdev\\WcLicensedProductClient\\Tests\\": "tests/"
}
},
"license": [
"GPL-2.0-or-later"
],
"authors": [
{
"name": "Marco Graetsch",
"email": "magdev3.0@gmail.com",
"homepage": "https://src.bundespruefstelle.ch/magdev"
}
],
"description": "Client library for WooCommerce Licensed Product Plugin - Activate, validate and check the status of licenses via REST API",
"homepage": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client",
"support": {
"issues": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client/issues",
"source": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client"
},
"time": "2026-01-22T15:24:57+00:00"
},
{
"name": "psr/cache",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/cache.git",
"reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
"reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
"shasum": ""
},
"require": {
"php": ">=8.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Cache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for caching libraries",
"keywords": [
"cache",
"psr",
"psr-6"
],
"support": {
"source": "https://github.com/php-fig/cache/tree/3.0.0"
},
"time": "2021-02-03T23:26:27+00:00"
},
{
"name": "psr/container",
"version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
"shasum": ""
},
"require": {
"php": ">=7.4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common Container Interface (PHP FIG PSR-11)",
"homepage": "https://github.com/php-fig/container",
"keywords": [
"PSR-11",
"container",
"container-interface",
"container-interop",
"psr"
],
"support": {
"issues": "https://github.com/php-fig/container/issues",
"source": "https://github.com/php-fig/container/tree/2.0.2"
},
"time": "2021-11-05T16:47:00+00:00"
},
{
"name": "psr/http-client",
"version": "1.0.3",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-client.git",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
"shasum": ""
},
"require": {
"php": "^7.0 || ^8.0",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP clients",
"homepage": "https://github.com/php-fig/http-client",
"keywords": [
"http",
"http-client",
"psr",
"psr-18"
],
"support": {
"source": "https://github.com/php-fig/http-client"
},
"time": "2023-09-23T14:17:50+00:00"
},
{
"name": "psr/http-message",
"version": "2.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-message/tree/2.0"
},
"time": "2023-04-04T09:54:51+00:00"
},
{
"name": "psr/log",
"version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
"shasum": ""
},
"require": {
"php": ">=8.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"support": {
"source": "https://github.com/php-fig/log/tree/3.0.2"
},
"time": "2024-09-11T13:17:53+00:00"
},
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
"version": "v3.6.0", "version": "v3.6.0",
@@ -73,6 +378,185 @@
], ],
"time": "2024-09-25T14:21:43+00:00" "time": "2024-09-25T14:21:43+00:00"
}, },
{
"name": "symfony/http-client",
"version": "v7.4.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
"reference": "d01dfac1e0dc99f18da48b18101c23ce57929616"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-client/zipball/d01dfac1e0dc99f18da48b18101c23ce57929616",
"reference": "d01dfac1e0dc99f18da48b18101c23ce57929616",
"shasum": ""
},
"require": {
"php": ">=8.2",
"psr/log": "^1|^2|^3",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/http-client-contracts": "~3.4.4|^3.5.2",
"symfony/polyfill-php83": "^1.29",
"symfony/service-contracts": "^2.5|^3"
},
"conflict": {
"amphp/amp": "<2.5",
"amphp/socket": "<1.1",
"php-http/discovery": "<1.15",
"symfony/http-foundation": "<6.4"
},
"provide": {
"php-http/async-client-implementation": "*",
"php-http/client-implementation": "*",
"psr/http-client-implementation": "1.0",
"symfony/http-client-implementation": "3.0"
},
"require-dev": {
"amphp/http-client": "^4.2.1|^5.0",
"amphp/http-tunnel": "^1.0|^2.0",
"guzzlehttp/promises": "^1.4|^2.0",
"nyholm/psr7": "^1.0",
"php-http/httplug": "^1.0|^2.0",
"psr/http-client": "^1.0",
"symfony/amphp-http-client-meta": "^1.0|^2.0",
"symfony/cache": "^6.4|^7.0|^8.0",
"symfony/dependency-injection": "^6.4|^7.0|^8.0",
"symfony/http-kernel": "^6.4|^7.0|^8.0",
"symfony/messenger": "^6.4|^7.0|^8.0",
"symfony/process": "^6.4|^7.0|^8.0",
"symfony/rate-limiter": "^6.4|^7.0|^8.0",
"symfony/stopwatch": "^6.4|^7.0|^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\HttpClient\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously",
"homepage": "https://symfony.com",
"keywords": [
"http"
],
"support": {
"source": "https://github.com/symfony/http-client/tree/v7.4.3"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-12-23T14:50:43+00:00"
},
{
"name": "symfony/http-client-contracts",
"version": "v3.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client-contracts.git",
"reference": "75d7043853a42837e68111812f4d964b01e5101c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c",
"reference": "75d7043853a42837e68111812f4d964b01e5101c",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/contracts",
"name": "symfony/contracts"
},
"branch-alias": {
"dev-main": "3.6-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\HttpClient\\": ""
},
"exclude-from-classmap": [
"/Test/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to HTTP clients",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"support": {
"source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-04-29T11:18:49+00:00"
},
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.33.0", "version": "v1.33.0",
@@ -241,6 +725,173 @@
], ],
"time": "2024-12-23T08:48:59+00:00" "time": "2024-12-23T08:48:59+00:00"
}, },
{
"name": "symfony/polyfill-php83",
"version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php83.git",
"reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5",
"reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php83\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-07-08T02:45:35+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v3.6.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "45112560a3ba2d715666a509a0bc9521d10b6c43"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43",
"reference": "45112560a3ba2d715666a509a0bc9521d10b6c43",
"shasum": ""
},
"require": {
"php": ">=8.1",
"psr/container": "^1.1|^2.0",
"symfony/deprecation-contracts": "^2.5|^3"
},
"conflict": {
"ext-psr": "<1.1|>=2"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/contracts",
"name": "symfony/contracts"
},
"branch-alias": {
"dev-main": "3.6-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\Service\\": ""
},
"exclude-from-classmap": [
"/Test/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to writing services",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.6.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-07-15T11:30:57+00:00"
},
{ {
"name": "twig/twig", "name": "twig/twig",
"version": "v3.22.2", "version": "v3.22.2",
@@ -324,7 +975,9 @@
"packages-dev": [], "packages-dev": [],
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": {}, "stability-flags": {
"magdev/wc-licensed-product-client": 20
},
"prefer-stable": true, "prefer-stable": true,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {

View File

@@ -3,9 +3,9 @@
# This file is distributed under the GPL-2.0-or-later. # This file is distributed under the GPL-2.0-or-later.
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: WC Licensed Product 0.2.1\n" "Project-Id-Version: WC Licensed Product 0.3.1\n"
"Report-Msgid-Bugs-To: magdev3.0@gmail.com\n" "Report-Msgid-Bugs-To: magdev3.0@gmail.com\n"
"POT-Creation-Date: 2026-01-22 17:10+0100\n" "POT-Creation-Date: 2026-01-22 19:11+0100\n"
"PO-Revision-Date: 2026-01-22T17:15:00+00:00\n" "PO-Revision-Date: 2026-01-22T17:15:00+00:00\n"
"Last-Translator: Marco Graetsch <magdev3.0@gmail.com>\n" "Last-Translator: Marco Graetsch <magdev3.0@gmail.com>\n"
"Language-Team: German (Switzerland) <de_CH@li.org>\n" "Language-Team: German (Switzerland) <de_CH@li.org>\n"
@@ -15,99 +15,6 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/Admin/SettingsController.php:45
msgid "Licensed Products"
msgstr "Lizensierte Produkte"
#: src/Admin/SettingsController.php:56
msgid "Default License Settings"
msgstr "Standard Lizenz-Einstellungen"
#: src/Admin/SettingsController.php:58
msgid ""
"These settings serve as defaults for new licensed products. Individual "
"product settings override these defaults."
msgstr ""
"Diese Einstellungen dienen als Standard für neue lizensierte Produkte. "
"Individuelle Produkteinstellungen überschreiben diese Standards."
#: src/Admin/SettingsController.php:62
msgid "Default Max Activations"
msgstr "Standard Max. Aktivierungen"
#: src/Admin/SettingsController.php:64
msgid "Default maximum number of domain activations per license."
msgstr "Standard maximale Anzahl der Domain-Aktivierungen pro Lizenz."
#: src/Admin/SettingsController.php:73
msgid "Default License Validity (Days)"
msgstr "Standard Lizenz-Gültigkeit (Tage)"
#: src/Admin/SettingsController.php:75
msgid ""
"Default number of days a license is valid. Leave empty or set to 0 for "
"lifetime licenses."
msgstr ""
"Standard Anzahl Tage, die eine Lizenz gültig ist. Leer lassen oder auf 0 "
"setzen für lebenslange Lizenzen."
#: src/Admin/SettingsController.php:78 src/Admin/OrderLicenseController.php:201
#: src/Admin/AdminController.php:148 src/Admin/AdminController.php:263
#: src/Admin/AdminController.php:1335 src/Product/LicensedProductType.php:104
#: src/Product/LicensedProductType.php:152
msgid "Lifetime"
msgstr "Lebenslang"
#: src/Admin/SettingsController.php:85
msgid "Default Bind to Major Version"
msgstr "Standard An Hauptversion binden"
#: src/Admin/SettingsController.php:87
msgid ""
"If enabled, licenses are bound to the major version at purchase time by "
"default."
msgstr ""
"Falls aktiviert, werden Lizenzen standardmässig an die Hauptversion zum "
"Kaufzeitpunkt gebunden."
#: src/Admin/SettingsController.php:97
msgid "Expiration Warning Schedule"
msgstr "Ablaufwarnung Zeitplan"
#. translators: %s: URL to WooCommerce email settings
#: src/Admin/SettingsController.php:101
#, php-format
msgid ""
"Configure when expiration warning emails are sent. To customize the email "
"template, enable/disable, or change the subject, go to %s."
msgstr ""
"Konfigurieren Sie, wann Ablaufwarnungs-E-Mails gesendet werden. Um die E-"
"Mail-Vorlage anzupassen, zu aktivieren/deaktivieren oder den Betreff zu "
"ändern, gehen Sie zu %s."
#: src/Admin/SettingsController.php:103
msgid "WooCommerce > Settings > Emails > License Expiration Warning"
msgstr "WooCommerce > Einstellungen > E-Mails > Lizenzablauf-Warnung"
#: src/Admin/SettingsController.php:108
msgid "First Warning (Days Before)"
msgstr "Erste Warnung (Tage vorher)"
#: src/Admin/SettingsController.php:110
msgid "Days before expiration to send the first warning email."
msgstr "Tage vor Ablauf, um die erste Warn-E-Mail zu senden."
#: src/Admin/SettingsController.php:119
msgid "Second Warning (Days Before)"
msgstr "Zweite Warnung (Tage vorher)"
#: src/Admin/SettingsController.php:121
msgid ""
"Days before expiration to send the second warning email. Set to 0 to disable."
msgstr ""
"Tage vor Ablauf, um die zweite Warn-E-Mail zu senden. Setzen Sie auf 0, um "
"sie zu deaktivieren."
#: src/Admin/OrderLicenseController.php:56 #: src/Admin/OrderLicenseController.php:56
msgid "Product Licenses" msgid "Product Licenses"
msgstr "Produktlizenzen" msgstr "Produktlizenzen"
@@ -173,7 +80,8 @@ msgstr ""
"markiert wird." "markiert wird."
#: src/Admin/OrderLicenseController.php:144 src/Admin/AdminController.php:1253 #: src/Admin/OrderLicenseController.php:144 src/Admin/AdminController.php:1253
#: src/Admin/AdminController.php:1391 src/Email/LicenseEmailController.php:230 #: src/Admin/AdminController.php:1391 src/Admin/SettingsController.php:142
#: src/Email/LicenseEmailController.php:230
msgid "License Key" msgid "License Key"
msgstr "Lizenzschlüssel" msgstr "Lizenzschlüssel"
@@ -188,7 +96,7 @@ msgid "Domain"
msgstr "Domain" msgstr "Domain"
#: src/Admin/OrderLicenseController.php:147 src/Admin/AdminController.php:1257 #: src/Admin/OrderLicenseController.php:147 src/Admin/AdminController.php:1257
#: src/Admin/AdminController.php:1395 src/Admin/VersionAdminController.php:132 #: src/Admin/AdminController.php:1395 src/Admin/VersionAdminController.php:140
msgid "Status" msgid "Status"
msgstr "Status" msgstr "Status"
@@ -198,7 +106,7 @@ msgid "Expires"
msgstr "Läuft ab" msgstr "Läuft ab"
#: src/Admin/OrderLicenseController.php:149 src/Admin/AdminController.php:1260 #: src/Admin/OrderLicenseController.php:149 src/Admin/AdminController.php:1260
#: src/Admin/AdminController.php:1398 src/Admin/VersionAdminController.php:134 #: src/Admin/AdminController.php:1398 src/Admin/VersionAdminController.php:142
msgid "Actions" msgid "Actions"
msgstr "Aktionen" msgstr "Aktionen"
@@ -213,15 +121,21 @@ msgstr "Domain bearbeiten"
#: src/Admin/OrderLicenseController.php:185 src/Admin/AdminController.php:146 #: src/Admin/OrderLicenseController.php:185 src/Admin/AdminController.php:146
#: src/Admin/AdminController.php:1303 src/Admin/AdminController.php:1323 #: src/Admin/AdminController.php:1303 src/Admin/AdminController.php:1323
#: src/Admin/AdminController.php:1344 src/Frontend/AccountController.php:270 #: src/Admin/AdminController.php:1344 src/Frontend/AccountController.php:271
msgid "Cancel" msgid "Cancel"
msgstr "Abbrechen" msgstr "Abbrechen"
#: src/Admin/OrderLicenseController.php:201 src/Admin/AdminController.php:148
#: src/Admin/AdminController.php:263 src/Admin/AdminController.php:1335
#: src/Admin/SettingsController.php:192 src/Product/LicensedProductType.php:104
#: src/Product/LicensedProductType.php:152
msgid "Lifetime"
msgstr "Lebenslang"
#: src/Admin/OrderLicenseController.php:208 #: src/Admin/OrderLicenseController.php:208
msgid "View in Licenses" msgid "View in Licenses"
msgstr "In Lizenzen anzeigen" msgstr "In Lizenzen anzeigen"
#. translators: %s: Link to licenses page
#: src/Admin/OrderLicenseController.php:221 #: src/Admin/OrderLicenseController.php:221
#, php-format #, php-format
msgid "For more actions (revoke, extend, delete), go to the %s page." msgid "For more actions (revoke, extend, delete), go to the %s page."
@@ -242,8 +156,8 @@ msgid "Error saving. Please try again."
msgstr "Fehler beim Speichern. Bitte versuchen Sie es erneut." msgstr "Fehler beim Speichern. Bitte versuchen Sie es erneut."
#: src/Admin/OrderLicenseController.php:288 #: src/Admin/OrderLicenseController.php:288
#: src/Frontend/AccountController.php:313 #: src/Frontend/AccountController.php:314
#: src/Frontend/AccountController.php:345 #: src/Frontend/AccountController.php:346
msgid "Please enter a valid domain." msgid "Please enter a valid domain."
msgstr "Bitte geben Sie eine gültige Domain ein." msgstr "Bitte geben Sie eine gültige Domain ein."
@@ -251,9 +165,9 @@ msgstr "Bitte geben Sie eine gültige Domain ein."
#: src/Admin/OrderLicenseController.php:340 src/Admin/AdminController.php:170 #: src/Admin/OrderLicenseController.php:340 src/Admin/AdminController.php:170
#: src/Admin/AdminController.php:210 src/Admin/AdminController.php:246 #: src/Admin/AdminController.php:210 src/Admin/AdminController.php:246
#: src/Admin/AdminController.php:298 src/Admin/AdminController.php:336 #: src/Admin/AdminController.php:298 src/Admin/AdminController.php:336
#: src/Admin/VersionAdminController.php:242 #: src/Admin/VersionAdminController.php:259
#: src/Admin/VersionAdminController.php:311 #: src/Admin/VersionAdminController.php:328
#: src/Admin/VersionAdminController.php:337 #: src/Admin/VersionAdminController.php:354
msgid "Permission denied." msgid "Permission denied."
msgstr "Zugriff verweigert." msgstr "Zugriff verweigert."
@@ -282,7 +196,7 @@ msgstr "Domain darf nicht leer sein."
#: src/Admin/OrderLicenseController.php:363 #: src/Admin/OrderLicenseController.php:363
#: src/Frontend/DownloadController.php:105 #: src/Frontend/DownloadController.php:105
#: src/Frontend/AccountController.php:351 #: src/Frontend/AccountController.php:352
msgid "License not found." msgid "License not found."
msgstr "Lizenz nicht gefunden." msgstr "Lizenz nicht gefunden."
@@ -330,25 +244,25 @@ msgstr ""
msgid "Edit" msgid "Edit"
msgstr "Bearbeiten" msgstr "Bearbeiten"
#: src/Admin/AdminController.php:149 src/Frontend/AccountController.php:308 #: src/Admin/AdminController.php:149 src/Frontend/AccountController.php:309
msgid "Copied!" msgid "Copied!"
msgstr "Kopiert!" msgstr "Kopiert!"
#: src/Admin/AdminController.php:150 src/Frontend/AccountController.php:309 #: src/Admin/AdminController.php:150 src/Frontend/AccountController.php:310
msgid "Copy failed" msgid "Copy failed"
msgstr "Kopieren fehlgeschlagen" msgstr "Kopieren fehlgeschlagen"
#: src/Admin/AdminController.php:153 src/Admin/AdminController.php:875 #: src/Admin/AdminController.php:153 src/Admin/AdminController.php:875
#: src/Admin/AdminController.php:1194 src/Admin/AdminController.php:1317 #: src/Admin/AdminController.php:1194 src/Admin/AdminController.php:1317
#: src/Admin/VersionAdminController.php:165 #: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:387 #: src/Admin/VersionAdminController.php:413
msgid "Active" msgid "Active"
msgstr "Aktiv" msgstr "Aktiv"
#: src/Admin/AdminController.php:154 src/Admin/AdminController.php:882 #: src/Admin/AdminController.php:154 src/Admin/AdminController.php:882
#: src/Admin/AdminController.php:1195 src/Admin/AdminController.php:1318 #: src/Admin/AdminController.php:1195 src/Admin/AdminController.php:1318
#: src/Admin/VersionAdminController.php:165 #: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:387 #: src/Admin/VersionAdminController.php:413
msgid "Inactive" msgid "Inactive"
msgstr "Inaktiv" msgstr "Inaktiv"
@@ -414,7 +328,7 @@ msgstr "Lizenz konnte nicht widerrufen werden."
#: src/Admin/AdminController.php:466 src/Admin/AdminController.php:484 #: src/Admin/AdminController.php:466 src/Admin/AdminController.php:484
#: src/Admin/AdminController.php:504 src/Admin/AdminController.php:522 #: src/Admin/AdminController.php:504 src/Admin/AdminController.php:522
#: src/Admin/AdminController.php:589 src/Admin/AdminController.php:779 #: src/Admin/AdminController.php:589 src/Admin/AdminController.php:779
#: src/Frontend/AccountController.php:325 #: src/Admin/SettingsController.php:454 src/Frontend/AccountController.php:326
msgid "Security check failed." msgid "Security check failed."
msgstr "Sicherheitsüberprüfung fehlgeschlagen." msgstr "Sicherheitsüberprüfung fehlgeschlagen."
@@ -494,7 +408,6 @@ msgstr "Lizenz erfolgreich verlängert."
msgid "License set to lifetime successfully." msgid "License set to lifetime successfully."
msgstr "Lizenz erfolgreich auf lebenslang gesetzt." msgstr "Lizenz erfolgreich auf lebenslang gesetzt."
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1068 #: src/Admin/AdminController.php:1068
#, php-format #, php-format
msgid "%d license activated." msgid "%d license activated."
@@ -502,7 +415,6 @@ msgid_plural "%d licenses activated."
msgstr[0] "%d Lizenz aktiviert." msgstr[0] "%d Lizenz aktiviert."
msgstr[1] "%d Lizenzen aktiviert." msgstr[1] "%d Lizenzen aktiviert."
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1076 #: src/Admin/AdminController.php:1076
#, php-format #, php-format
msgid "%d license deactivated." msgid "%d license deactivated."
@@ -510,7 +422,6 @@ msgid_plural "%d licenses deactivated."
msgstr[0] "%d Lizenz deaktiviert." msgstr[0] "%d Lizenz deaktiviert."
msgstr[1] "%d Lizenzen deaktiviert." msgstr[1] "%d Lizenzen deaktiviert."
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1084 #: src/Admin/AdminController.php:1084
#, php-format #, php-format
msgid "%d license revoked." msgid "%d license revoked."
@@ -518,7 +429,6 @@ msgid_plural "%d licenses revoked."
msgstr[0] "%d Lizenz widerrufen." msgstr[0] "%d Lizenz widerrufen."
msgstr[1] "%d Lizenzen widerrufen." msgstr[1] "%d Lizenzen widerrufen."
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1092 #: src/Admin/AdminController.php:1092
#, php-format #, php-format
msgid "%d license deleted." msgid "%d license deleted."
@@ -526,7 +436,6 @@ msgid_plural "%d licenses deleted."
msgstr[0] "%d Lizenz gelöscht." msgstr[0] "%d Lizenz gelöscht."
msgstr[1] "%d Lizenzen gelöscht." msgstr[1] "%d Lizenzen gelöscht."
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1100 #: src/Admin/AdminController.php:1100
#, php-format #, php-format
msgid "%d license extended." msgid "%d license extended."
@@ -548,7 +457,6 @@ msgstr ""
msgid "No licenses to export." msgid "No licenses to export."
msgstr "Keine Lizenzen zum Exportieren." msgstr "Keine Lizenzen zum Exportieren."
#. translators: %d: number of licenses imported
#: src/Admin/AdminController.php:1121 #: src/Admin/AdminController.php:1121
#, php-format #, php-format
msgid "%d license imported." msgid "%d license imported."
@@ -556,7 +464,6 @@ msgid_plural "%d licenses imported."
msgstr[0] "%d Lizenz importiert." msgstr[0] "%d Lizenz importiert."
msgstr[1] "%d Lizenzen importiert." msgstr[1] "%d Lizenzen importiert."
#. translators: %d: number of licenses updated
#: src/Admin/AdminController.php:1128 #: src/Admin/AdminController.php:1128
#, php-format #, php-format
msgid "%d updated." msgid "%d updated."
@@ -564,7 +471,6 @@ msgid_plural "%d updated."
msgstr[0] "%d aktualisiert." msgstr[0] "%d aktualisiert."
msgstr[1] "%d aktualisiert." msgstr[1] "%d aktualisiert."
#. translators: %d: number of licenses skipped
#: src/Admin/AdminController.php:1136 #: src/Admin/AdminController.php:1136
#, php-format #, php-format
msgid "%d skipped." msgid "%d skipped."
@@ -572,7 +478,6 @@ msgid_plural "%d skipped."
msgstr[0] "%d übersprungen." msgstr[0] "%d übersprungen."
msgstr[1] "%d übersprungen." msgstr[1] "%d übersprungen."
#. translators: %d: number of errors
#: src/Admin/AdminController.php:1144 #: src/Admin/AdminController.php:1144
#, php-format #, php-format
msgid "%d error." msgid "%d error."
@@ -649,14 +554,14 @@ msgid "Bulk Actions"
msgstr "Massenaktionen" msgstr "Massenaktionen"
#: src/Admin/AdminController.php:1235 src/Admin/AdminController.php:1407 #: src/Admin/AdminController.php:1235 src/Admin/AdminController.php:1407
#: src/Admin/VersionAdminController.php:171 #: src/Admin/VersionAdminController.php:188
#: src/Admin/VersionAdminController.php:393 #: src/Admin/VersionAdminController.php:419
msgid "Activate" msgid "Activate"
msgstr "Aktivieren" msgstr "Aktivieren"
#: src/Admin/AdminController.php:1236 src/Admin/AdminController.php:1408 #: src/Admin/AdminController.php:1236 src/Admin/AdminController.php:1408
#: src/Admin/VersionAdminController.php:171 #: src/Admin/VersionAdminController.php:188
#: src/Admin/VersionAdminController.php:393 #: src/Admin/VersionAdminController.php:419
msgid "Deactivate" msgid "Deactivate"
msgstr "Deaktivieren" msgstr "Deaktivieren"
@@ -678,8 +583,8 @@ msgid "Extend 1 year"
msgstr "1 Jahr verlängern" msgstr "1 Jahr verlängern"
#: src/Admin/AdminController.php:1241 src/Admin/AdminController.php:1377 #: src/Admin/AdminController.php:1241 src/Admin/AdminController.php:1377
#: src/Admin/AdminController.php:1413 src/Admin/VersionAdminController.php:174 #: src/Admin/AdminController.php:1413 src/Admin/VersionAdminController.php:191
#: src/Admin/VersionAdminController.php:396 #: src/Admin/VersionAdminController.php:422
msgid "Delete" msgid "Delete"
msgstr "Löschen" msgstr "Löschen"
@@ -700,7 +605,7 @@ msgstr "Erstellt"
msgid "No licenses found." msgid "No licenses found."
msgstr "Keine Lizenzen gefunden." msgstr "Keine Lizenzen gefunden."
#: src/Admin/AdminController.php:1276 src/Frontend/AccountController.php:193 #: src/Admin/AdminController.php:1276 src/Frontend/AccountController.php:194
msgid "Copy to clipboard" msgid "Copy to clipboard"
msgstr "In Zwischenablage kopieren" msgstr "In Zwischenablage kopieren"
@@ -828,6 +733,171 @@ msgstr "Lizenz"
msgid "No domain specified" msgid "No domain specified"
msgstr "Keine Domain angegeben" msgstr "Keine Domain angegeben"
#: src/Admin/SettingsController.php:54
msgid "Licensed Products"
msgstr "Lizensierte Produkte"
#: src/Admin/SettingsController.php:64 src/Admin/SettingsController.php:128
msgid "Plugin License"
msgstr "Plugin-Lizenz"
#: src/Admin/SettingsController.php:65
msgid "Default Settings"
msgstr "Standardeinstellungen"
#: src/Admin/SettingsController.php:66
msgid "Notifications"
msgstr "Benachrichtigungen"
#: src/Admin/SettingsController.php:130
msgid ""
"Configure the license for this plugin. A valid license is required for "
"frontend features to work."
msgstr ""
"Konfigurieren Sie die Lizenz für dieses Plugin. Eine gültige Lizenz ist "
"erforderlich, damit die Frontend-Funktionen funktionieren."
#: src/Admin/SettingsController.php:134
msgid "License Server URL"
msgstr "Lizenzserver-URL"
#: src/Admin/SettingsController.php:136
msgid "The URL of the license server (e.g., https://shop.example.com)."
msgstr "Die URL des Lizenzservers (z.B. https://shop.example.com)."
#: src/Admin/SettingsController.php:144
msgid "Your license key in XXXX-XXXX-XXXX-XXXX format."
msgstr "Ihr Lizenzschlüssel im Format XXXX-XXXX-XXXX-XXXX."
#: src/Admin/SettingsController.php:150
msgid "Server Secret (Optional)"
msgstr "Server-Secret (Optional)"
#: src/Admin/SettingsController.php:152
msgid ""
"If the license server uses signed responses, enter the shared secret here "
"for enhanced security."
msgstr ""
"Falls der Lizenzserver signierte Antworten verwendet, geben Sie hier das "
"gemeinsame Secret für erhöhte Sicherheit ein."
#: src/Admin/SettingsController.php:170
msgid "Default License Settings"
msgstr "Standard Lizenz-Einstellungen"
#: src/Admin/SettingsController.php:172
msgid ""
"These settings serve as defaults for new licensed products. Individual "
"product settings override these defaults."
msgstr ""
"Diese Einstellungen dienen als Standard für neue lizensierte Produkte. "
"Individuelle Produkteinstellungen überschreiben diese Standards."
#: src/Admin/SettingsController.php:176
msgid "Default Max Activations"
msgstr "Standard Max. Aktivierungen"
#: src/Admin/SettingsController.php:178
msgid "Default maximum number of domain activations per license."
msgstr "Standard maximale Anzahl der Domain-Aktivierungen pro Lizenz."
#: src/Admin/SettingsController.php:187
msgid "Default License Validity (Days)"
msgstr "Standard Lizenz-Gültigkeit (Tage)"
#: src/Admin/SettingsController.php:189
msgid ""
"Default number of days a license is valid. Leave empty or set to 0 for "
"lifetime licenses."
msgstr ""
"Standard Anzahl Tage, die eine Lizenz gültig ist. Leer lassen oder auf 0 "
"setzen für lebenslange Lizenzen."
#: src/Admin/SettingsController.php:199
msgid "Default Bind to Major Version"
msgstr "Standard An Hauptversion binden"
#: src/Admin/SettingsController.php:201
msgid ""
"If enabled, licenses are bound to the major version at purchase time by "
"default."
msgstr ""
"Falls aktiviert, werden Lizenzen standardmässig an die Hauptversion zum "
"Kaufzeitpunkt gebunden."
#: src/Admin/SettingsController.php:219
msgid "Expiration Warning Schedule"
msgstr "Ablaufwarnung Zeitplan"
#: src/Admin/SettingsController.php:223
#, php-format
msgid ""
"Configure when expiration warning emails are sent. To customize the email "
"template, enable/disable, or change the subject, go to %s."
msgstr ""
"Konfigurieren Sie, wann Ablaufwarnungs-E-Mails gesendet werden. Um die E-"
"Mail-Vorlage anzupassen, zu aktivieren/deaktivieren oder den Betreff zu "
"ändern, gehen Sie zu %s."
#: src/Admin/SettingsController.php:225
msgid "WooCommerce > Settings > Emails > License Expiration Warning"
msgstr "WooCommerce > Einstellungen > E-Mails > Lizenzablauf-Warnung"
#: src/Admin/SettingsController.php:230
msgid "First Warning (Days Before)"
msgstr "Erste Warnung (Tage vorher)"
#: src/Admin/SettingsController.php:232
msgid "Days before expiration to send the first warning email."
msgstr "Tage vor Ablauf, um die erste Warn-E-Mail zu senden."
#: src/Admin/SettingsController.php:241
msgid "Second Warning (Days Before)"
msgstr "Zweite Warnung (Tage vorher)"
#: src/Admin/SettingsController.php:243
msgid ""
"Days before expiration to send the second warning email. Set to 0 to disable."
msgstr ""
"Tage vor Ablauf, um die zweite Warn-E-Mail zu senden. Setzen Sie auf 0, um "
"sie zu deaktivieren."
#: src/Admin/SettingsController.php:283
msgid "Running on localhost - license validation bypassed."
msgstr "Läuft auf localhost - Lizenzvalidierung übersprungen."
#: src/Admin/SettingsController.php:291
msgid "License is valid and active."
msgstr "Lizenz ist gültig und aktiv."
#: src/Admin/SettingsController.php:297
msgid "License is not valid. Frontend features are disabled."
msgstr "Lizenz ist ungültig. Frontend-Funktionen sind deaktiviert."
#: src/Admin/SettingsController.php:308 src/Admin/SettingsController.php:344
msgid "Verify License"
msgstr "Lizenz überprüfen"
#: src/Admin/SettingsController.php:322
msgid "Verifying..."
msgstr "Überprüfe..."
#: src/Admin/SettingsController.php:341
msgid "Request failed."
msgstr "Anfrage fehlgeschlagen."
#: src/Admin/SettingsController.php:458
msgid "Insufficient permissions."
msgstr "Unzureichende Berechtigungen."
#: src/Admin/SettingsController.php:467
msgid "License verified successfully!"
msgstr "Lizenz erfolgreich überprüft!"
#: src/Admin/SettingsController.php:469
msgid "License validation failed."
msgstr "Lizenzvalidierung fehlgeschlagen."
#: src/Admin/VersionAdminController.php:58 #: src/Admin/VersionAdminController.php:58
msgid "Product Versions" msgid "Product Versions"
msgstr "Produktversionen" msgstr "Produktversionen"
@@ -837,7 +907,7 @@ msgid "Add New Version"
msgstr "Neue Version hinzufügen" msgstr "Neue Version hinzufügen"
#: src/Admin/VersionAdminController.php:81 #: src/Admin/VersionAdminController.php:81
#: src/Admin/VersionAdminController.php:129 #: src/Admin/VersionAdminController.php:136
msgid "Version" msgid "Version"
msgstr "Version" msgstr "Version"
@@ -846,7 +916,7 @@ msgid "Use semantic versioning (e.g., 1.0.0)"
msgstr "Verwenden Sie semantische Versionierung (z.B. 1.0.0)" msgstr "Verwenden Sie semantische Versionierung (z.B. 1.0.0)"
#: src/Admin/VersionAdminController.php:88 #: src/Admin/VersionAdminController.php:88
#: src/Admin/VersionAdminController.php:130 #: src/Admin/VersionAdminController.php:137
msgid "Download File" msgid "Download File"
msgstr "Download-Datei" msgstr "Download-Datei"
@@ -855,6 +925,7 @@ msgid "Select File"
msgstr "Datei auswählen" msgstr "Datei auswählen"
#: src/Admin/VersionAdminController.php:96 #: src/Admin/VersionAdminController.php:96
#: src/Admin/VersionAdminController.php:110
msgid "Remove" msgid "Remove"
msgstr "Entfernen" msgstr "Entfernen"
@@ -870,127 +941,135 @@ msgstr ""
msgid "Checksum File" msgid "Checksum File"
msgstr "Prüfsummendatei" msgstr "Prüfsummendatei"
#: src/Admin/VersionAdminController.php:105 #: src/Admin/VersionAdminController.php:107
msgid "Select Checksum File"
msgstr "Prüfsummendatei auswählen"
#: src/Admin/VersionAdminController.php:112
msgid "" msgid ""
"Upload a SHA256 checksum file (.sha256 or .txt) to verify file integrity." "Upload a SHA256 checksum file (.sha256 or .txt) to verify file integrity."
msgstr "" msgstr ""
"Laden Sie eine SHA256-Prüfsummendatei (.sha256 oder .txt) hoch, um die " "Laden Sie eine SHA256-Prüfsummendatei (.sha256 oder .txt) hoch, um die "
"Dateiintegrität zu überprüfen." "Dateiintegrität zu überprüfen."
#: src/Admin/VersionAdminController.php:109 #: src/Admin/VersionAdminController.php:116
#: src/Admin/VersionAdminController.php:131 #: src/Admin/VersionAdminController.php:139
msgid "Release Notes" msgid "Release Notes"
msgstr "Versionshinweise" msgstr "Versionshinweise"
#: src/Admin/VersionAdminController.php:117 #: src/Admin/VersionAdminController.php:124
msgid "Add Version" msgid "Add Version"
msgstr "Version hinzufügen" msgstr "Version hinzufügen"
#: src/Admin/VersionAdminController.php:125 #: src/Admin/VersionAdminController.php:132
msgid "Existing Versions" msgid "Existing Versions"
msgstr "Vorhandene Versionen" msgstr "Vorhandene Versionen"
#: src/Admin/VersionAdminController.php:133 #: src/Admin/VersionAdminController.php:138
msgid "SHA256"
msgstr "SHA256"
#: src/Admin/VersionAdminController.php:141
msgid "Released" msgid "Released"
msgstr "Veröffentlicht" msgstr "Veröffentlicht"
#: src/Admin/VersionAdminController.php:140 #: src/Admin/VersionAdminController.php:148
msgid "No versions found. Add your first version above." msgid "No versions found. Add your first version above."
msgstr "Keine Versionen gefunden. Fügen Sie Ihre erste Version oben hinzu." msgstr "Keine Versionen gefunden. Fügen Sie Ihre erste Version oben hinzu."
#: src/Admin/VersionAdminController.php:156 #: src/Admin/VersionAdminController.php:165
#: src/Admin/VersionAdminController.php:378 #: src/Admin/VersionAdminController.php:396
msgid "Uploaded file" msgid "Uploaded file"
msgstr "Hochgeladene Datei" msgstr "Hochgeladene Datei"
#: src/Admin/VersionAdminController.php:159 #: src/Admin/VersionAdminController.php:169
#: src/Admin/VersionAdminController.php:381 #: src/Admin/VersionAdminController.php:400
msgid "No download file" msgid "No download file"
msgstr "Keine Download-Datei" msgstr "Keine Download-Datei"
#: src/Admin/VersionAdminController.php:215 #: src/Admin/VersionAdminController.php:232
msgid "Are you sure you want to delete this version?" msgid "Are you sure you want to delete this version?"
msgstr "Sind Sie sicher, dass Sie diese Version löschen möchten?" msgstr "Sind Sie sicher, dass Sie diese Version löschen möchten?"
#: src/Admin/VersionAdminController.php:216 #: src/Admin/VersionAdminController.php:233
msgid "Please enter a version number." msgid "Please enter a version number."
msgstr "Bitte geben Sie eine Versionsnummer ein." msgstr "Bitte geben Sie eine Versionsnummer ein."
#: src/Admin/VersionAdminController.php:217 #: src/Admin/VersionAdminController.php:234
msgid "Please enter a valid version number (e.g., 1.0.0)." msgid "Please enter a valid version number (e.g., 1.0.0)."
msgstr "Bitte geben Sie eine gültige Versionsnummer ein (z.B. 1.0.0)." msgstr "Bitte geben Sie eine gültige Versionsnummer ein (z.B. 1.0.0)."
#: src/Admin/VersionAdminController.php:218 #: src/Admin/VersionAdminController.php:235
msgid "An error occurred. Please try again." msgid "An error occurred. Please try again."
msgstr "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut." msgstr "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut."
#: src/Admin/VersionAdminController.php:219 #: src/Admin/VersionAdminController.php:236
msgid "Select Download File" msgid "Select Download File"
msgstr "Download-Datei auswählen" msgstr "Download-Datei auswählen"
#: src/Admin/VersionAdminController.php:220 #: src/Admin/VersionAdminController.php:237
msgid "Use this file" msgid "Use this file"
msgstr "Diese Datei verwenden" msgstr "Diese Datei verwenden"
#: src/Admin/VersionAdminController.php:221 #: src/Admin/VersionAdminController.php:238
msgid "" msgid ""
"Invalid checksum file format. File must contain a 64-character SHA256 hash." "Invalid checksum file format. File must contain a 64-character SHA256 hash."
msgstr "" msgstr ""
"Ungültiges Prüfsummendateiformat. Die Datei muss einen 64-stelligen " "Ungültiges Prüfsummendateiformat. Die Datei muss einen 64-stelligen SHA256-"
"SHA256-Hash enthalten." "Hash enthalten."
#: src/Admin/VersionAdminController.php:222 #: src/Admin/VersionAdminController.php:239
msgid "Failed to read checksum file." msgid "Failed to read checksum file."
msgstr "Prüfsummendatei konnte nicht gelesen werden." msgstr "Prüfsummendatei konnte nicht gelesen werden."
#: src/Admin/VersionAdminController.php:252 #: src/Admin/VersionAdminController.php:269
msgid "Product ID and version are required." msgid "Product ID and version are required."
msgstr "Produkt-ID und Version sind erforderlich." msgstr "Produkt-ID und Version sind erforderlich."
#: src/Admin/VersionAdminController.php:257 #: src/Admin/VersionAdminController.php:274
msgid "Invalid version format. Use semantic versioning (e.g., 1.0.0)." msgid "Invalid version format. Use semantic versioning (e.g., 1.0.0)."
msgstr "" msgstr ""
"Ungültiges Versionsformat. Verwenden Sie semantische Versionierung (z.B. " "Ungültiges Versionsformat. Verwenden Sie semantische Versionierung (z.B. "
"1.0.0)." "1.0.0)."
#: src/Admin/VersionAdminController.php:262 #: src/Admin/VersionAdminController.php:279
msgid "This version already exists." msgid "This version already exists."
msgstr "Diese Version existiert bereits." msgstr "Diese Version existiert bereits."
#: src/Admin/VersionAdminController.php:268 #: src/Admin/VersionAdminController.php:285
msgid "Product not found." msgid "Product not found."
msgstr "Produkt nicht gefunden." msgstr "Produkt nicht gefunden."
#: src/Admin/VersionAdminController.php:272 #: src/Admin/VersionAdminController.php:289
msgid "This product is not a licensed product." msgid "This product is not a licensed product."
msgstr "Dieses Produkt ist kein lizensiertes Produkt." msgstr "Dieses Produkt ist kein lizensiertes Produkt."
#: src/Admin/VersionAdminController.php:289 #: src/Admin/VersionAdminController.php:306
msgid "Failed to create version." msgid "Failed to create version."
msgstr "Version konnte nicht erstellt werden." msgstr "Version konnte nicht erstellt werden."
#: src/Admin/VersionAdminController.php:297 #: src/Admin/VersionAdminController.php:314
msgid "Version added successfully." msgid "Version added successfully."
msgstr "Version erfolgreich hinzugefügt." msgstr "Version erfolgreich hinzugefügt."
#: src/Admin/VersionAdminController.php:317 #: src/Admin/VersionAdminController.php:334
#: src/Admin/VersionAdminController.php:344 #: src/Admin/VersionAdminController.php:361
msgid "Version ID is required." msgid "Version ID is required."
msgstr "Versions-ID ist erforderlich." msgstr "Versions-ID ist erforderlich."
#: src/Admin/VersionAdminController.php:323 #: src/Admin/VersionAdminController.php:340
msgid "Failed to delete version." msgid "Failed to delete version."
msgstr "Version konnte nicht gelöscht werden." msgstr "Version konnte nicht gelöscht werden."
#: src/Admin/VersionAdminController.php:326 #: src/Admin/VersionAdminController.php:343
msgid "Version deleted successfully." msgid "Version deleted successfully."
msgstr "Version erfolgreich gelöscht." msgstr "Version erfolgreich gelöscht."
#: src/Admin/VersionAdminController.php:350 #: src/Admin/VersionAdminController.php:367
msgid "Failed to update version." msgid "Failed to update version."
msgstr "Version konnte nicht aktualisiert werden." msgstr "Version konnte nicht aktualisiert werden."
#: src/Admin/VersionAdminController.php:354 #: src/Admin/VersionAdminController.php:371
msgid "Version updated successfully." msgid "Version updated successfully."
msgstr "Version erfolgreich aktualisiert." msgstr "Version erfolgreich aktualisiert."
@@ -1083,11 +1162,19 @@ msgstr "Diese Lizenz ist inaktiv."
msgid "This license is not valid for this domain." msgid "This license is not valid for this domain."
msgstr "Diese Lizenz ist für diese Domain nicht gültig." msgstr "Diese Lizenz ist für diese Domain nicht gültig."
#: src/License/LicenseManager.php:760 src/Frontend/AccountController.php:139 #: src/License/LicenseManager.php:760 src/Frontend/AccountController.php:140
#: src/Email/LicenseExpirationEmail.php:107 #: src/Email/LicenseExpirationEmail.php:107
msgid "Unknown Product" msgid "Unknown Product"
msgstr "Unbekanntes Produkt" msgstr "Unbekanntes Produkt"
#: src/License/PluginLicenseChecker.php:117
msgid "License settings not configured."
msgstr "Lizenzeinstellungen nicht konfiguriert."
#: src/License/PluginLicenseChecker.php:153
msgid "Could not connect to license server."
msgstr "Verbindung zum Lizenzserver konnte nicht hergestellt werden."
#: src/Product/LicensedProductType.php:55 #: src/Product/LicensedProductType.php:55
msgid "Licensed Product" msgid "Licensed Product"
msgstr "Lizensiertes Produkt" msgstr "Lizensiertes Produkt"
@@ -1101,7 +1188,6 @@ msgstr "Lizenz-Einstellungen"
msgid "%d days" msgid "%d days"
msgstr "%d Tage" msgstr "%d Tage"
#. translators: %s: URL to settings page
#: src/Product/LicensedProductType.php:113 #: src/Product/LicensedProductType.php:113
#, php-format #, php-format
msgid "Leave fields empty to use default settings from %s." msgid "Leave fields empty to use default settings from %s."
@@ -1115,7 +1201,6 @@ msgstr "WooCommerce > Einstellungen > Lizensierte Produkte"
msgid "Max Activations" msgid "Max Activations"
msgstr "Max. Aktivierungen" msgstr "Max. Aktivierungen"
#. translators: %d: default max activations value
#: src/Product/LicensedProductType.php:125 #: src/Product/LicensedProductType.php:125
#, php-format #, php-format
msgid "Maximum number of domain activations per license. Default: %d" msgid "Maximum number of domain activations per license. Default: %d"
@@ -1125,7 +1210,6 @@ msgstr "Maximale Anzahl der Domain-Aktivierungen pro Lizenz. Standard: %d"
msgid "License Validity (Days)" msgid "License Validity (Days)"
msgstr "Lizenz-Gültigkeit (Tage)" msgstr "Lizenz-Gültigkeit (Tage)"
#. translators: %s: default validity value
#: src/Product/LicensedProductType.php:143 #: src/Product/LicensedProductType.php:143
#, php-format #, php-format
msgid "Number of days the license is valid. Leave empty for default (%s)." msgid "Number of days the license is valid. Leave empty for default (%s)."
@@ -1135,7 +1219,6 @@ msgstr "Anzahl Tage, die die Lizenz gültig ist. Leer lassen für Standard (%s).
msgid "Bind to Major Version" msgid "Bind to Major Version"
msgstr "An Hauptversion binden" msgstr "An Hauptversion binden"
#. translators: %s: default bind to version value (Yes/No)
#: src/Product/LicensedProductType.php:161 #: src/Product/LicensedProductType.php:161
#, php-format #, php-format
msgid "" msgid ""
@@ -1157,7 +1240,6 @@ msgstr "Nein"
msgid "Attachment file not found." msgid "Attachment file not found."
msgstr "Anhangs-Datei nicht gefunden." msgstr "Anhangs-Datei nicht gefunden."
#. translators: 1: provided hash, 2: calculated hash
#: src/Product/VersionManager.php:177 #: src/Product/VersionManager.php:177
#, php-format #, php-format
msgid "File checksum does not match. Expected: %1$s, Got: %2$s" msgid "File checksum does not match. Expected: %1$s, Got: %2$s"
@@ -1218,11 +1300,11 @@ msgstr "Download-Datei nicht gefunden."
msgid "Please log in to view your licenses." msgid "Please log in to view your licenses."
msgstr "Bitte melden Sie sich an, um Ihre Lizenzen zu sehen." msgstr "Bitte melden Sie sich an, um Ihre Lizenzen zu sehen."
#: src/Frontend/AccountController.php:164 #: src/Frontend/AccountController.php:165
msgid "You have no licenses yet." msgid "You have no licenses yet."
msgstr "Sie haben noch keine Lizenzen." msgstr "Sie haben noch keine Lizenzen."
#: src/Frontend/AccountController.php:189 #: src/Frontend/AccountController.php:190
#: src/Email/LicenseEmailController.php:173 #: src/Email/LicenseEmailController.php:173
#: src/Email/LicenseEmailController.php:177 #: src/Email/LicenseEmailController.php:177
#: src/Email/LicenseEmailController.php:281 #: src/Email/LicenseEmailController.php:281
@@ -1231,76 +1313,76 @@ msgstr "Sie haben noch keine Lizenzen."
msgid "License Key:" msgid "License Key:"
msgstr "Lizenzschlüssel:" msgstr "Lizenzschlüssel:"
#: src/Frontend/AccountController.php:200 #: src/Frontend/AccountController.php:201
#: src/Email/LicenseExpirationEmail.php:215 #: src/Email/LicenseExpirationEmail.php:215
#: src/Email/LicenseExpirationEmail.php:271 #: src/Email/LicenseExpirationEmail.php:271
msgid "Domain:" msgid "Domain:"
msgstr "Domain:" msgstr "Domain:"
#: src/Frontend/AccountController.php:206 #: src/Frontend/AccountController.php:207
msgid "Transfer to new domain" msgid "Transfer to new domain"
msgstr "Auf neue Domain übertragen" msgstr "Auf neue Domain übertragen"
#: src/Frontend/AccountController.php:208 #: src/Frontend/AccountController.php:209
msgid "Transfer" msgid "Transfer"
msgstr "Übertragen" msgstr "Übertragen"
#: src/Frontend/AccountController.php:212 #: src/Frontend/AccountController.php:213
#: src/Email/LicenseEmailController.php:284 #: src/Email/LicenseEmailController.php:284
#: src/Email/LicenseExpirationEmail.php:219 #: src/Email/LicenseExpirationEmail.php:219
#: src/Email/LicenseExpirationEmail.php:272 #: src/Email/LicenseExpirationEmail.php:272
msgid "Expires:" msgid "Expires:"
msgstr "Läuft ab:" msgstr "Läuft ab:"
#: src/Frontend/AccountController.php:217 #: src/Frontend/AccountController.php:218
#: src/Email/LicenseEmailController.php:248 #: src/Email/LicenseEmailController.php:248
#: src/Email/LicenseEmailController.php:287 #: src/Email/LicenseEmailController.php:287
msgid "Never" msgid "Never"
msgstr "Nie" msgstr "Nie"
#: src/Frontend/AccountController.php:225 #: src/Frontend/AccountController.php:226
msgid "Available Downloads" msgid "Available Downloads"
msgstr "Verfügbare Downloads" msgstr "Verfügbare Downloads"
#: src/Frontend/AccountController.php:231 #: src/Frontend/AccountController.php:232
#, php-format #, php-format
msgid "Version %s" msgid "Version %s"
msgstr "Version %s" msgstr "Version %s"
#: src/Frontend/AccountController.php:248 #: src/Frontend/AccountController.php:249
msgid "Close" msgid "Close"
msgstr "Schliessen" msgstr "Schliessen"
#: src/Frontend/AccountController.php:249 #: src/Frontend/AccountController.php:250
msgid "Transfer License to New Domain" msgid "Transfer License to New Domain"
msgstr "Lizenz auf neue Domain übertragen" msgstr "Lizenz auf neue Domain übertragen"
#: src/Frontend/AccountController.php:254 #: src/Frontend/AccountController.php:255
msgid "Current Domain" msgid "Current Domain"
msgstr "Aktuelle Domain" msgstr "Aktuelle Domain"
#: src/Frontend/AccountController.php:259 #: src/Frontend/AccountController.php:260
msgid "New Domain" msgid "New Domain"
msgstr "Neue Domain" msgstr "Neue Domain"
#: src/Frontend/AccountController.php:263 #: src/Frontend/AccountController.php:264
msgid "Enter the new domain without http:// or www." msgid "Enter the new domain without http:// or www."
msgstr "Geben Sie die neue Domain ohne http:// oder www ein." msgstr "Geben Sie die neue Domain ohne http:// oder www ein."
#: src/Frontend/AccountController.php:268 #: src/Frontend/AccountController.php:269
msgid "Transfer License" msgid "Transfer License"
msgstr "Lizenz übertragen" msgstr "Lizenz übertragen"
#: src/Frontend/AccountController.php:310 #: src/Frontend/AccountController.php:311
#: src/Frontend/AccountController.php:377 #: src/Frontend/AccountController.php:378
msgid "License transferred successfully!" msgid "License transferred successfully!"
msgstr "Lizenz erfolgreich übertragen!" msgstr "Lizenz erfolgreich übertragen!"
#: src/Frontend/AccountController.php:311 #: src/Frontend/AccountController.php:312
msgid "Transfer failed. Please try again." msgid "Transfer failed. Please try again."
msgstr "Übertragung fehlgeschlagen. Bitte versuchen Sie es erneut." msgstr "Übertragung fehlgeschlagen. Bitte versuchen Sie es erneut."
#: src/Frontend/AccountController.php:312 #: src/Frontend/AccountController.php:313
msgid "" msgid ""
"Are you sure you want to transfer this license to a new domain? This action " "Are you sure you want to transfer this license to a new domain? This action "
"cannot be undone." "cannot be undone."
@@ -1308,31 +1390,31 @@ msgstr ""
"Sind Sie sicher, dass Sie diese Lizenz auf eine neue Domain übertragen " "Sind Sie sicher, dass Sie diese Lizenz auf eine neue Domain übertragen "
"möchten? Diese Aktion kann nicht rückgängig gemacht werden." "möchten? Diese Aktion kann nicht rückgängig gemacht werden."
#: src/Frontend/AccountController.php:331 #: src/Frontend/AccountController.php:332
msgid "Please log in to transfer a license." msgid "Please log in to transfer a license."
msgstr "Bitte melden Sie sich an, um eine Lizenz zu übertragen." msgstr "Bitte melden Sie sich an, um eine Lizenz zu übertragen."
#: src/Frontend/AccountController.php:337 #: src/Frontend/AccountController.php:338
msgid "Invalid license." msgid "Invalid license."
msgstr "Ungültige Lizenz." msgstr "Ungültige Lizenz."
#: src/Frontend/AccountController.php:355 #: src/Frontend/AccountController.php:356
msgid "You do not have permission to transfer this license." msgid "You do not have permission to transfer this license."
msgstr "Sie haben keine Berechtigung, diese Lizenz zu übertragen." msgstr "Sie haben keine Berechtigung, diese Lizenz zu übertragen."
#: src/Frontend/AccountController.php:360 #: src/Frontend/AccountController.php:361
msgid "Revoked licenses cannot be transferred." msgid "Revoked licenses cannot be transferred."
msgstr "Widerrufene Lizenzen können nicht übertragen werden." msgstr "Widerrufene Lizenzen können nicht übertragen werden."
#: src/Frontend/AccountController.php:364 #: src/Frontend/AccountController.php:365
msgid "Expired licenses cannot be transferred." msgid "Expired licenses cannot be transferred."
msgstr "Abgelaufene Lizenzen können nicht übertragen werden." msgstr "Abgelaufene Lizenzen können nicht übertragen werden."
#: src/Frontend/AccountController.php:369 #: src/Frontend/AccountController.php:370
msgid "The new domain is the same as the current domain." msgid "The new domain is the same as the current domain."
msgstr "Die neue Domain ist dieselbe wie die aktuelle Domain." msgstr "Die neue Domain ist dieselbe wie die aktuelle Domain."
#: src/Frontend/AccountController.php:381 #: src/Frontend/AccountController.php:382
msgid "Failed to transfer license. Please try again." msgid "Failed to transfer license. Please try again."
msgstr "Lizenzübertragung fehlgeschlagen. Bitte versuchen Sie es erneut." msgstr "Lizenzübertragung fehlgeschlagen. Bitte versuchen Sie es erneut."
@@ -1420,7 +1502,6 @@ msgstr ""
"Um dieses Produkt weiterhin zu nutzen, verlängern Sie bitte Ihre Lizenz vor " "Um dieses Produkt weiterhin zu nutzen, verlängern Sie bitte Ihre Lizenz vor "
"dem Ablaufdatum." "dem Ablaufdatum."
#. translators: %s: list of placeholders
#: src/Email/LicenseExpirationEmail.php:301 #: src/Email/LicenseExpirationEmail.php:301
#, php-format #, php-format
msgid "Available placeholders: %s" msgid "Available placeholders: %s"
@@ -1458,7 +1539,21 @@ msgstr "E-Mail-Typ"
msgid "Choose which format of email to send." msgid "Choose which format of email to send."
msgstr "Wählen Sie, welches E-Mail-Format gesendet werden soll." msgstr "Wählen Sie, welches E-Mail-Format gesendet werden soll."
#. translators: %s: WooCommerce plugin name #: src/Plugin.php:255
msgid "WC Licensed Product"
msgstr "WC Licensed Product"
#: src/Plugin.php:256
msgid ""
"Plugin license is not configured or invalid. Frontend features are disabled."
msgstr ""
"Plugin-Lizenz ist nicht konfiguriert oder ungültig. Frontend-Funktionen sind "
"deaktiviert."
#: src/Plugin.php:257
msgid "Configure License"
msgstr "Lizenz konfigurieren"
#: wc-licensed-product.php:61 #: wc-licensed-product.php:61
#, php-format #, php-format
msgid "%s requires WooCommerce to be installed and active." msgid "%s requires WooCommerce to be installed and active."
@@ -1479,9 +1574,6 @@ msgstr ""
#~ "Alternativ: Geben Sie eine externe Download-URL ein, anstatt eine Datei " #~ "Alternativ: Geben Sie eine externe Download-URL ein, anstatt eine Datei "
#~ "hochzuladen." #~ "hochzuladen."
#~ msgid "SHA256 Hash"
#~ msgstr "SHA256 Prüfsumme"
#~ msgid "Enter SHA256 checksum..." #~ msgid "Enter SHA256 checksum..."
#~ msgstr "SHA256 Prüfsumme eingeben..." #~ msgstr "SHA256 Prüfsumme eingeben..."

View File

@@ -1,105 +1,23 @@
# WooCommerce Licensed Product Translation Template # SOME DESCRIPTIVE TITLE.
# Copyright (C) 2026 Marco Graetsch # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the WooCommerce Licensed Product package. # This file is distributed under the same license as the WC Licensed Product package.
# Marco Graetsch <magdev3.0@gmail.com>, 2026. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: WooCommerce Licensed Product 0.2.1\n" "Project-Id-Version: WC Licensed Product 0.3.2\n"
"Report-Msgid-Bugs-To: magdev3.0@gmail.com\n" "Report-Msgid-Bugs-To: magdev3.0@gmail.com\n"
"POT-Creation-Date: 2026-01-22 17:10+0100\n" "POT-Creation-Date: 2026-01-22 19:11+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: src/Admin/SettingsController.php:45
msgid "Licensed Products"
msgstr ""
#: src/Admin/SettingsController.php:56
msgid "Default License Settings"
msgstr ""
#: src/Admin/SettingsController.php:58
msgid ""
"These settings serve as defaults for new licensed products. Individual "
"product settings override these defaults."
msgstr ""
#: src/Admin/SettingsController.php:62
msgid "Default Max Activations"
msgstr ""
#: src/Admin/SettingsController.php:64
msgid "Default maximum number of domain activations per license."
msgstr ""
#: src/Admin/SettingsController.php:73
msgid "Default License Validity (Days)"
msgstr ""
#: src/Admin/SettingsController.php:75
msgid ""
"Default number of days a license is valid. Leave empty or set to 0 for "
"lifetime licenses."
msgstr ""
#: src/Admin/SettingsController.php:78 src/Admin/OrderLicenseController.php:201
#: src/Admin/AdminController.php:148 src/Admin/AdminController.php:263
#: src/Admin/AdminController.php:1335 src/Product/LicensedProductType.php:104
#: src/Product/LicensedProductType.php:152
msgid "Lifetime"
msgstr ""
#: src/Admin/SettingsController.php:85
msgid "Default Bind to Major Version"
msgstr ""
#: src/Admin/SettingsController.php:87
msgid ""
"If enabled, licenses are bound to the major version at purchase time by "
"default."
msgstr ""
#: src/Admin/SettingsController.php:97
msgid "Expiration Warning Schedule"
msgstr ""
#. translators: %s: URL to WooCommerce email settings
#: src/Admin/SettingsController.php:101
#, php-format
msgid ""
"Configure when expiration warning emails are sent. To customize the email "
"template, enable/disable, or change the subject, go to %s."
msgstr ""
#: src/Admin/SettingsController.php:103
msgid "WooCommerce > Settings > Emails > License Expiration Warning"
msgstr ""
#: src/Admin/SettingsController.php:108
msgid "First Warning (Days Before)"
msgstr ""
#: src/Admin/SettingsController.php:110
msgid "Days before expiration to send the first warning email."
msgstr ""
#: src/Admin/SettingsController.php:119
msgid "Second Warning (Days Before)"
msgstr ""
#: src/Admin/SettingsController.php:121
msgid ""
"Days before expiration to send the second warning email. Set to 0 to disable."
msgstr ""
#: src/Admin/OrderLicenseController.php:56 #: src/Admin/OrderLicenseController.php:56
msgid "Product Licenses" msgid "Product Licenses"
msgstr "" msgstr ""
@@ -158,7 +76,8 @@ msgid "Licenses will be generated when the order is marked as paid/completed."
msgstr "" msgstr ""
#: src/Admin/OrderLicenseController.php:144 src/Admin/AdminController.php:1253 #: src/Admin/OrderLicenseController.php:144 src/Admin/AdminController.php:1253
#: src/Admin/AdminController.php:1391 src/Email/LicenseEmailController.php:230 #: src/Admin/AdminController.php:1391 src/Admin/SettingsController.php:142
#: src/Email/LicenseEmailController.php:230
msgid "License Key" msgid "License Key"
msgstr "" msgstr ""
@@ -173,7 +92,7 @@ msgid "Domain"
msgstr "" msgstr ""
#: src/Admin/OrderLicenseController.php:147 src/Admin/AdminController.php:1257 #: src/Admin/OrderLicenseController.php:147 src/Admin/AdminController.php:1257
#: src/Admin/AdminController.php:1395 src/Admin/VersionAdminController.php:132 #: src/Admin/AdminController.php:1395 src/Admin/VersionAdminController.php:140
msgid "Status" msgid "Status"
msgstr "" msgstr ""
@@ -183,7 +102,7 @@ msgid "Expires"
msgstr "" msgstr ""
#: src/Admin/OrderLicenseController.php:149 src/Admin/AdminController.php:1260 #: src/Admin/OrderLicenseController.php:149 src/Admin/AdminController.php:1260
#: src/Admin/AdminController.php:1398 src/Admin/VersionAdminController.php:134 #: src/Admin/AdminController.php:1398 src/Admin/VersionAdminController.php:142
msgid "Actions" msgid "Actions"
msgstr "" msgstr ""
@@ -198,15 +117,21 @@ msgstr ""
#: src/Admin/OrderLicenseController.php:185 src/Admin/AdminController.php:146 #: src/Admin/OrderLicenseController.php:185 src/Admin/AdminController.php:146
#: src/Admin/AdminController.php:1303 src/Admin/AdminController.php:1323 #: src/Admin/AdminController.php:1303 src/Admin/AdminController.php:1323
#: src/Admin/AdminController.php:1344 src/Frontend/AccountController.php:270 #: src/Admin/AdminController.php:1344 src/Frontend/AccountController.php:271
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
#: src/Admin/OrderLicenseController.php:201 src/Admin/AdminController.php:148
#: src/Admin/AdminController.php:263 src/Admin/AdminController.php:1335
#: src/Admin/SettingsController.php:192 src/Product/LicensedProductType.php:104
#: src/Product/LicensedProductType.php:152
msgid "Lifetime"
msgstr ""
#: src/Admin/OrderLicenseController.php:208 #: src/Admin/OrderLicenseController.php:208
msgid "View in Licenses" msgid "View in Licenses"
msgstr "" msgstr ""
#. translators: %s: Link to licenses page
#: src/Admin/OrderLicenseController.php:221 #: src/Admin/OrderLicenseController.php:221
#, php-format #, php-format
msgid "For more actions (revoke, extend, delete), go to the %s page." msgid "For more actions (revoke, extend, delete), go to the %s page."
@@ -225,8 +150,8 @@ msgid "Error saving. Please try again."
msgstr "" msgstr ""
#: src/Admin/OrderLicenseController.php:288 #: src/Admin/OrderLicenseController.php:288
#: src/Frontend/AccountController.php:313 #: src/Frontend/AccountController.php:314
#: src/Frontend/AccountController.php:345 #: src/Frontend/AccountController.php:346
msgid "Please enter a valid domain." msgid "Please enter a valid domain."
msgstr "" msgstr ""
@@ -234,9 +159,9 @@ msgstr ""
#: src/Admin/OrderLicenseController.php:340 src/Admin/AdminController.php:170 #: src/Admin/OrderLicenseController.php:340 src/Admin/AdminController.php:170
#: src/Admin/AdminController.php:210 src/Admin/AdminController.php:246 #: src/Admin/AdminController.php:210 src/Admin/AdminController.php:246
#: src/Admin/AdminController.php:298 src/Admin/AdminController.php:336 #: src/Admin/AdminController.php:298 src/Admin/AdminController.php:336
#: src/Admin/VersionAdminController.php:242 #: src/Admin/VersionAdminController.php:259
#: src/Admin/VersionAdminController.php:311 #: src/Admin/VersionAdminController.php:328
#: src/Admin/VersionAdminController.php:337 #: src/Admin/VersionAdminController.php:354
msgid "Permission denied." msgid "Permission denied."
msgstr "" msgstr ""
@@ -265,7 +190,7 @@ msgstr ""
#: src/Admin/OrderLicenseController.php:363 #: src/Admin/OrderLicenseController.php:363
#: src/Frontend/DownloadController.php:105 #: src/Frontend/DownloadController.php:105
#: src/Frontend/AccountController.php:351 #: src/Frontend/AccountController.php:352
msgid "License not found." msgid "License not found."
msgstr "" msgstr ""
@@ -311,25 +236,25 @@ msgstr ""
msgid "Edit" msgid "Edit"
msgstr "" msgstr ""
#: src/Admin/AdminController.php:149 src/Frontend/AccountController.php:308 #: src/Admin/AdminController.php:149 src/Frontend/AccountController.php:309
msgid "Copied!" msgid "Copied!"
msgstr "" msgstr ""
#: src/Admin/AdminController.php:150 src/Frontend/AccountController.php:309 #: src/Admin/AdminController.php:150 src/Frontend/AccountController.php:310
msgid "Copy failed" msgid "Copy failed"
msgstr "" msgstr ""
#: src/Admin/AdminController.php:153 src/Admin/AdminController.php:875 #: src/Admin/AdminController.php:153 src/Admin/AdminController.php:875
#: src/Admin/AdminController.php:1194 src/Admin/AdminController.php:1317 #: src/Admin/AdminController.php:1194 src/Admin/AdminController.php:1317
#: src/Admin/VersionAdminController.php:165 #: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:387 #: src/Admin/VersionAdminController.php:413
msgid "Active" msgid "Active"
msgstr "" msgstr ""
#: src/Admin/AdminController.php:154 src/Admin/AdminController.php:882 #: src/Admin/AdminController.php:154 src/Admin/AdminController.php:882
#: src/Admin/AdminController.php:1195 src/Admin/AdminController.php:1318 #: src/Admin/AdminController.php:1195 src/Admin/AdminController.php:1318
#: src/Admin/VersionAdminController.php:165 #: src/Admin/VersionAdminController.php:182
#: src/Admin/VersionAdminController.php:387 #: src/Admin/VersionAdminController.php:413
msgid "Inactive" msgid "Inactive"
msgstr "" msgstr ""
@@ -395,7 +320,7 @@ msgstr ""
#: src/Admin/AdminController.php:466 src/Admin/AdminController.php:484 #: src/Admin/AdminController.php:466 src/Admin/AdminController.php:484
#: src/Admin/AdminController.php:504 src/Admin/AdminController.php:522 #: src/Admin/AdminController.php:504 src/Admin/AdminController.php:522
#: src/Admin/AdminController.php:589 src/Admin/AdminController.php:779 #: src/Admin/AdminController.php:589 src/Admin/AdminController.php:779
#: src/Frontend/AccountController.php:325 #: src/Admin/SettingsController.php:454 src/Frontend/AccountController.php:326
msgid "Security check failed." msgid "Security check failed."
msgstr "" msgstr ""
@@ -475,7 +400,6 @@ msgstr ""
msgid "License set to lifetime successfully." msgid "License set to lifetime successfully."
msgstr "" msgstr ""
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1068 #: src/Admin/AdminController.php:1068
#, php-format #, php-format
msgid "%d license activated." msgid "%d license activated."
@@ -483,7 +407,6 @@ msgid_plural "%d licenses activated."
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1076 #: src/Admin/AdminController.php:1076
#, php-format #, php-format
msgid "%d license deactivated." msgid "%d license deactivated."
@@ -491,7 +414,6 @@ msgid_plural "%d licenses deactivated."
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1084 #: src/Admin/AdminController.php:1084
#, php-format #, php-format
msgid "%d license revoked." msgid "%d license revoked."
@@ -499,7 +421,6 @@ msgid_plural "%d licenses revoked."
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1092 #: src/Admin/AdminController.php:1092
#, php-format #, php-format
msgid "%d license deleted." msgid "%d license deleted."
@@ -507,7 +428,6 @@ msgid_plural "%d licenses deleted."
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#. translators: %d: number of licenses
#: src/Admin/AdminController.php:1100 #: src/Admin/AdminController.php:1100
#, php-format #, php-format
msgid "%d license extended." msgid "%d license extended."
@@ -527,7 +447,6 @@ msgstr ""
msgid "No licenses to export." msgid "No licenses to export."
msgstr "" msgstr ""
#. translators: %d: number of licenses imported
#: src/Admin/AdminController.php:1121 #: src/Admin/AdminController.php:1121
#, php-format #, php-format
msgid "%d license imported." msgid "%d license imported."
@@ -535,7 +454,6 @@ msgid_plural "%d licenses imported."
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#. translators: %d: number of licenses updated
#: src/Admin/AdminController.php:1128 #: src/Admin/AdminController.php:1128
#, php-format #, php-format
msgid "%d updated." msgid "%d updated."
@@ -543,7 +461,6 @@ msgid_plural "%d updated."
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#. translators: %d: number of licenses skipped
#: src/Admin/AdminController.php:1136 #: src/Admin/AdminController.php:1136
#, php-format #, php-format
msgid "%d skipped." msgid "%d skipped."
@@ -551,7 +468,6 @@ msgid_plural "%d skipped."
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#. translators: %d: number of errors
#: src/Admin/AdminController.php:1144 #: src/Admin/AdminController.php:1144
#, php-format #, php-format
msgid "%d error." msgid "%d error."
@@ -628,14 +544,14 @@ msgid "Bulk Actions"
msgstr "" msgstr ""
#: src/Admin/AdminController.php:1235 src/Admin/AdminController.php:1407 #: src/Admin/AdminController.php:1235 src/Admin/AdminController.php:1407
#: src/Admin/VersionAdminController.php:171 #: src/Admin/VersionAdminController.php:188
#: src/Admin/VersionAdminController.php:393 #: src/Admin/VersionAdminController.php:419
msgid "Activate" msgid "Activate"
msgstr "" msgstr ""
#: src/Admin/AdminController.php:1236 src/Admin/AdminController.php:1408 #: src/Admin/AdminController.php:1236 src/Admin/AdminController.php:1408
#: src/Admin/VersionAdminController.php:171 #: src/Admin/VersionAdminController.php:188
#: src/Admin/VersionAdminController.php:393 #: src/Admin/VersionAdminController.php:419
msgid "Deactivate" msgid "Deactivate"
msgstr "" msgstr ""
@@ -657,8 +573,8 @@ msgid "Extend 1 year"
msgstr "" msgstr ""
#: src/Admin/AdminController.php:1241 src/Admin/AdminController.php:1377 #: src/Admin/AdminController.php:1241 src/Admin/AdminController.php:1377
#: src/Admin/AdminController.php:1413 src/Admin/VersionAdminController.php:174 #: src/Admin/AdminController.php:1413 src/Admin/VersionAdminController.php:191
#: src/Admin/VersionAdminController.php:396 #: src/Admin/VersionAdminController.php:422
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
@@ -679,7 +595,7 @@ msgstr ""
msgid "No licenses found." msgid "No licenses found."
msgstr "" msgstr ""
#: src/Admin/AdminController.php:1276 src/Frontend/AccountController.php:193 #: src/Admin/AdminController.php:1276 src/Frontend/AccountController.php:194
msgid "Copy to clipboard" msgid "Copy to clipboard"
msgstr "" msgstr ""
@@ -802,6 +718,156 @@ msgstr ""
msgid "No domain specified" msgid "No domain specified"
msgstr "" msgstr ""
#: src/Admin/SettingsController.php:54
msgid "Licensed Products"
msgstr ""
#: src/Admin/SettingsController.php:64 src/Admin/SettingsController.php:128
msgid "Plugin License"
msgstr ""
#: src/Admin/SettingsController.php:65
msgid "Default Settings"
msgstr ""
#: src/Admin/SettingsController.php:66
msgid "Notifications"
msgstr ""
#: src/Admin/SettingsController.php:130
msgid ""
"Configure the license for this plugin. A valid license is required for "
"frontend features to work."
msgstr ""
#: src/Admin/SettingsController.php:134
msgid "License Server URL"
msgstr ""
#: src/Admin/SettingsController.php:136
msgid "The URL of the license server (e.g., https://shop.example.com)."
msgstr ""
#: src/Admin/SettingsController.php:144
msgid "Your license key in XXXX-XXXX-XXXX-XXXX format."
msgstr ""
#: src/Admin/SettingsController.php:150
msgid "Server Secret (Optional)"
msgstr ""
#: src/Admin/SettingsController.php:152
msgid ""
"If the license server uses signed responses, enter the shared secret here "
"for enhanced security."
msgstr ""
#: src/Admin/SettingsController.php:170
msgid "Default License Settings"
msgstr ""
#: src/Admin/SettingsController.php:172
msgid ""
"These settings serve as defaults for new licensed products. Individual "
"product settings override these defaults."
msgstr ""
#: src/Admin/SettingsController.php:176
msgid "Default Max Activations"
msgstr ""
#: src/Admin/SettingsController.php:178
msgid "Default maximum number of domain activations per license."
msgstr ""
#: src/Admin/SettingsController.php:187
msgid "Default License Validity (Days)"
msgstr ""
#: src/Admin/SettingsController.php:189
msgid ""
"Default number of days a license is valid. Leave empty or set to 0 for "
"lifetime licenses."
msgstr ""
#: src/Admin/SettingsController.php:199
msgid "Default Bind to Major Version"
msgstr ""
#: src/Admin/SettingsController.php:201
msgid ""
"If enabled, licenses are bound to the major version at purchase time by "
"default."
msgstr ""
#: src/Admin/SettingsController.php:219
msgid "Expiration Warning Schedule"
msgstr ""
#: src/Admin/SettingsController.php:223
#, php-format
msgid ""
"Configure when expiration warning emails are sent. To customize the email "
"template, enable/disable, or change the subject, go to %s."
msgstr ""
#: src/Admin/SettingsController.php:225
msgid "WooCommerce > Settings > Emails > License Expiration Warning"
msgstr ""
#: src/Admin/SettingsController.php:230
msgid "First Warning (Days Before)"
msgstr ""
#: src/Admin/SettingsController.php:232
msgid "Days before expiration to send the first warning email."
msgstr ""
#: src/Admin/SettingsController.php:241
msgid "Second Warning (Days Before)"
msgstr ""
#: src/Admin/SettingsController.php:243
msgid ""
"Days before expiration to send the second warning email. Set to 0 to disable."
msgstr ""
#: src/Admin/SettingsController.php:283
msgid "Running on localhost - license validation bypassed."
msgstr ""
#: src/Admin/SettingsController.php:291
msgid "License is valid and active."
msgstr ""
#: src/Admin/SettingsController.php:297
msgid "License is not valid. Frontend features are disabled."
msgstr ""
#: src/Admin/SettingsController.php:308 src/Admin/SettingsController.php:344
msgid "Verify License"
msgstr ""
#: src/Admin/SettingsController.php:322
msgid "Verifying..."
msgstr ""
#: src/Admin/SettingsController.php:341
msgid "Request failed."
msgstr ""
#: src/Admin/SettingsController.php:458
msgid "Insufficient permissions."
msgstr ""
#: src/Admin/SettingsController.php:467
msgid "License verified successfully!"
msgstr ""
#: src/Admin/SettingsController.php:469
msgid "License validation failed."
msgstr ""
#: src/Admin/VersionAdminController.php:58 #: src/Admin/VersionAdminController.php:58
msgid "Product Versions" msgid "Product Versions"
msgstr "" msgstr ""
@@ -811,7 +877,7 @@ msgid "Add New Version"
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:81 #: src/Admin/VersionAdminController.php:81
#: src/Admin/VersionAdminController.php:129 #: src/Admin/VersionAdminController.php:136
msgid "Version" msgid "Version"
msgstr "" msgstr ""
@@ -820,7 +886,7 @@ msgid "Use semantic versioning (e.g., 1.0.0)"
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:88 #: src/Admin/VersionAdminController.php:88
#: src/Admin/VersionAdminController.php:130 #: src/Admin/VersionAdminController.php:137
msgid "Download File" msgid "Download File"
msgstr "" msgstr ""
@@ -829,6 +895,7 @@ msgid "Select File"
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:96 #: src/Admin/VersionAdminController.php:96
#: src/Admin/VersionAdminController.php:110
msgid "Remove" msgid "Remove"
msgstr "" msgstr ""
@@ -842,121 +909,129 @@ msgstr ""
msgid "Checksum File" msgid "Checksum File"
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:105 #: src/Admin/VersionAdminController.php:107
msgid "Select Checksum File"
msgstr ""
#: src/Admin/VersionAdminController.php:112
msgid "" msgid ""
"Upload a SHA256 checksum file (.sha256 or .txt) to verify file integrity." "Upload a SHA256 checksum file (.sha256 or .txt) to verify file integrity."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:109 #: src/Admin/VersionAdminController.php:116
#: src/Admin/VersionAdminController.php:131 #: src/Admin/VersionAdminController.php:139
msgid "Release Notes" msgid "Release Notes"
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:117 #: src/Admin/VersionAdminController.php:124
msgid "Add Version" msgid "Add Version"
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:125 #: src/Admin/VersionAdminController.php:132
msgid "Existing Versions" msgid "Existing Versions"
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:133 #: src/Admin/VersionAdminController.php:138
msgid "SHA256"
msgstr ""
#: src/Admin/VersionAdminController.php:141
msgid "Released" msgid "Released"
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:140 #: src/Admin/VersionAdminController.php:148
msgid "No versions found. Add your first version above." msgid "No versions found. Add your first version above."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:156 #: src/Admin/VersionAdminController.php:165
#: src/Admin/VersionAdminController.php:378 #: src/Admin/VersionAdminController.php:396
msgid "Uploaded file" msgid "Uploaded file"
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:159 #: src/Admin/VersionAdminController.php:169
#: src/Admin/VersionAdminController.php:381 #: src/Admin/VersionAdminController.php:400
msgid "No download file" msgid "No download file"
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:215 #: src/Admin/VersionAdminController.php:232
msgid "Are you sure you want to delete this version?" msgid "Are you sure you want to delete this version?"
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:216 #: src/Admin/VersionAdminController.php:233
msgid "Please enter a version number." msgid "Please enter a version number."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:217 #: src/Admin/VersionAdminController.php:234
msgid "Please enter a valid version number (e.g., 1.0.0)." msgid "Please enter a valid version number (e.g., 1.0.0)."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:218 #: src/Admin/VersionAdminController.php:235
msgid "An error occurred. Please try again." msgid "An error occurred. Please try again."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:219 #: src/Admin/VersionAdminController.php:236
msgid "Select Download File" msgid "Select Download File"
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:220 #: src/Admin/VersionAdminController.php:237
msgid "Use this file" msgid "Use this file"
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:221 #: src/Admin/VersionAdminController.php:238
msgid "" msgid ""
"Invalid checksum file format. File must contain a 64-character SHA256 hash." "Invalid checksum file format. File must contain a 64-character SHA256 hash."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:222 #: src/Admin/VersionAdminController.php:239
msgid "Failed to read checksum file." msgid "Failed to read checksum file."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:252 #: src/Admin/VersionAdminController.php:269
msgid "Product ID and version are required." msgid "Product ID and version are required."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:257 #: src/Admin/VersionAdminController.php:274
msgid "Invalid version format. Use semantic versioning (e.g., 1.0.0)." msgid "Invalid version format. Use semantic versioning (e.g., 1.0.0)."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:262 #: src/Admin/VersionAdminController.php:279
msgid "This version already exists." msgid "This version already exists."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:268 #: src/Admin/VersionAdminController.php:285
msgid "Product not found." msgid "Product not found."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:272 #: src/Admin/VersionAdminController.php:289
msgid "This product is not a licensed product." msgid "This product is not a licensed product."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:289 #: src/Admin/VersionAdminController.php:306
msgid "Failed to create version." msgid "Failed to create version."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:297 #: src/Admin/VersionAdminController.php:314
msgid "Version added successfully." msgid "Version added successfully."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:317 #: src/Admin/VersionAdminController.php:334
#: src/Admin/VersionAdminController.php:344 #: src/Admin/VersionAdminController.php:361
msgid "Version ID is required." msgid "Version ID is required."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:323 #: src/Admin/VersionAdminController.php:340
msgid "Failed to delete version." msgid "Failed to delete version."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:326 #: src/Admin/VersionAdminController.php:343
msgid "Version deleted successfully." msgid "Version deleted successfully."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:350 #: src/Admin/VersionAdminController.php:367
msgid "Failed to update version." msgid "Failed to update version."
msgstr "" msgstr ""
#: src/Admin/VersionAdminController.php:354 #: src/Admin/VersionAdminController.php:371
msgid "Version updated successfully." msgid "Version updated successfully."
msgstr "" msgstr ""
@@ -1047,11 +1122,19 @@ msgstr ""
msgid "This license is not valid for this domain." msgid "This license is not valid for this domain."
msgstr "" msgstr ""
#: src/License/LicenseManager.php:760 src/Frontend/AccountController.php:139 #: src/License/LicenseManager.php:760 src/Frontend/AccountController.php:140
#: src/Email/LicenseExpirationEmail.php:107 #: src/Email/LicenseExpirationEmail.php:107
msgid "Unknown Product" msgid "Unknown Product"
msgstr "" msgstr ""
#: src/License/PluginLicenseChecker.php:117
msgid "License settings not configured."
msgstr ""
#: src/License/PluginLicenseChecker.php:153
msgid "Could not connect to license server."
msgstr ""
#: src/Product/LicensedProductType.php:55 #: src/Product/LicensedProductType.php:55
msgid "Licensed Product" msgid "Licensed Product"
msgstr "" msgstr ""
@@ -1065,7 +1148,6 @@ msgstr ""
msgid "%d days" msgid "%d days"
msgstr "" msgstr ""
#. translators: %s: URL to settings page
#: src/Product/LicensedProductType.php:113 #: src/Product/LicensedProductType.php:113
#, php-format #, php-format
msgid "Leave fields empty to use default settings from %s." msgid "Leave fields empty to use default settings from %s."
@@ -1079,7 +1161,6 @@ msgstr ""
msgid "Max Activations" msgid "Max Activations"
msgstr "" msgstr ""
#. translators: %d: default max activations value
#: src/Product/LicensedProductType.php:125 #: src/Product/LicensedProductType.php:125
#, php-format #, php-format
msgid "Maximum number of domain activations per license. Default: %d" msgid "Maximum number of domain activations per license. Default: %d"
@@ -1089,7 +1170,6 @@ msgstr ""
msgid "License Validity (Days)" msgid "License Validity (Days)"
msgstr "" msgstr ""
#. translators: %s: default validity value
#: src/Product/LicensedProductType.php:143 #: src/Product/LicensedProductType.php:143
#, php-format #, php-format
msgid "Number of days the license is valid. Leave empty for default (%s)." msgid "Number of days the license is valid. Leave empty for default (%s)."
@@ -1099,7 +1179,6 @@ msgstr ""
msgid "Bind to Major Version" msgid "Bind to Major Version"
msgstr "" msgstr ""
#. translators: %s: default bind to version value (Yes/No)
#: src/Product/LicensedProductType.php:161 #: src/Product/LicensedProductType.php:161
#, php-format #, php-format
msgid "" msgid ""
@@ -1119,7 +1198,6 @@ msgstr ""
msgid "Attachment file not found." msgid "Attachment file not found."
msgstr "" msgstr ""
#. translators: 1: provided hash, 2: calculated hash
#: src/Product/VersionManager.php:177 #: src/Product/VersionManager.php:177
#, php-format #, php-format
msgid "File checksum does not match. Expected: %1$s, Got: %2$s" msgid "File checksum does not match. Expected: %1$s, Got: %2$s"
@@ -1180,11 +1258,11 @@ msgstr ""
msgid "Please log in to view your licenses." msgid "Please log in to view your licenses."
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:164 #: src/Frontend/AccountController.php:165
msgid "You have no licenses yet." msgid "You have no licenses yet."
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:189 #: src/Frontend/AccountController.php:190
#: src/Email/LicenseEmailController.php:173 #: src/Email/LicenseEmailController.php:173
#: src/Email/LicenseEmailController.php:177 #: src/Email/LicenseEmailController.php:177
#: src/Email/LicenseEmailController.php:281 #: src/Email/LicenseEmailController.php:281
@@ -1193,106 +1271,106 @@ msgstr ""
msgid "License Key:" msgid "License Key:"
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:200 #: src/Frontend/AccountController.php:201
#: src/Email/LicenseExpirationEmail.php:215 #: src/Email/LicenseExpirationEmail.php:215
#: src/Email/LicenseExpirationEmail.php:271 #: src/Email/LicenseExpirationEmail.php:271
msgid "Domain:" msgid "Domain:"
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:206 #: src/Frontend/AccountController.php:207
msgid "Transfer to new domain" msgid "Transfer to new domain"
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:208 #: src/Frontend/AccountController.php:209
msgid "Transfer" msgid "Transfer"
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:212 #: src/Frontend/AccountController.php:213
#: src/Email/LicenseEmailController.php:284 #: src/Email/LicenseEmailController.php:284
#: src/Email/LicenseExpirationEmail.php:219 #: src/Email/LicenseExpirationEmail.php:219
#: src/Email/LicenseExpirationEmail.php:272 #: src/Email/LicenseExpirationEmail.php:272
msgid "Expires:" msgid "Expires:"
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:217 #: src/Frontend/AccountController.php:218
#: src/Email/LicenseEmailController.php:248 #: src/Email/LicenseEmailController.php:248
#: src/Email/LicenseEmailController.php:287 #: src/Email/LicenseEmailController.php:287
msgid "Never" msgid "Never"
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:225 #: src/Frontend/AccountController.php:226
msgid "Available Downloads" msgid "Available Downloads"
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:231 #: src/Frontend/AccountController.php:232
#, php-format #, php-format
msgid "Version %s" msgid "Version %s"
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:248 #: src/Frontend/AccountController.php:249
msgid "Close" msgid "Close"
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:249 #: src/Frontend/AccountController.php:250
msgid "Transfer License to New Domain" msgid "Transfer License to New Domain"
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:254 #: src/Frontend/AccountController.php:255
msgid "Current Domain" msgid "Current Domain"
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:259 #: src/Frontend/AccountController.php:260
msgid "New Domain" msgid "New Domain"
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:263 #: src/Frontend/AccountController.php:264
msgid "Enter the new domain without http:// or www." msgid "Enter the new domain without http:// or www."
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:268 #: src/Frontend/AccountController.php:269
msgid "Transfer License" msgid "Transfer License"
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:310 #: src/Frontend/AccountController.php:311
#: src/Frontend/AccountController.php:377 #: src/Frontend/AccountController.php:378
msgid "License transferred successfully!" msgid "License transferred successfully!"
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:311 #: src/Frontend/AccountController.php:312
msgid "Transfer failed. Please try again." msgid "Transfer failed. Please try again."
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:312 #: src/Frontend/AccountController.php:313
msgid "" msgid ""
"Are you sure you want to transfer this license to a new domain? This action " "Are you sure you want to transfer this license to a new domain? This action "
"cannot be undone." "cannot be undone."
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:331 #: src/Frontend/AccountController.php:332
msgid "Please log in to transfer a license." msgid "Please log in to transfer a license."
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:337 #: src/Frontend/AccountController.php:338
msgid "Invalid license." msgid "Invalid license."
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:355 #: src/Frontend/AccountController.php:356
msgid "You do not have permission to transfer this license." msgid "You do not have permission to transfer this license."
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:360 #: src/Frontend/AccountController.php:361
msgid "Revoked licenses cannot be transferred." msgid "Revoked licenses cannot be transferred."
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:364 #: src/Frontend/AccountController.php:365
msgid "Expired licenses cannot be transferred." msgid "Expired licenses cannot be transferred."
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:369 #: src/Frontend/AccountController.php:370
msgid "The new domain is the same as the current domain." msgid "The new domain is the same as the current domain."
msgstr "" msgstr ""
#: src/Frontend/AccountController.php:381 #: src/Frontend/AccountController.php:382
msgid "Failed to transfer license. Please try again." msgid "Failed to transfer license. Please try again."
msgstr "" msgstr ""
@@ -1373,7 +1451,6 @@ msgid ""
"expiration date." "expiration date."
msgstr "" msgstr ""
#. translators: %s: list of placeholders
#: src/Email/LicenseExpirationEmail.php:301 #: src/Email/LicenseExpirationEmail.php:301
#, php-format #, php-format
msgid "Available placeholders: %s" msgid "Available placeholders: %s"
@@ -1411,7 +1488,19 @@ msgstr ""
msgid "Choose which format of email to send." msgid "Choose which format of email to send."
msgstr "" msgstr ""
#. translators: %s: WooCommerce plugin name #: src/Plugin.php:255
msgid "WC Licensed Product"
msgstr ""
#: src/Plugin.php:256
msgid ""
"Plugin license is not configured or invalid. Frontend features are disabled."
msgstr ""
#: src/Plugin.php:257
msgid "Configure License"
msgstr ""
#: wc-licensed-product.php:61 #: wc-licensed-product.php:61
#, php-format #, php-format
msgid "%s requires WooCommerce to be installed and active." msgid "%s requires WooCommerce to be installed and active."

View File

@@ -2,8 +2,8 @@
"openapi": "3.1.0", "openapi": "3.1.0",
"info": { "info": {
"title": "WooCommerce Licensed Product API", "title": "WooCommerce Licensed Product API",
"description": "REST API for validating and managing software licenses bound to domains. This API allows external applications to validate license keys, check license status, and activate licenses on specific domains.", "description": "REST API for validating and managing software licenses bound to domains. This API allows external applications to validate license keys, check license status, and activate licenses on specific domains.\n\n## Response Signing (Optional)\n\nWhen the server is configured with `WC_LICENSE_SERVER_SECRET`, all API responses include cryptographic signatures for tamper protection:\n\n- `X-License-Signature`: HMAC-SHA256 signature of the response\n- `X-License-Timestamp`: Unix timestamp when the response was generated\n\nSignature verification prevents man-in-the-middle attacks and ensures response integrity. Use the `magdev/wc-licensed-product-client` library's `SecureLicenseClient` class to automatically verify signatures.",
"version": "0.0.7", "version": "0.3.2",
"contact": { "contact": {
"name": "Marco Graetsch", "name": "Marco Graetsch",
"url": "https://src.bundespruefstelle.ch/magdev", "url": "https://src.bundespruefstelle.ch/magdev",
@@ -55,6 +55,14 @@
"responses": { "responses": {
"200": { "200": {
"description": "License is valid for the specified domain", "description": "License is valid for the specified domain",
"headers": {
"X-License-Signature": {
"$ref": "#/components/headers/X-License-Signature"
},
"X-License-Timestamp": {
"$ref": "#/components/headers/X-License-Timestamp"
}
},
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -156,6 +164,14 @@
"responses": { "responses": {
"200": { "200": {
"description": "License status retrieved successfully", "description": "License status retrieved successfully",
"headers": {
"X-License-Signature": {
"$ref": "#/components/headers/X-License-Signature"
},
"X-License-Timestamp": {
"$ref": "#/components/headers/X-License-Timestamp"
}
},
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -221,6 +237,14 @@
"responses": { "responses": {
"200": { "200": {
"description": "License activated successfully or already activated", "description": "License activated successfully or already activated",
"headers": {
"X-License-Signature": {
"$ref": "#/components/headers/X-License-Signature"
},
"X-License-Timestamp": {
"$ref": "#/components/headers/X-License-Timestamp"
}
},
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -519,6 +543,26 @@
} }
} }
} }
},
"headers": {
"X-License-Signature": {
"description": "HMAC-SHA256 signature of the response body for tamper protection. Only present when server is configured with WC_LICENSE_SERVER_SECRET. Signature format: hex-encoded HMAC-SHA256 of (timestamp + ':' + canonical_json_body) using a per-license derived key.",
"schema": {
"type": "string",
"pattern": "^[a-f0-9]{64}$",
"example": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"
},
"required": false
},
"X-License-Timestamp": {
"description": "Unix timestamp when the response was generated. Used together with X-License-Signature to prevent replay attacks. Only present when server is configured with WC_LICENSE_SERVER_SECRET.",
"schema": {
"type": "string",
"pattern": "^[0-9]+$",
"example": "1737550000"
},
"required": false
}
} }
}, },
"tags": [ "tags": [

Binary file not shown.

View File

@@ -0,0 +1 @@
7b895090538f9063fac1509b6f7a40a2b71dc9958b3a255cbfcc60d0320ae5e5 releases/wc-licensed-product-0.2.1.zip

View File

@@ -9,6 +9,8 @@ declare(strict_types=1);
namespace Jeremias\WcLicensedProduct\Admin; namespace Jeremias\WcLicensedProduct\Admin;
use Jeremias\WcLicensedProduct\License\PluginLicenseChecker;
/** /**
* Handles WooCommerce settings tab for license defaults * Handles WooCommerce settings tab for license defaults
*/ */
@@ -19,6 +21,11 @@ final class SettingsController
*/ */
public const OPTION_NAME = 'wc_licensed_product_settings'; public const OPTION_NAME = 'wc_licensed_product_settings';
/**
* Tab ID
*/
private const TAB_ID = 'licensed_product';
/** /**
* Constructor * Constructor
*/ */
@@ -33,8 +40,10 @@ final class SettingsController
private function registerHooks(): void private function registerHooks(): void
{ {
add_filter('woocommerce_settings_tabs_array', [$this, 'addSettingsTab'], 50); add_filter('woocommerce_settings_tabs_array', [$this, 'addSettingsTab'], 50);
add_action('woocommerce_settings_tabs_licensed_product', [$this, 'renderSettingsTab']); add_action('woocommerce_sections_' . self::TAB_ID, [$this, 'outputSections']);
add_action('woocommerce_update_options_licensed_product', [$this, 'saveSettings']); add_action('woocommerce_settings_' . self::TAB_ID, [$this, 'renderSettingsTab']);
add_action('woocommerce_update_options_' . self::TAB_ID, [$this, 'saveSettings']);
add_action('wp_ajax_wclp_verify_plugin_license', [$this, 'handleVerifyLicense']);
} }
/** /**
@@ -42,14 +51,119 @@ final class SettingsController
*/ */
public function addSettingsTab(array $tabs): array public function addSettingsTab(array $tabs): array
{ {
$tabs['licensed_product'] = __('Licensed Products', 'wc-licensed-product'); $tabs[self::TAB_ID] = __('Licensed Products', 'wc-licensed-product');
return $tabs; return $tabs;
} }
/** /**
* Get settings fields * Get available sections
*/
public function getSections(): array
{
return [
'' => __('Plugin License', 'wc-licensed-product'),
'defaults' => __('Default Settings', 'wc-licensed-product'),
'notifications' => __('Notifications', 'wc-licensed-product'),
];
}
/**
* Get current section from URL
*/
private function getCurrentSection(): string
{
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
return isset($_GET['section']) ? sanitize_title(wp_unslash($_GET['section'])) : '';
}
/**
* Output sections navigation (sub-tabs)
*/
public function outputSections(): void
{
$sections = $this->getSections();
if (empty($sections) || count($sections) <= 1) {
return;
}
$currentSection = $this->getCurrentSection();
echo '<ul class="subsubsub">';
$arrayKeys = array_keys($sections);
foreach ($sections as $id => $label) {
$url = admin_url('admin.php?page=wc-settings&tab=' . self::TAB_ID . '&section=' . sanitize_title($id));
$class = ($currentSection === $id) ? 'current' : '';
$separator = (end($arrayKeys) === $id) ? '' : ' | ';
echo '<li><a href="' . esc_url($url) . '" class="' . esc_attr($class) . '">' . esc_html($label) . '</a>' . $separator . '</li>';
}
echo '</ul><br class="clear" />';
}
/**
* Get settings fields for the current section
*/ */
public function getSettingsFields(): array public function getSettingsFields(): array
{
$currentSection = $this->getCurrentSection();
return match ($currentSection) {
'defaults' => $this->getDefaultsSettings(),
'notifications' => $this->getNotificationsSettings(),
default => $this->getPluginLicenseSettings(),
};
}
/**
* Get plugin license settings (default section)
*/
private function getPluginLicenseSettings(): array
{
return [
'plugin_license_section_title' => [
'name' => __('Plugin License', 'wc-licensed-product'),
'type' => 'title',
'desc' => __('Configure the license for this plugin. A valid license is required for frontend features to work.', 'wc-licensed-product'),
'id' => 'wc_licensed_product_section_plugin_license',
],
'plugin_license_server_url' => [
'name' => __('License Server URL', 'wc-licensed-product'),
'type' => 'url',
'desc' => __('The URL of the license server (e.g., https://shop.example.com).', 'wc-licensed-product'),
'id' => 'wc_licensed_product_plugin_license_server_url',
'default' => '',
'placeholder' => 'https://shop.example.com',
],
'plugin_license_key' => [
'name' => __('License Key', 'wc-licensed-product'),
'type' => 'text',
'desc' => __('Your license key in XXXX-XXXX-XXXX-XXXX format.', 'wc-licensed-product'),
'id' => 'wc_licensed_product_plugin_license_key',
'default' => '',
'placeholder' => 'XXXX-XXXX-XXXX-XXXX',
],
'plugin_license_server_secret' => [
'name' => __('Server Secret (Optional)', 'wc-licensed-product'),
'type' => 'password',
'desc' => __('If the license server uses signed responses, enter the shared secret here for enhanced security.', 'wc-licensed-product'),
'id' => 'wc_licensed_product_plugin_license_server_secret',
'default' => '',
],
'plugin_license_section_end' => [
'type' => 'sectionend',
'id' => 'wc_licensed_product_section_plugin_license_end',
],
];
}
/**
* Get default license settings
*/
private function getDefaultsSettings(): array
{ {
return [ return [
'section_title' => [ 'section_title' => [
@@ -92,7 +206,15 @@ final class SettingsController
'type' => 'sectionend', 'type' => 'sectionend',
'id' => 'wc_licensed_product_section_defaults_end', 'id' => 'wc_licensed_product_section_defaults_end',
], ],
// Email settings section ];
}
/**
* Get notifications settings
*/
private function getNotificationsSettings(): array
{
return [
'email_section_title' => [ 'email_section_title' => [
'name' => __('Expiration Warning Schedule', 'wc-licensed-product'), 'name' => __('Expiration Warning Schedule', 'wc-licensed-product'),
'type' => 'title', 'type' => 'title',
@@ -138,9 +260,96 @@ final class SettingsController
*/ */
public function renderSettingsTab(): void public function renderSettingsTab(): void
{ {
$currentSection = $this->getCurrentSection();
// Only show license status on the plugin license section
if ($currentSection === '') {
$this->renderLicenseStatus();
}
woocommerce_admin_fields($this->getSettingsFields()); woocommerce_admin_fields($this->getSettingsFields());
} }
/**
* Render license status notice
*/
private function renderLicenseStatus(): void
{
$checker = PluginLicenseChecker::getInstance();
if ($checker->isLocalhost()) {
echo '<div class="notice notice-info inline"><p>';
echo '<span class="dashicons dashicons-info" style="color: #00a0d2;"></span> ';
echo esc_html__('Running on localhost - license validation bypassed.', 'wc-licensed-product');
echo '</p></div>';
return;
}
if ($checker->isLicenseValid()) {
echo '<div class="notice notice-success inline"><p>';
echo '<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span> ';
echo esc_html__('License is valid and active.', 'wc-licensed-product');
echo '</p></div>';
} else {
$error = $checker->getLastError();
echo '<div class="notice notice-error inline"><p>';
echo '<span class="dashicons dashicons-warning" style="color: #dc3232;"></span> ';
echo esc_html__('License is not valid. Frontend features are disabled.', 'wc-licensed-product');
if ($error) {
echo '<br><small>' . esc_html($error) . '</small>';
}
echo '</p></div>';
}
// Add verify button
$nonce = wp_create_nonce('wclp_verify_license');
echo '<p>';
echo '<button type="button" class="button" id="wclp-verify-license" data-nonce="' . esc_attr($nonce) . '">';
echo esc_html__('Verify License', 'wc-licensed-product');
echo '</button>';
echo '<span id="wclp-verify-result" style="margin-left: 10px;"></span>';
echo '</p>';
// Inline script for verify button
?>
<script type="text/javascript">
jQuery(function($) {
$('#wclp-verify-license').on('click', function() {
var $btn = $(this);
var $result = $('#wclp-verify-result');
var nonce = $btn.data('nonce');
$btn.prop('disabled', true).text('<?php echo esc_js(__('Verifying...', 'wc-licensed-product')); ?>');
$result.text('');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'wclp_verify_plugin_license',
nonce: nonce
},
success: function(response) {
if (response.success) {
$result.html('<span style="color: #46b450;">' + response.data.message + '</span>');
location.reload();
} else {
$result.html('<span style="color: #dc3232;">' + response.data.message + '</span>');
}
},
error: function() {
$result.html('<span style="color: #dc3232;"><?php echo esc_js(__('Request failed.', 'wc-licensed-product')); ?></span>');
},
complete: function() {
$btn.prop('disabled', false).text('<?php echo esc_js(__('Verify License', 'wc-licensed-product')); ?>');
}
});
});
});
</script>
<?php
}
/** /**
* Save settings * Save settings
*/ */
@@ -210,4 +419,55 @@ final class SettingsController
$value = get_option('wc_licensed_product_expiration_warning_days_second', 1); $value = get_option('wc_licensed_product_expiration_warning_days_second', 1);
return max(0, (int) $value); return max(0, (int) $value);
} }
/**
* Get plugin license server URL
*/
public static function getPluginLicenseServerUrl(): string
{
return (string) get_option('wc_licensed_product_plugin_license_server_url', '');
}
/**
* Get plugin license key
*/
public static function getPluginLicenseKey(): string
{
return (string) get_option('wc_licensed_product_plugin_license_key', '');
}
/**
* Get plugin license server secret
*/
public static function getPluginLicenseServerSecret(): ?string
{
$secret = get_option('wc_licensed_product_plugin_license_server_secret', '');
return !empty($secret) ? (string) $secret : null;
}
/**
* Handle AJAX verify license request
*/
public function handleVerifyLicense(): void
{
if (!check_ajax_referer('wclp_verify_license', 'nonce', false)) {
wp_send_json_error(['message' => __('Security check failed.', 'wc-licensed-product')], 403);
}
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(['message' => __('Insufficient permissions.', 'wc-licensed-product')], 403);
}
$checker = PluginLicenseChecker::getInstance();
$checker->clearCache();
$valid = $checker->validateLicense(true);
if ($valid) {
wp_send_json_success(['message' => __('License verified successfully!', 'wc-licensed-product')]);
} else {
$error = $checker->getLastError() ?: __('License validation failed.', 'wc-licensed-product');
wp_send_json_error(['message' => $error]);
}
}
} }

View File

@@ -101,7 +101,14 @@ final class VersionAdminController
<tr id="sha256-hash-row" style="display: none;"> <tr id="sha256-hash-row" style="display: none;">
<th><label for="new_checksum_file"><?php esc_html_e('Checksum File', 'wc-licensed-product'); ?></label></th> <th><label for="new_checksum_file"><?php esc_html_e('Checksum File', 'wc-licensed-product'); ?></label></th>
<td> <td>
<input type="file" id="new_checksum_file" name="new_checksum_file" accept=".sha256,.txt" /> <input type="file" id="new_checksum_file" name="new_checksum_file" accept=".sha256,.txt" style="display: none;" />
<span id="selected_checksum_name" class="selected-file-name"></span>
<button type="button" class="button" id="select-checksum-file-btn">
<?php esc_html_e('Select Checksum File', 'wc-licensed-product'); ?>
</button>
<button type="button" class="button" id="remove-checksum-file-btn" style="display: none;">
<?php esc_html_e('Remove', 'wc-licensed-product'); ?>
</button>
<p class="description"><?php esc_html_e('Upload a SHA256 checksum file (.sha256 or .txt) to verify file integrity.', 'wc-licensed-product'); ?></p> <p class="description"><?php esc_html_e('Upload a SHA256 checksum file (.sha256 or .txt) to verify file integrity.', 'wc-licensed-product'); ?></p>
</td> </td>
</tr> </tr>
@@ -128,6 +135,7 @@ final class VersionAdminController
<tr> <tr>
<th><?php esc_html_e('Version', 'wc-licensed-product'); ?></th> <th><?php esc_html_e('Version', 'wc-licensed-product'); ?></th>
<th><?php esc_html_e('Download File', 'wc-licensed-product'); ?></th> <th><?php esc_html_e('Download File', 'wc-licensed-product'); ?></th>
<th><?php esc_html_e('SHA256', 'wc-licensed-product'); ?></th>
<th><?php esc_html_e('Release Notes', 'wc-licensed-product'); ?></th> <th><?php esc_html_e('Release Notes', 'wc-licensed-product'); ?></th>
<th><?php esc_html_e('Status', 'wc-licensed-product'); ?></th> <th><?php esc_html_e('Status', 'wc-licensed-product'); ?></th>
<th><?php esc_html_e('Released', 'wc-licensed-product'); ?></th> <th><?php esc_html_e('Released', 'wc-licensed-product'); ?></th>
@@ -137,7 +145,7 @@ final class VersionAdminController
<tbody> <tbody>
<?php if (empty($versions)): ?> <?php if (empty($versions)): ?>
<tr class="no-versions"> <tr class="no-versions">
<td colspan="6"><?php esc_html_e('No versions found. Add your first version above.', 'wc-licensed-product'); ?></td> <td colspan="7"><?php esc_html_e('No versions found. Add your first version above.', 'wc-licensed-product'); ?></td>
</tr> </tr>
<?php else: ?> <?php else: ?>
<?php foreach ($versions as $version): ?> <?php foreach ($versions as $version): ?>
@@ -149,16 +157,25 @@ final class VersionAdminController
$filename = $version->getDownloadFilename(); $filename = $version->getDownloadFilename();
if ($effectiveUrl): if ($effectiveUrl):
?> ?>
<a href="<?php echo esc_url($effectiveUrl); ?>" target="_blank"> <span class="version-download-link">
<?php echo esc_html($filename ?: wp_basename($effectiveUrl)); ?> <a href="<?php echo esc_url($effectiveUrl); ?>" target="_blank">
</a> <?php echo esc_html($filename ?: wp_basename($effectiveUrl)); ?>
<?php if ($version->getAttachmentId()): ?> </a>
<span class="dashicons dashicons-media-archive" title="<?php esc_attr_e('Uploaded file', 'wc-licensed-product'); ?>"></span> <?php if ($version->getAttachmentId()): ?>
<?php endif; ?> <span class="dashicons dashicons-media-archive" title="<?php esc_attr_e('Uploaded file', 'wc-licensed-product'); ?>"></span>
<?php endif; ?>
</span>
<?php else: ?> <?php else: ?>
<em><?php esc_html_e('No download file', 'wc-licensed-product'); ?></em> <em><?php esc_html_e('No download file', 'wc-licensed-product'); ?></em>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td>
<?php if ($version->getFileHash()): ?>
<code class="file-hash" title="<?php echo esc_attr($version->getFileHash()); ?>"><?php echo esc_html(substr($version->getFileHash(), 0, 12)); ?>...</code>
<?php else: ?>
<em>—</em>
<?php endif; ?>
</td>
<td><?php echo esc_html($version->getReleaseNotes() ? wp_trim_words($version->getReleaseNotes(), 10) : '—'); ?></td> <td><?php echo esc_html($version->getReleaseNotes() ? wp_trim_words($version->getReleaseNotes(), 10) : '—'); ?></td>
<td> <td>
<span class="version-status version-status-<?php echo $version->isActive() ? 'active' : 'inactive'; ?>"> <span class="version-status version-status-<?php echo $version->isActive() ? 'active' : 'inactive'; ?>">
@@ -371,16 +388,25 @@ final class VersionAdminController
$filename = $version->getDownloadFilename(); $filename = $version->getDownloadFilename();
if ($effectiveUrl): if ($effectiveUrl):
?> ?>
<a href="<?php echo esc_url($effectiveUrl); ?>" target="_blank"> <span class="version-download-link">
<?php echo esc_html($filename ?: wp_basename($effectiveUrl)); ?> <a href="<?php echo esc_url($effectiveUrl); ?>" target="_blank">
</a> <?php echo esc_html($filename ?: wp_basename($effectiveUrl)); ?>
<?php if ($version->getAttachmentId()): ?> </a>
<span class="dashicons dashicons-media-archive" title="<?php esc_attr_e('Uploaded file', 'wc-licensed-product'); ?>"></span> <?php if ($version->getAttachmentId()): ?>
<?php endif; ?> <span class="dashicons dashicons-media-archive" title="<?php esc_attr_e('Uploaded file', 'wc-licensed-product'); ?>"></span>
<?php endif; ?>
</span>
<?php else: ?> <?php else: ?>
<em><?php esc_html_e('No download file', 'wc-licensed-product'); ?></em> <em><?php esc_html_e('No download file', 'wc-licensed-product'); ?></em>
<?php endif; ?> <?php endif; ?>
</td> </td>
<td>
<?php if ($version->getFileHash()): ?>
<code class="file-hash" title="<?php echo esc_attr($version->getFileHash()); ?>"><?php echo esc_html(substr($version->getFileHash(), 0, 12)); ?>...</code>
<?php else: ?>
<em>—</em>
<?php endif; ?>
</td>
<td><?php echo esc_html($version->getReleaseNotes() ? wp_trim_words($version->getReleaseNotes(), 10) : '—'); ?></td> <td><?php echo esc_html($version->getReleaseNotes() ? wp_trim_words($version->getReleaseNotes(), 10) : '—'); ?></td>
<td> <td>
<span class="version-status version-status-<?php echo $version->isActive() ? 'active' : 'inactive'; ?>"> <span class="version-status version-status-<?php echo $version->isActive() ? 'active' : 'inactive'; ?>">

View File

@@ -129,6 +129,7 @@ final class AccountController
), ),
'release_notes' => $version->getReleaseNotes(), 'release_notes' => $version->getReleaseNotes(),
'released_at' => $version->getReleasedAt()->format(get_option('date_format')), 'released_at' => $version->getReleasedAt()->format(get_option('date_format')),
'file_hash' => $version->getFileHash(),
]; ];
} }
} }

View File

@@ -0,0 +1,287 @@
<?php
/**
* Plugin License Checker
*
* Validates the plugin's own license against a remote server.
*
* @package Jeremias\WcLicensedProduct\License
*/
declare(strict_types=1);
namespace Jeremias\WcLicensedProduct\License;
use Magdev\WcLicensedProductClient\LicenseClient;
use Magdev\WcLicensedProductClient\LicenseClientInterface;
use Magdev\WcLicensedProductClient\SecureLicenseClient;
use Magdev\WcLicensedProductClient\Exception\LicenseException;
use Symfony\Component\HttpClient\HttpClient;
/**
* Handles validation of this plugin's license
*/
final class PluginLicenseChecker
{
/**
* Cache key for license validation result
*/
private const CACHE_KEY = 'wclp_plugin_license_valid';
/**
* Cache TTL for successful validation (1 hour)
*/
private const CACHE_TTL = 3600;
/**
* Cache key for error messages
*/
private const ERROR_CACHE_KEY = 'wclp_plugin_license_error';
/**
* Cache TTL for errors (5 minutes)
*/
private const ERROR_CACHE_TTL = 300;
/**
* Singleton instance
*/
private static ?self $instance = null;
/**
* Cached localhost check result
*/
private ?bool $isLocalhostCached = null;
/**
* Get singleton instance
*/
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Private constructor for singleton
*/
private function __construct()
{
// Private constructor
}
/**
* Check if the plugin license is valid
*
* Returns cached result if available, otherwise validates against server.
*/
public function isLicenseValid(): bool
{
// Always valid on localhost
if ($this->isLocalhost()) {
return true;
}
// Check cache first
$cached = get_transient(self::CACHE_KEY);
if ($cached !== false) {
return (bool) $cached;
}
// Validate against server
return $this->validateLicense();
}
/**
* Validate license against the server
*
* @param bool $forceRefresh Force refresh even if cached
* @return bool True if license is valid
*/
public function validateLicense(bool $forceRefresh = false): bool
{
// Always valid on localhost
if ($this->isLocalhost()) {
return true;
}
// Check settings are configured
$serverUrl = $this->getLicenseServerUrl();
$licenseKey = $this->getLicenseKey();
if (empty($serverUrl) || empty($licenseKey)) {
set_transient(
self::ERROR_CACHE_KEY,
__('License settings not configured.', 'wc-licensed-product'),
self::ERROR_CACHE_TTL
);
return false;
}
// Check cache unless force refresh
if (!$forceRefresh) {
$cached = get_transient(self::CACHE_KEY);
if ($cached !== false) {
return (bool) $cached;
}
}
try {
$client = $this->createLicenseClient();
$domain = $this->getCurrentDomain();
// Validate the license
$client->validate($licenseKey, $domain);
// Valid license - cache success
set_transient(self::CACHE_KEY, 1, self::CACHE_TTL);
delete_transient(self::ERROR_CACHE_KEY);
return true;
} catch (LicenseException $e) {
// License-specific error (invalid, expired, revoked, etc.)
set_transient(self::CACHE_KEY, 0, self::CACHE_TTL);
set_transient(self::ERROR_CACHE_KEY, $e->getMessage(), self::ERROR_CACHE_TTL);
return false;
} catch (\Throwable $e) {
// Network/server error - use shorter cache to allow retry
set_transient(
self::ERROR_CACHE_KEY,
__('Could not connect to license server.', 'wc-licensed-product') . ' ' . $e->getMessage(),
self::ERROR_CACHE_TTL
);
// Don't cache validation failure on network errors - allow retry on next page load
return false;
}
}
/**
* Get the last error message
*/
public function getLastError(): ?string
{
$error = get_transient(self::ERROR_CACHE_KEY);
return $error !== false ? (string) $error : null;
}
/**
* Clear the validation cache
*/
public function clearCache(): void
{
delete_transient(self::CACHE_KEY);
delete_transient(self::ERROR_CACHE_KEY);
$this->isLocalhostCached = null;
}
/**
* Check if running on localhost
*
* Matches localhost, 127.0.0.1, ::1, and any port number.
*/
public function isLocalhost(): bool
{
if ($this->isLocalhostCached !== null) {
return $this->isLocalhostCached;
}
$domain = $this->getCurrentDomain();
// Remove port number if present
$domainWithoutPort = preg_replace('/:[\d]+$/', '', $domain);
// Check for localhost variants
$localhostNames = ['localhost', '127.0.0.1', '::1'];
if (in_array($domainWithoutPort, $localhostNames, true)) {
$this->isLocalhostCached = true;
return true;
}
// Check for .localhost and .local subdomains
if (
str_ends_with($domainWithoutPort, '.localhost') ||
str_ends_with($domainWithoutPort, '.local')
) {
$this->isLocalhostCached = true;
return true;
}
$this->isLocalhostCached = false;
return false;
}
/**
* Get the current domain from the site URL
*/
private function getCurrentDomain(): string
{
$siteUrl = get_site_url();
$parsed = parse_url($siteUrl);
$host = $parsed['host'] ?? 'localhost';
// Include port if non-standard
if (isset($parsed['port'])) {
$host .= ':' . $parsed['port'];
}
return strtolower($host);
}
/**
* Get the license server URL from settings
*/
private function getLicenseServerUrl(): string
{
return (string) get_option('wc_licensed_product_plugin_license_server_url', '');
}
/**
* Get the license key from settings
*/
private function getLicenseKey(): string
{
return (string) get_option('wc_licensed_product_plugin_license_key', '');
}
/**
* Get the server secret from settings (optional)
*/
private function getServerSecret(): ?string
{
$secret = get_option('wc_licensed_product_plugin_license_server_secret', '');
return !empty($secret) ? (string) $secret : null;
}
/**
* Create the license client instance
*/
private function createLicenseClient(): LicenseClientInterface
{
$httpClient = HttpClient::create([
'timeout' => 10,
'verify_peer' => true,
]);
$serverUrl = $this->getLicenseServerUrl();
$serverSecret = $this->getServerSecret();
// Use secure client if server secret is configured
if ($serverSecret !== null) {
return new SecureLicenseClient(
httpClient: $httpClient,
baseUrl: $serverUrl,
serverSecret: $serverSecret,
);
}
return new LicenseClient(
httpClient: $httpClient,
baseUrl: $serverUrl,
);
}
}

View File

@@ -22,6 +22,7 @@ use Jeremias\WcLicensedProduct\Email\LicenseEmailController;
use Jeremias\WcLicensedProduct\Frontend\AccountController; use Jeremias\WcLicensedProduct\Frontend\AccountController;
use Jeremias\WcLicensedProduct\Frontend\DownloadController; use Jeremias\WcLicensedProduct\Frontend\DownloadController;
use Jeremias\WcLicensedProduct\License\LicenseManager; use Jeremias\WcLicensedProduct\License\LicenseManager;
use Jeremias\WcLicensedProduct\License\PluginLicenseChecker;
use Jeremias\WcLicensedProduct\Product\LicensedProductType; use Jeremias\WcLicensedProduct\Product\LicensedProductType;
use Jeremias\WcLicensedProduct\Product\VersionManager; use Jeremias\WcLicensedProduct\Product\VersionManager;
use Twig\Environment; use Twig\Environment;
@@ -119,13 +120,23 @@ final class Plugin
$this->licenseManager = new LicenseManager(); $this->licenseManager = new LicenseManager();
$this->versionManager = new VersionManager(); $this->versionManager = new VersionManager();
// Initialize controllers // Check plugin license
$licenseChecker = PluginLicenseChecker::getInstance();
$isLicensed = $licenseChecker->isLicenseValid();
// Always initialize product type (needed for existing orders)
new LicensedProductType(); new LicensedProductType();
new CheckoutController($this->licenseManager);
new StoreApiExtension($this->licenseManager); // Only initialize frontend components if licensed or on localhost
$this->registerCheckoutBlocksIntegration(); if ($isLicensed) {
$this->downloadController = new DownloadController($this->licenseManager, $this->versionManager); new CheckoutController($this->licenseManager);
new AccountController($this->twig, $this->licenseManager, $this->versionManager, $this->downloadController); new StoreApiExtension($this->licenseManager);
$this->registerCheckoutBlocksIntegration();
$this->downloadController = new DownloadController($this->licenseManager, $this->versionManager);
new AccountController($this->twig, $this->licenseManager, $this->versionManager, $this->downloadController);
}
// Always initialize REST API and email controller
new RestApiController($this->licenseManager); new RestApiController($this->licenseManager);
new LicenseEmailController($this->licenseManager); new LicenseEmailController($this->licenseManager);
@@ -134,11 +145,17 @@ final class Plugin
(new ResponseSigner())->register(); (new ResponseSigner())->register();
} }
// Admin always available
if (is_admin()) { if (is_admin()) {
new AdminController($this->twig, $this->licenseManager); new AdminController($this->twig, $this->licenseManager);
new VersionAdminController($this->versionManager); new VersionAdminController($this->versionManager);
new OrderLicenseController($this->licenseManager); new OrderLicenseController($this->licenseManager);
new SettingsController(); new SettingsController();
// Show admin notice if unlicensed and not on localhost
if (!$isLicensed && !$licenseChecker->isLocalhost()) {
add_action('admin_notices', [$this, 'showUnlicensedNotice']);
}
} }
} }
@@ -164,12 +181,16 @@ final class Plugin
*/ */
private function registerHooks(): void private function registerHooks(): void
{ {
// Generate license on order completion (multiple hooks for compatibility) // Only register order hooks if licensed (license generation requires valid license)
add_action('woocommerce_order_status_completed', [$this, 'onOrderCompleted']); $licenseChecker = PluginLicenseChecker::getInstance();
add_action('woocommerce_order_status_processing', [$this, 'onOrderCompleted']); if ($licenseChecker->isLicenseValid()) {
// Generate license on order completion (multiple hooks for compatibility)
add_action('woocommerce_order_status_completed', [$this, 'onOrderCompleted']);
add_action('woocommerce_order_status_processing', [$this, 'onOrderCompleted']);
// Also hook into payment complete for immediate license generation // Also hook into payment complete for immediate license generation
add_action('woocommerce_payment_complete', [$this, 'onOrderCompleted']); add_action('woocommerce_payment_complete', [$this, 'onOrderCompleted']);
}
} }
/** /**
@@ -221,4 +242,29 @@ final class Plugin
{ {
return $this->twig->render($template, $context); return $this->twig->render($template, $context);
} }
/**
* Show admin notice when plugin is unlicensed
*/
public function showUnlicensedNotice(): void
{
$settingsUrl = admin_url('admin.php?page=wc-settings&tab=licensed_product');
?>
<div class="notice notice-warning is-dismissible">
<p>
<strong><?php esc_html_e('WC Licensed Product', 'wc-licensed-product'); ?>:</strong>
<?php esc_html_e('Plugin license is not configured or invalid. Frontend features are disabled.', 'wc-licensed-product'); ?>
<a href="<?php echo esc_url($settingsUrl); ?>"><?php esc_html_e('Configure License', 'wc-licensed-product'); ?></a>
</p>
</div>
<?php
}
/**
* Get the plugin license checker instance
*/
public function getLicenseChecker(): PluginLicenseChecker
{
return PluginLicenseChecker::getInstance();
}
} }

View File

@@ -64,6 +64,12 @@
</a> </a>
<span class="download-version">v{{ esc_html(download.version) }}</span> <span class="download-version">v{{ esc_html(download.version) }}</span>
<span class="download-date">{{ esc_html(download.released_at) }}</span> <span class="download-date">{{ esc_html(download.released_at) }}</span>
{% if download.file_hash %}
<span class="download-hash" title="{{ esc_attr(download.file_hash) }}">
<span class="dashicons dashicons-shield"></span>
<code>{{ download.file_hash[:12] }}...</code>
</span>
{% endif %}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View File

@@ -3,7 +3,7 @@
* Plugin Name: WooCommerce Licensed Product * Plugin Name: WooCommerce Licensed Product
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-licensed-product * Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-licensed-product
* Description: WooCommerce plugin to sell software products using license keys with domain-based validation. * Description: WooCommerce plugin to sell software products using license keys with domain-based validation.
* Version: 0.2.1 * Version: 0.3.2
* Author: Marco Graetsch * Author: Marco Graetsch
* Author URI: https://src.bundespruefstelle.ch/magdev * Author URI: https://src.bundespruefstelle.ch/magdev
* License: GPL-2.0-or-later * License: GPL-2.0-or-later
@@ -28,7 +28,7 @@ if (!defined('ABSPATH')) {
} }
// Plugin constants // Plugin constants
define('WC_LICENSED_PRODUCT_VERSION', '0.2.1'); define('WC_LICENSED_PRODUCT_VERSION', '0.3.2');
define('WC_LICENSED_PRODUCT_PLUGIN_FILE', __FILE__); define('WC_LICENSED_PRODUCT_PLUGIN_FILE', __FILE__);
define('WC_LICENSED_PRODUCT_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('WC_LICENSED_PRODUCT_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WC_LICENSED_PRODUCT_PLUGIN_URL', plugin_dir_url(__FILE__)); define('WC_LICENSED_PRODUCT_PLUGIN_URL', plugin_dir_url(__FILE__));