Implement version 0.0.2 features

Add product version management:
- ProductVersion model and VersionManager class
- VersionAdminController with meta box on product edit page
- AJAX-based version CRUD (add, delete, toggle status)
- JavaScript for version management UI

Add email notifications:
- LicenseEmailController for order emails
- License keys included in order completed emails
- Support for both HTML and plain text emails

Add REST API rate limiting:
- 30 requests per minute per IP
- Cloudflare and proxy-aware IP detection
- HTTP 429 response with Retry-After header

Other changes:
- Bump version to 0.0.2
- Update CHANGELOG.md
- Add version status styles to admin.css

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-21 19:15:19 +01:00
parent 82c18633a1
commit dec4bd609b
10 changed files with 1269 additions and 4 deletions

View File

@@ -0,0 +1,137 @@
<?php
/**
* Product Version Model
*
* @package Jeremias\WcLicensedProduct\Product
*/
declare(strict_types=1);
namespace Jeremias\WcLicensedProduct\Product;
/**
* Product version entity model
*/
class ProductVersion
{
private int $id;
private int $productId;
private string $version;
private int $majorVersion;
private int $minorVersion;
private int $patchVersion;
private ?string $releaseNotes;
private ?string $downloadUrl;
private bool $isActive;
private \DateTimeInterface $releasedAt;
private \DateTimeInterface $createdAt;
/**
* Create version from database row
*/
public static function fromArray(array $data): self
{
$version = new self();
$version->id = (int) $data['id'];
$version->productId = (int) $data['product_id'];
$version->version = $data['version'];
$version->majorVersion = (int) $data['major_version'];
$version->minorVersion = (int) $data['minor_version'];
$version->patchVersion = (int) $data['patch_version'];
$version->releaseNotes = $data['release_notes'] ?: null;
$version->downloadUrl = $data['download_url'] ?: null;
$version->isActive = (bool) $data['is_active'];
$version->releasedAt = new \DateTimeImmutable($data['released_at']);
$version->createdAt = new \DateTimeImmutable($data['created_at']);
return $version;
}
/**
* Parse version string into components
*/
public static function parseVersion(string $versionString): array
{
$parts = explode('.', $versionString);
return [
'major' => (int) ($parts[0] ?? 0),
'minor' => (int) ($parts[1] ?? 0),
'patch' => (int) ($parts[2] ?? 0),
];
}
public function getId(): int
{
return $this->id;
}
public function getProductId(): int
{
return $this->productId;
}
public function getVersion(): string
{
return $this->version;
}
public function getMajorVersion(): int
{
return $this->majorVersion;
}
public function getMinorVersion(): int
{
return $this->minorVersion;
}
public function getPatchVersion(): int
{
return $this->patchVersion;
}
public function getReleaseNotes(): ?string
{
return $this->releaseNotes;
}
public function getDownloadUrl(): ?string
{
return $this->downloadUrl;
}
public function isActive(): bool
{
return $this->isActive;
}
public function getReleasedAt(): \DateTimeInterface
{
return $this->releasedAt;
}
public function getCreatedAt(): \DateTimeInterface
{
return $this->createdAt;
}
/**
* Convert to array
*/
public function toArray(): array
{
return [
'id' => $this->id,
'product_id' => $this->productId,
'version' => $this->version,
'major_version' => $this->majorVersion,
'minor_version' => $this->minorVersion,
'patch_version' => $this->patchVersion,
'release_notes' => $this->releaseNotes,
'download_url' => $this->downloadUrl,
'is_active' => $this->isActive,
'released_at' => $this->releasedAt->format('Y-m-d H:i:s'),
'created_at' => $this->createdAt->format('Y-m-d H:i:s'),
];
}
}