You've already forked wc-tier-and-package-prices
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a4b84f7e41 | |||
| 2e9c948a07 | |||
| dd4333bd11 | |||
| b909221ae2 | |||
| d80c9d90f9 | |||
| 2bf0cd82fe | |||
| 9451cc1965 | |||
| 02b0308058 | |||
| 38e9506d4e | |||
| 74cc56005e | |||
| 136eed2bdd | |||
| 178f86f3e6 | |||
| 7286459ff2 | |||
| cbe758267e |
217
.gitea/workflows/release.yml
Normal file
217
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,217 @@
|
||||
name: Create Release Package
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.3'
|
||||
extensions: mbstring, xml, zip, intl, gettext
|
||||
tools: composer:v2
|
||||
|
||||
- name: Get version from tag
|
||||
id: version
|
||||
run: |
|
||||
VERSION=${GITHUB_REF_NAME#v}
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Building version: $VERSION"
|
||||
|
||||
- name: Validate composer.json
|
||||
run: composer validate --strict
|
||||
|
||||
- name: Install Composer dependencies (production)
|
||||
run: |
|
||||
composer config platform.php 8.3.0
|
||||
composer install --no-dev --optimize-autoloader --no-interaction
|
||||
|
||||
- name: Install gettext
|
||||
run: apt-get update && apt-get install -y gettext
|
||||
|
||||
- name: Compile translations
|
||||
run: |
|
||||
for po in languages/*.po; do
|
||||
if [ -f "$po" ]; then
|
||||
mo="${po%.po}.mo"
|
||||
echo "Compiling $po to $mo"
|
||||
msgfmt -o "$mo" "$po"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Verify plugin version matches tag
|
||||
run: |
|
||||
PLUGIN_VERSION=$(grep -oP "Version:\s*\K[0-9]+\.[0-9]+\.[0-9]+" wc-tier-and-package-prices.php | head -1)
|
||||
TAG_VERSION=${{ steps.version.outputs.version }}
|
||||
if [ "$PLUGIN_VERSION" != "$TAG_VERSION" ]; then
|
||||
echo "Error: Plugin version ($PLUGIN_VERSION) does not match tag version ($TAG_VERSION)"
|
||||
exit 1
|
||||
fi
|
||||
echo "Version verified: $PLUGIN_VERSION"
|
||||
|
||||
- name: Create release directory
|
||||
run: mkdir -p releases
|
||||
|
||||
- name: Build release package
|
||||
run: |
|
||||
VERSION=${{ steps.version.outputs.version }}
|
||||
PLUGIN_NAME="wc-tier-and-package-prices"
|
||||
RELEASE_FILE="releases/${PLUGIN_NAME}-${VERSION}.zip"
|
||||
|
||||
cd ..
|
||||
zip -r "${PLUGIN_NAME}/${RELEASE_FILE}" "${PLUGIN_NAME}" \
|
||||
-x "${PLUGIN_NAME}/.git/*" \
|
||||
-x "${PLUGIN_NAME}/.gitea/*" \
|
||||
-x "${PLUGIN_NAME}/.github/*" \
|
||||
-x "${PLUGIN_NAME}/.vscode/*" \
|
||||
-x "${PLUGIN_NAME}/.idea/*" \
|
||||
-x "${PLUGIN_NAME}/.claude/*" \
|
||||
-x "${PLUGIN_NAME}/CLAUDE.md" \
|
||||
-x "${PLUGIN_NAME}/wordpress" \
|
||||
-x "${PLUGIN_NAME}/wordpress/*" \
|
||||
-x "${PLUGIN_NAME}/core" \
|
||||
-x "${PLUGIN_NAME}/core/*" \
|
||||
-x "${PLUGIN_NAME}/wp-core" \
|
||||
-x "${PLUGIN_NAME}/wp-core/*" \
|
||||
-x "${PLUGIN_NAME}/releases/*" \
|
||||
-x "${PLUGIN_NAME}/composer.lock" \
|
||||
-x "${PLUGIN_NAME}/*.log" \
|
||||
-x "${PLUGIN_NAME}/logs/*" \
|
||||
-x "${PLUGIN_NAME}/.gitignore" \
|
||||
-x "${PLUGIN_NAME}/.gitmodules" \
|
||||
-x "${PLUGIN_NAME}/.editorconfig" \
|
||||
-x "${PLUGIN_NAME}/phpcs.xml*" \
|
||||
-x "${PLUGIN_NAME}/phpunit.xml*" \
|
||||
-x "${PLUGIN_NAME}/tests/*" \
|
||||
-x "${PLUGIN_NAME}/templates/cache/*" \
|
||||
-x "${PLUGIN_NAME}/notes.*" \
|
||||
-x "${PLUGIN_NAME}/*.po~" \
|
||||
-x "${PLUGIN_NAME}/*.bak" \
|
||||
-x "${PLUGIN_NAME}/lib/*/.git/*" \
|
||||
-x "${PLUGIN_NAME}/lib/*/CLAUDE.md" \
|
||||
-x "${PLUGIN_NAME}/vendor/magdev/*/.git/*" \
|
||||
-x "${PLUGIN_NAME}/vendor/magdev/*/CLAUDE.md" \
|
||||
-x "*.DS_Store" \
|
||||
-x "*Thumbs.db"
|
||||
|
||||
cd "${PLUGIN_NAME}"
|
||||
echo "Created: ${RELEASE_FILE}"
|
||||
ls -lh "${RELEASE_FILE}"
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
VERSION=${{ steps.version.outputs.version }}
|
||||
PLUGIN_NAME="wc-tier-and-package-prices"
|
||||
RELEASE_FILE="releases/${PLUGIN_NAME}-${VERSION}.zip"
|
||||
|
||||
cd releases
|
||||
sha256sum "${PLUGIN_NAME}-${VERSION}.zip" > "${PLUGIN_NAME}-${VERSION}.zip.sha256"
|
||||
echo "SHA256:"
|
||||
cat "${PLUGIN_NAME}-${VERSION}.zip.sha256"
|
||||
|
||||
- name: Verify package structure
|
||||
run: |
|
||||
set +o pipefail
|
||||
VERSION=${{ steps.version.outputs.version }}
|
||||
PLUGIN_NAME="wc-tier-and-package-prices"
|
||||
|
||||
echo "Package contents (first 50 entries):"
|
||||
unzip -l "releases/${PLUGIN_NAME}-${VERSION}.zip" | head -50 || true
|
||||
|
||||
# Verify main plugin file exists
|
||||
if unzip -l "releases/${PLUGIN_NAME}-${VERSION}.zip" | grep -q "${PLUGIN_NAME}/${PLUGIN_NAME}.php"; then
|
||||
echo "✓ Main plugin file at correct location"
|
||||
else
|
||||
echo "✗ Error: Main plugin file not found at ${PLUGIN_NAME}/${PLUGIN_NAME}.php"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify vendor directory included
|
||||
if unzip -l "releases/${PLUGIN_NAME}-${VERSION}.zip" | grep -q "${PLUGIN_NAME}/vendor/"; then
|
||||
echo "✓ Vendor directory included"
|
||||
else
|
||||
echo "✗ Error: Vendor directory not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify excluded files are not present
|
||||
if unzip -l "releases/${PLUGIN_NAME}-${VERSION}.zip" | grep -qE "CLAUDE\.md|\.claude/|\.git/|wordpress/"; then
|
||||
echo "✗ Error: Excluded files found in package"
|
||||
unzip -l "releases/${PLUGIN_NAME}-${VERSION}.zip" | grep -E "CLAUDE\.md|\.claude/|\.git/|wordpress/"
|
||||
exit 1
|
||||
else
|
||||
echo "✓ No excluded files in package"
|
||||
fi
|
||||
|
||||
- name: Extract changelog for release notes
|
||||
id: changelog
|
||||
run: |
|
||||
VERSION=${{ steps.version.outputs.version }}
|
||||
|
||||
# Extract release notes from CHANGELOG.md
|
||||
NOTES=$(sed -n "/^## \[${VERSION}\]/,/^## \[/p" CHANGELOG.md | sed '$ d' | tail -n +2)
|
||||
if [ -z "$NOTES" ]; then
|
||||
NOTES="Release version ${VERSION}"
|
||||
fi
|
||||
|
||||
echo "$NOTES" > release_notes.txt
|
||||
echo "Release notes extracted"
|
||||
|
||||
- name: Create Gitea Release
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.SRC_GITEA_TOKEN }}
|
||||
run: |
|
||||
VERSION=${{ steps.version.outputs.version }}
|
||||
TAG_NAME=${{ github.ref_name }}
|
||||
PLUGIN_NAME="wc-tier-and-package-prices"
|
||||
|
||||
# Check if this is a prerelease (contains hyphen like v1.0.0-beta)
|
||||
PRERELEASE="false"
|
||||
if [[ "$TAG_NAME" == *-* ]]; then
|
||||
PRERELEASE="true"
|
||||
fi
|
||||
|
||||
BODY=$(cat release_notes.txt)
|
||||
|
||||
# Create release via Gitea API
|
||||
RELEASE_RESPONSE=$(curl -s -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\": \"${TAG_NAME}\", \"name\": \"Release ${VERSION}\", \"body\": $(echo "$BODY" | jq -Rs .), \"draft\": false, \"prerelease\": ${PRERELEASE}}" \
|
||||
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases")
|
||||
|
||||
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id')
|
||||
|
||||
if [ "$RELEASE_ID" == "null" ] || [ -z "$RELEASE_ID" ]; then
|
||||
echo "Failed to create release:"
|
||||
echo "$RELEASE_RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Created release ID: $RELEASE_ID"
|
||||
|
||||
# Upload release assets
|
||||
for file in "releases/${PLUGIN_NAME}-${VERSION}.zip" "releases/${PLUGIN_NAME}-${VERSION}.zip.sha256"; do
|
||||
if [ -f "$file" ]; then
|
||||
FILENAME=$(basename "$file")
|
||||
echo "Uploading $FILENAME..."
|
||||
curl -s -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary "@$file" \
|
||||
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=${FILENAME}"
|
||||
echo "Uploaded $FILENAME"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Release created successfully: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG_NAME}"
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -37,3 +37,6 @@ wp-core
|
||||
|
||||
# Releases (not tracked in git)
|
||||
/releases/
|
||||
|
||||
# Claude Code local settings
|
||||
.claude/settings.json
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "lib/wc-licensed-product-client"]
|
||||
path = lib/wc-licensed-product-client
|
||||
url = ../wc-licensed-product-client.git
|
||||
54
CHANGELOG.md
54
CHANGELOG.md
@@ -5,6 +5,60 @@ All notable changes to WooCommerce Tier and Package Prices will be documented in
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.4.0] - 2026-01-29
|
||||
|
||||
### Added
|
||||
|
||||
- **Gitea CI/CD Release Pipeline**: Automated release workflow triggered on version tags
|
||||
- Validates plugin version matches tag version
|
||||
- Installs production Composer dependencies
|
||||
- Compiles translation files (.po to .mo)
|
||||
- Creates release package with proper exclusions
|
||||
- Generates SHA256 checksum
|
||||
- Publishes release to Gitea with changelog notes
|
||||
|
||||
---
|
||||
|
||||
## [1.3.1] - 2026-01-27
|
||||
|
||||
### Changed
|
||||
|
||||
- **Switched to SecureLicenseClient**: Upgraded from basic `LicenseClient` to `SecureLicenseClient` with HMAC-SHA256 response signature verification for enhanced security against tampering and replay attacks
|
||||
|
||||
- **Added Server Secret Configuration**: New "Server Secret" field in License settings for secure communication with the license server
|
||||
|
||||
### Added
|
||||
|
||||
- **Rate Limit Handling**: Added proper handling of `RateLimitExceededException` with user-friendly messages showing retry wait time
|
||||
- **Signature Verification Error Handling**: Added dedicated handling for `SignatureException` when response signatures fail verification
|
||||
- **URL Validation Error Handling**: Added handling for `InvalidArgumentException` from SSRF protection in the license client
|
||||
|
||||
### Security
|
||||
|
||||
- Response signatures are now verified using HMAC-SHA256 with license-specific derived keys (RFC 5869 HKDF)
|
||||
- The license client now validates server URLs to prevent SSRF attacks (blocks private IP ranges)
|
||||
- HTTP connections require HTTPS unless explicitly allowed for localhost testing
|
||||
|
||||
### Technical Details
|
||||
|
||||
**License Client Upgrade**:
|
||||
|
||||
- Changed from `LicenseClient` to `SecureLicenseClient`
|
||||
- Added `serverSecret` parameter for signature verification
|
||||
- Library updated from `v0.1.0` to `dev-main` with new security features
|
||||
|
||||
**New Exception Handling**:
|
||||
|
||||
- `RateLimitExceededException` - shows retry time to user
|
||||
- `SignatureException` - indicates server secret mismatch
|
||||
- `InvalidArgumentException` - invalid/blocked URL detected
|
||||
|
||||
**New Settings Field**:
|
||||
|
||||
- `wc_tpp_license_server_secret` (password type) for the shared secret
|
||||
|
||||
---
|
||||
|
||||
## [1.3.0] - 2026-01-25
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
360
CLAUDE.md
360
CLAUDE.md
@@ -1,7 +1,7 @@
|
||||
# WooCommerce Tier and Package Prices - AI Context Document
|
||||
|
||||
**Last Updated:** 2026-01-25
|
||||
**Current Version:** 1.3.0
|
||||
**Last Updated:** 2026-01-29
|
||||
**Current Version:** 1.4.0
|
||||
**Author:** Marco Graetsch
|
||||
**Project Status:** Production-ready WordPress plugin
|
||||
|
||||
@@ -20,9 +20,9 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
|
||||
|
||||
**Note for AI Assistants:** Clean this section after the specific features are done or new releases are made. Effective changes are tracked in `CHANGELOG.md`. Do not add completed versions here - document them in the Session History section at the end of this file. Always keep the `Known Bugs` section and create a section with the next bugfix and minor version after a release.
|
||||
|
||||
### Version 1.3.1
|
||||
### Version 1.4.1
|
||||
|
||||
- TBD
|
||||
- No planned changes yet
|
||||
|
||||
## Technical Stack
|
||||
|
||||
@@ -40,7 +40,7 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
|
||||
```json
|
||||
{
|
||||
"twig/twig": "^3.0",
|
||||
"magdev/wc-licensed-product-client": "^0.1",
|
||||
"magdev/wc-licensed-product-client": "dev-main",
|
||||
"symfony/http-client": "^7.0",
|
||||
"psr/log": "^3.0",
|
||||
"psr/cache": "^3.0",
|
||||
@@ -55,6 +55,8 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
|
||||
```txt
|
||||
wc-tier-and-package-prices/
|
||||
├── wc-tier-and-package-prices.php # Main plugin file (entry point)
|
||||
├── .gitea/workflows/
|
||||
│ └── release.yml # CI/CD release pipeline
|
||||
├── includes/ # PHP classes
|
||||
│ ├── class-wc-tpp-admin.php # Admin settings integration
|
||||
│ ├── class-wc-tpp-settings.php # WooCommerce settings page
|
||||
@@ -81,7 +83,9 @@ wc-tier-and-package-prices/
|
||||
│ ├── *.pot # Translation template
|
||||
│ ├── *.po # Translation sources
|
||||
│ └── *.mo # Compiled translations
|
||||
├── vendor/ # Composer dependencies (included in releases)
|
||||
├── lib/ # Bundled libraries (git submodules)
|
||||
│ └── wc-licensed-product-client/ # License client library (v0.2.x)
|
||||
├── vendor/ # Composer dependencies (generated)
|
||||
├── releases/ # Release packages (not in git)
|
||||
└── *.md # Documentation files
|
||||
|
||||
@@ -353,136 +357,133 @@ Symptom: Help icon appearing far from label text at container edge
|
||||
|
||||
## Release Process
|
||||
|
||||
### Version Bumping
|
||||
### Automated CI/CD Pipeline (Recommended)
|
||||
|
||||
Update version in 3 places:
|
||||
Since v1.4.0, releases are automated via Gitea CI/CD. The pipeline is triggered when a version tag is pushed.
|
||||
|
||||
1. `wc-tier-and-package-prices.php` - Plugin header comment (line 7)
|
||||
2. `wc-tier-and-package-prices.php` - `WC_TPP_VERSION` constant (line 26)
|
||||
3. `composer.json` - version field (optional, not critical)
|
||||
|
||||
### Creating Release Package
|
||||
|
||||
**CRITICAL:** The zip command must be run from the **parent directory** of the plugin folder to create proper archive structure.
|
||||
**Workflow:**
|
||||
|
||||
```bash
|
||||
# From parent directory (/home/magdev/workspaces/php)
|
||||
cd /home/magdev/workspaces/php
|
||||
# 1. Update version in 3 places
|
||||
# - wc-tier-and-package-prices.php (header comment, line 7)
|
||||
# - wc-tier-and-package-prices.php (WC_TPP_VERSION constant)
|
||||
# - composer.json (version field)
|
||||
|
||||
# Create zip excluding dev files - note the correct path structure
|
||||
zip -r wc-tier-and-package-prices/releases/wc-tier-and-package-prices-X.X.X.zip wc-tier-and-package-prices/ \
|
||||
-x 'wc-tier-and-package-prices/.git*' \
|
||||
'wc-tier-and-package-prices/*.log' \
|
||||
'wc-tier-and-package-prices/.claude/*' \
|
||||
'wc-tier-and-package-prices/CLAUDE.md' \
|
||||
'wc-tier-and-package-prices/releases/*' \
|
||||
'wc-tier-and-package-prices/node_modules/*' \
|
||||
'wc-tier-and-package-prices/.DS_Store' \
|
||||
'wc-tier-and-package-prices/Thumbs.db' \
|
||||
'wc-tier-and-package-prices/.vscode/*' \
|
||||
'wc-tier-and-package-prices/.idea/*' \
|
||||
'wc-tier-and-package-prices/*.sublime-*' \
|
||||
'wc-tier-and-package-prices/notes.*' \
|
||||
'wc-tier-and-package-prices/logs/*' \
|
||||
'wc-tier-and-package-prices/templates/cache/*' \
|
||||
'wc-tier-and-package-prices/composer.lock'
|
||||
# 2. Update CHANGELOG.md with release notes
|
||||
|
||||
# Return to project directory
|
||||
cd wc-tier-and-package-prices
|
||||
# 3. Commit and push to dev
|
||||
git add -A && git commit -m "Version X.X.X - [description]
|
||||
|
||||
# Generate SHA256 checksum
|
||||
cd releases
|
||||
sha256sum wc-tier-and-package-prices-X.X.X.zip > wc-tier-and-package-prices-X.X.X.zip.sha256
|
||||
cd ..
|
||||
```
|
||||
|
||||
**IMPORTANT NOTES:**
|
||||
|
||||
- The `vendor/` directory MUST be included in releases (Twig dependency required for runtime)
|
||||
- Running zip from wrong directory creates empty or malformed archives
|
||||
- Exclusion patterns must match the relative path structure used in zip command
|
||||
- Always verify the package with `unzip -l` and test extraction before committing
|
||||
|
||||
### Verification Steps
|
||||
|
||||
After creating the release package, always verify:
|
||||
|
||||
```bash
|
||||
# Check package size (should be ~400-450KB, NOT 8MB+ or near 0)
|
||||
ls -lh releases/wc-tier-and-package-prices-X.X.X.zip
|
||||
|
||||
# Verify exclusions worked
|
||||
unzip -l releases/wc-tier-and-package-prices-X.X.X.zip | grep -E "CLAUDE\.md|\.claude/|\.git" && echo "ERROR: Excluded files found!" || echo "OK: No excluded files"
|
||||
|
||||
# Test extraction
|
||||
cd /tmp && rm -rf test-extract && unzip -q /path/to/releases/wc-tier-and-package-prices-X.X.X.zip -d test-extract && ls -la test-extract/wc-tier-and-package-prices/
|
||||
|
||||
# Verify version in extracted package
|
||||
head -30 /tmp/test-extract/wc-tier-and-package-prices/wc-tier-and-package-prices.php | grep -E "Version:|WC_TPP_VERSION"
|
||||
|
||||
# Verify template changes (if applicable)
|
||||
grep 'class="regular"' /tmp/test-extract/wc-tier-and-package-prices/templates/admin/*.twig
|
||||
```
|
||||
|
||||
### Git Workflow for Releases
|
||||
|
||||
**Standard workflow:** Work on `dev` branch → merge to `main` → tag → push
|
||||
|
||||
```bash
|
||||
# 1. Ensure you're on dev branch with all changes committed
|
||||
git checkout dev
|
||||
git add [files]
|
||||
git commit -m "Release version X.X.X - [description]
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
||||
|
||||
# 2. Merge dev to main
|
||||
git checkout main
|
||||
git merge dev --no-edit # Should be fast-forward
|
||||
|
||||
# 3. Create annotated tag
|
||||
git tag -a vX.X.X -m "Release version X.X.X - [description]"
|
||||
|
||||
# 4. Push everything
|
||||
git push origin main
|
||||
git push origin vX.X.X
|
||||
|
||||
# 5. Update dev and push
|
||||
git checkout dev
|
||||
git rebase main # Should be up-to-date already
|
||||
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
|
||||
git push origin dev
|
||||
|
||||
# 6. If you have uncommitted local changes (like .claude/settings.local.json)
|
||||
git stash push -m "Local development settings"
|
||||
# ... do git operations ...
|
||||
git stash pop
|
||||
# 4. Merge to main and push
|
||||
git checkout main && git merge dev --no-edit && git push origin main
|
||||
|
||||
# 5. Create and push tag (triggers CI/CD)
|
||||
git tag -a vX.X.X -m "Release version X.X.X - [description]"
|
||||
git push origin vX.X.X
|
||||
|
||||
# 6. Switch back to dev
|
||||
git checkout dev
|
||||
```
|
||||
|
||||
**Important Git Notes:**
|
||||
**What the CI/CD Pipeline Does:**
|
||||
|
||||
- Always commit from `dev` branch first
|
||||
- Tags should use format `vX.X.X` (e.g., `v1.1.22`)
|
||||
- Use annotated tags (`-a`) not lightweight tags
|
||||
- Commit messages should follow the established format with Claude Code attribution
|
||||
- `.claude/settings.local.json` changes are typically local-only (stash before rebasing)
|
||||
1. Checks out code with git submodules (`submodules: recursive`)
|
||||
2. Sets up PHP 8.3 with required extensions
|
||||
3. Validates `composer.json`
|
||||
4. Installs production Composer dependencies (`--no-dev`)
|
||||
5. Compiles translation files (.po → .mo)
|
||||
6. Validates plugin version matches tag version
|
||||
7. Creates release package with proper exclusions
|
||||
8. Generates SHA256 checksum
|
||||
9. Verifies package structure
|
||||
10. Extracts changelog notes for release description
|
||||
11. Creates Gitea release with assets
|
||||
|
||||
**Pipeline Location:** `.gitea/workflows/release.yml`
|
||||
|
||||
**Required Secret:** `SRC_GITEA_TOKEN` - Gitea API token for creating releases
|
||||
|
||||
### Git Submodule: wc-licensed-product-client
|
||||
|
||||
The `magdev/wc-licensed-product-client` library is bundled as a git submodule in `lib/` to avoid private repository authentication during CI/CD and to prevent conflicts with Composer's `vendor/` directory.
|
||||
|
||||
**Location:** `lib/wc-licensed-product-client/`
|
||||
|
||||
**Submodule URL:** Relative path `../wc-licensed-product-client.git` (works with Gitea CI/CD)
|
||||
|
||||
**Updating the submodule:**
|
||||
|
||||
```bash
|
||||
# Update to a specific tag
|
||||
cd lib/wc-licensed-product-client
|
||||
git fetch --tags
|
||||
git checkout v0.2.3 # or desired version
|
||||
cd ../..
|
||||
git add lib/wc-licensed-product-client
|
||||
git commit -m "Update wc-licensed-product-client to v0.2.3"
|
||||
```
|
||||
|
||||
**Composer Configuration:**
|
||||
|
||||
```json
|
||||
{
|
||||
"repositories": [
|
||||
{
|
||||
"type": "path",
|
||||
"url": "lib/wc-licensed-product-client"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"magdev/wc-licensed-product-client": "^0.2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Composer creates a symlink in `vendor/magdev/wc-licensed-product-client` pointing to `lib/wc-licensed-product-client`.
|
||||
|
||||
### Manual Release (Legacy)
|
||||
|
||||
For manual releases without CI/CD, run from the **parent directory**:
|
||||
|
||||
```bash
|
||||
cd /home/magdev/workspaces/php/wordpress/wp-content/plugins
|
||||
|
||||
zip -r wc-tier-and-package-prices/releases/wc-tier-and-package-prices-X.X.X.zip wc-tier-and-package-prices/ \
|
||||
-x 'wc-tier-and-package-prices/.git/*' \
|
||||
-x 'wc-tier-and-package-prices/.gitea/*' \
|
||||
-x 'wc-tier-and-package-prices/.claude/*' \
|
||||
-x 'wc-tier-and-package-prices/CLAUDE.md' \
|
||||
-x 'wc-tier-and-package-prices/releases/*' \
|
||||
-x 'wc-tier-and-package-prices/composer.lock' \
|
||||
-x 'wc-tier-and-package-prices/*.log' \
|
||||
-x 'wc-tier-and-package-prices/.gitignore' \
|
||||
-x 'wc-tier-and-package-prices/.gitmodules' \
|
||||
-x 'wc-tier-and-package-prices/vendor/*/.git/*' \
|
||||
-x '*.DS_Store'
|
||||
|
||||
cd wc-tier-and-package-prices/releases
|
||||
sha256sum wc-tier-and-package-prices-X.X.X.zip > wc-tier-and-package-prices-X.X.X.zip.sha256
|
||||
```
|
||||
|
||||
### What Gets Released
|
||||
|
||||
- All plugin source files
|
||||
- Compiled vendor dependencies
|
||||
- Compiled vendor dependencies (including submodule)
|
||||
- Translation files (.mo compiled from .po)
|
||||
- Assets (CSS, JS)
|
||||
- Documentation (README, CHANGELOG, etc.)
|
||||
|
||||
### What's Excluded
|
||||
|
||||
- Git metadata (`.git/`)
|
||||
- Git metadata (`.git/`, `.gitmodules`)
|
||||
- CI/CD workflows (`.gitea/`)
|
||||
- Development files (`.vscode/`, `.claude/`, `CLAUDE.md`)
|
||||
- Logs and cache files
|
||||
- Previous releases
|
||||
- `composer.lock` (but `vendor/` is included)
|
||||
- `composer.lock`
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
@@ -1101,8 +1102,8 @@ if (!empty($tiers)) {
|
||||
|
||||
### PHP
|
||||
|
||||
- Minimum: 7.4
|
||||
- Uses modern PHP features (type hints acceptable in new code)
|
||||
- Minimum: 8.3 (breaking change in v1.3.0)
|
||||
- Uses modern PHP features (type hints, named arguments, etc.)
|
||||
- Composer autoloader handles namespacing
|
||||
|
||||
### Browsers
|
||||
@@ -1138,4 +1139,137 @@ The plugin architecture is solid and well-tested. Most bugs arise from:
|
||||
|
||||
---
|
||||
|
||||
## Session History
|
||||
|
||||
### v1.3.0 Release Session (2026-01-25)
|
||||
|
||||
**Accomplished:**
|
||||
|
||||
1. Fixed known bugs from CLAUDE.md:
|
||||
- Removed all releases from git tracking (`git rm --cached -r releases/`)
|
||||
- Deleted all MD5 checksum files, keeping only SHA256
|
||||
- Updated `.gitignore` to exclude `/releases/`
|
||||
|
||||
2. Implemented v1.3.0 features:
|
||||
- Bumped PHP requirement from 7.4 to 8.3 (breaking change)
|
||||
- Added PHP version check with admin notice for incompatible servers
|
||||
- Added `magdev/wc-licensed-product-client` library integration
|
||||
- Refactored settings page to use WooCommerce modern sub-tabs pattern (`get_own_sections()`)
|
||||
- Created "General" and "License" sub-tabs
|
||||
- Implemented license management with AJAX validation/activation
|
||||
- Added license status caching via WordPress transients
|
||||
- Added CSS styling for license status display
|
||||
|
||||
3. Updated all translation files:
|
||||
- Added 28 new translation strings for license management
|
||||
- Updated .pot template and all 7 .po files
|
||||
- Compiled all .mo files
|
||||
|
||||
4. Created release package:
|
||||
- Package size: 737KB (increased due to new dependencies)
|
||||
- 642 files included (more due to license client library)
|
||||
- SHA256 checksum generated
|
||||
|
||||
**Key Learnings:**
|
||||
|
||||
- WooCommerce settings sub-tabs use `get_own_sections()` and `get_settings_for_{section}_section()` pattern
|
||||
- License client library `magdev/wc-licensed-product-client` is from private Gitea repo - requires `repositories` config in composer.json
|
||||
- Package version was `^0.1` not `^1.0` - always check available versions before setting constraint
|
||||
- Release package size increased from ~430KB to ~737KB due to new Composer dependencies
|
||||
|
||||
### v1.3.1 Release Session (2026-01-27)
|
||||
|
||||
**Accomplished:**
|
||||
|
||||
1. Reviewed server-implementation.md from license client library
|
||||
2. Upgraded from `LicenseClient` to `SecureLicenseClient`:
|
||||
- Added "Server Secret" configuration field in License settings tab
|
||||
- Updated `get_license_client()` to use `SecureLicenseClient` with HMAC-SHA256 signature verification
|
||||
- Updated JavaScript to pass `server_secret` in AJAX requests
|
||||
|
||||
3. Added comprehensive exception handling:
|
||||
- `RateLimitExceededException` - Shows retry time to user
|
||||
- `SignatureException` - Reports signature verification failures
|
||||
- `InvalidArgumentException` - SSRF protection for private IP blocking
|
||||
|
||||
4. Updated Composer dependencies:
|
||||
- Changed `magdev/wc-licensed-product-client` from `^0.1` to `dev-main`
|
||||
- Library includes new security features (SSRF protection, response signatures)
|
||||
|
||||
5. Updated translations:
|
||||
- Added 7 new translation strings for v1.3.1
|
||||
- All 7 language files updated and compiled
|
||||
|
||||
6. Created release package:
|
||||
- Package size: 775KB (after proper exclusions)
|
||||
- 650 files included
|
||||
- SHA256 checksum generated
|
||||
|
||||
7. Updated README.md:
|
||||
- Documented license management feature
|
||||
- Updated PHP requirement to 8.3
|
||||
- Added v1.3.0 and v1.3.1 changelog entries
|
||||
|
||||
8. Re-released v1.3.1:
|
||||
- Fixed release package exclusions (vendor .git dirs were included)
|
||||
- Added `.claude/settings.json` to .gitignore
|
||||
|
||||
**Key Learnings:**
|
||||
|
||||
- `SecureLicenseClient` requires `serverSecret` parameter for HMAC verification
|
||||
- The library's exception classes are in `Magdev\WcLicensedProductClient\Exception\` and `Magdev\WcLicensedProductClient\Security\` namespaces
|
||||
- `RateLimitExceededException` has a `retryAfter` property (int seconds)
|
||||
- Always check library documentation in `docs/server-implementation.md` for implementation requirements
|
||||
|
||||
**Release Package Learnings (CRITICAL):**
|
||||
|
||||
- Vendor directories installed via Composer (dev-main) contain their own `.git/` folders
|
||||
- Release exclusion patterns must include `'wc-tier-and-package-prices/vendor/*/.git/*'` and `'wc-tier-and-package-prices/vendor/*/CLAUDE.md'`
|
||||
- Run zip from `/home/magdev/workspaces/php/wordpress/wp-content/plugins/` (NOT `/home/magdev/workspaces/php/`)
|
||||
- Always verify exclusions with: `unzip -l package.zip | grep -E "\.git/|CLAUDE\.md"`
|
||||
|
||||
### v1.4.0 Release Session (2026-01-29)
|
||||
|
||||
**Accomplished:**
|
||||
|
||||
1. Created Gitea CI/CD release pipeline:
|
||||
- `.gitea/workflows/release.yml` - Automated release workflow
|
||||
- Triggers on version tags (`v*`)
|
||||
- Validates plugin version matches tag
|
||||
- Installs production Composer dependencies
|
||||
- Compiles translations (.po → .mo)
|
||||
- Creates release package with proper exclusions
|
||||
- Generates SHA256 checksum
|
||||
- Publishes release to Gitea with changelog notes
|
||||
|
||||
2. Added git submodule for license client:
|
||||
- Initially placed in `vendor/magdev/` - **FAILED** (Composer conflicts)
|
||||
- Fixed: Moved to `lib/wc-licensed-product-client/` like wp-fedistream
|
||||
- Uses relative URL `../wc-licensed-product-client.git`
|
||||
- Pinned to v0.2.2 tag
|
||||
- Avoids private repository authentication during CI/CD
|
||||
|
||||
3. Updated documentation:
|
||||
- README.md - Added CI/CD section, updated file structure
|
||||
- CLAUDE.md - Updated release process, added submodule docs
|
||||
|
||||
**Key Learnings:**
|
||||
|
||||
- **CRITICAL:** Git submodules should be in `lib/` NOT `vendor/` to avoid Composer conflicts
|
||||
- Composer manages `vendor/` directory - don't put submodules there
|
||||
- Use relative submodule URL (`../repo.git`) for Gitea CI/CD compatibility
|
||||
- Use version constraint `^0.2` instead of `@dev` for stability
|
||||
- Git submodules require `submodules: recursive` in checkout action
|
||||
- Composer creates symlink from `vendor/` to `lib/` automatically
|
||||
|
||||
**CI/CD Pipeline Notes:**
|
||||
|
||||
- Pipeline file: `.gitea/workflows/release.yml`
|
||||
- Required secret: `SRC_GITEA_TOKEN` for Gitea API access
|
||||
- Uses `shivammathur/setup-php@v2` for PHP setup
|
||||
- Uses `actions/checkout@v4` with `submodules: recursive`
|
||||
- Modeled after working wp-fedistream implementation
|
||||
|
||||
---
|
||||
|
||||
Always refer to this document when starting work on this project. Good luck!
|
||||
|
||||
77
README.md
77
README.md
@@ -20,10 +20,11 @@ A powerful WooCommerce plugin that adds tier pricing and package pricing functio
|
||||
|
||||
### Admin Features
|
||||
- Easy-to-use product meta boxes for adding tiers and packages
|
||||
- Global settings page under WooCommerce menu
|
||||
- Global settings page under WooCommerce menu with sub-tabs
|
||||
- Configure display position (before/after add to cart, after price)
|
||||
- Enable/disable tier or package pricing independently
|
||||
- Sortable pricing rules
|
||||
- License management with secure HMAC signature verification
|
||||
|
||||
### Frontend Features
|
||||
- Beautiful pricing tables on product pages
|
||||
@@ -39,6 +40,20 @@ A powerful WooCommerce plugin that adds tier pricing and package pricing functio
|
||||
2. Activate the plugin through the 'Plugins' menu in WordPress
|
||||
3. Make sure WooCommerce is installed and activated
|
||||
|
||||
### Automated Releases
|
||||
|
||||
This project uses a Gitea CI/CD pipeline for automated releases. When a version tag (e.g., `v1.4.0`) is pushed:
|
||||
|
||||
1. Code is checked out with git submodules (dependencies bundled)
|
||||
2. The pipeline validates the plugin version matches the tag
|
||||
3. Composer dependencies are installed (production only)
|
||||
4. Translation files are compiled (.po → .mo)
|
||||
5. A release package is created with proper exclusions
|
||||
6. SHA256 checksum is generated
|
||||
7. Release is published to Gitea with changelog notes extracted from CHANGELOG.md
|
||||
|
||||
The `magdev/wc-licensed-product-client` library is bundled as a git submodule to avoid private repository authentication during CI/CD.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Global Settings
|
||||
@@ -110,7 +125,9 @@ When editing a product, scroll to the **Product data** panel:
|
||||
|
||||
```
|
||||
wc-tier-and-package-prices/
|
||||
├── wc-tier-and-package-prices.php # Main plugin file (v1.1.20)
|
||||
├── wc-tier-and-package-prices.php # Main plugin file (v1.4.0)
|
||||
├── .gitea/workflows/
|
||||
│ └── release.yml # CI/CD release pipeline
|
||||
├── includes/
|
||||
│ ├── class-wc-tpp-admin.php # Admin settings integration
|
||||
│ ├── class-wc-tpp-settings.php # WooCommerce settings page
|
||||
@@ -137,7 +154,9 @@ wc-tier-and-package-prices/
|
||||
│ ├── wc-tier-package-prices.pot # Translation template
|
||||
│ ├── wc-tier-package-prices-*.po # Translation sources
|
||||
│ └── wc-tier-package-prices-*.mo # Compiled translations
|
||||
├── vendor/ # Composer dependencies (Twig)
|
||||
├── lib/ # Bundled libraries (git submodules)
|
||||
│ └── wc-licensed-product-client/ # License client library
|
||||
├── vendor/ # Composer dependencies (generated)
|
||||
├── CHANGELOG.md # Complete version history
|
||||
├── INSTALLATION.md # Installation guide
|
||||
├── QUICKSTART.md # Quick start guide
|
||||
@@ -149,7 +168,7 @@ wc-tier-and-package-prices/
|
||||
|
||||
- WordPress 6.0 or higher (tested up to 6.9.x)
|
||||
- WooCommerce 8.0 or higher (tested up to 10.x)
|
||||
- PHP 7.4 or higher
|
||||
- PHP 8.3 or higher (required since v1.3.0)
|
||||
|
||||
### Compatibility
|
||||
|
||||
@@ -183,9 +202,39 @@ This plugin is licensed under the GPL v2 or later.
|
||||
|
||||
## Changelog
|
||||
|
||||
### Version 1.4.0 - 2026-01-29
|
||||
|
||||
__Current Release__ - CI/CD Release Pipeline
|
||||
|
||||
- __New__: Gitea CI/CD release pipeline for automated builds and releases
|
||||
- __New__: Git submodule for `magdev/wc-licensed-product-client` library
|
||||
- __Changed__: Composer now uses path repository for bundled license client
|
||||
- __DevOps__: Automated version validation, translation compilation, and release publishing
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for complete details.
|
||||
|
||||
### Version 1.3.1 - 2026-01-27
|
||||
|
||||
Secure License Client
|
||||
|
||||
- __Changed__: Switched to `SecureLicenseClient` with HMAC-SHA256 response signature verification
|
||||
- __New__: Server Secret configuration field for secure communication with license server
|
||||
- __Security__: Response signatures verified using HMAC-SHA256 with license-specific derived keys
|
||||
|
||||
### Version 1.3.0 - 2026-01-25
|
||||
|
||||
__Breaking Changes__ - PHP 8.3+ Required
|
||||
|
||||
- __Breaking__: Minimum PHP version increased from 7.4 to 8.3
|
||||
- __New__: License management via `magdev/wc-licensed-product-client` library
|
||||
- __New__: Settings page split into "General" and "License" sub-tabs
|
||||
- __New__: AJAX-based license validation and activation with visual feedback
|
||||
- __New__: License status caching with daily auto-refresh
|
||||
- __New__: PHP version check with admin notice for incompatible servers
|
||||
|
||||
### Version 1.2.0 - 2025-12-29
|
||||
|
||||
__Current Release__ - Variable Product Support
|
||||
Variable Product Support
|
||||
|
||||
- __New__: Full support for WooCommerce variable products with variation-level pricing
|
||||
- __New__: Each variation can have independent tier and package pricing configuration
|
||||
@@ -194,24 +243,6 @@ __Current Release__ - Variable Product Support
|
||||
- __Fixed__: Quantity restrictions now work correctly per-variation
|
||||
- 100% backward compatible - no breaking changes
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for complete details.
|
||||
|
||||
### Version 1.1.22 - 2025-12-23
|
||||
|
||||
- Increased width of label input fields in admin interface
|
||||
|
||||
#### Fixed
|
||||
- **CRITICAL:** WooCommerce Blocks fatal error in mini-cart and cart blocks
|
||||
- Fixed `woocommerce_store_api_product_quantity_editable` filter signature mismatch
|
||||
- Filter now correctly accepts `WC_Product` object instead of cart item array
|
||||
- Resolves "Cannot use object of type WC_Product_Simple as array" fatal error
|
||||
|
||||
#### Technical Details
|
||||
- Updated `block_quantity_editable()` method signature to accept product object
|
||||
- Changed parameter from `$cart_item` array to `WC_Product $product`
|
||||
- Uses `$product->get_id()` instead of array access for product ID
|
||||
- Full compatibility with WooCommerce Store API and block-based cart/checkout
|
||||
|
||||
### Recent Major Updates
|
||||
|
||||
#### Version 1.1.7 - Enhanced Tier Pricing
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "magdev/wc-tier-package-prices",
|
||||
"description": "WooCommerce plugin for tier pricing and package prices with Twig templates",
|
||||
"version": "1.3.0",
|
||||
"type": "wordpress-plugin",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"authors": [
|
||||
@@ -12,18 +11,26 @@
|
||||
],
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://src.bundespruefstelle.ch/magdev/wc-licensed-product-client.git"
|
||||
"type": "path",
|
||||
"url": "lib/wc-licensed-product-client"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.3",
|
||||
"twig/twig": "^3.0",
|
||||
"magdev/wc-licensed-product-client": "^0.1"
|
||||
"magdev/wc-licensed-product-client": "^0.2"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"includes/"
|
||||
]
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"platform": {
|
||||
"php": "8.3.0"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true
|
||||
}
|
||||
|
||||
@@ -154,6 +154,16 @@ if (!class_exists('WC_TPP_Settings')) {
|
||||
'desc_tip' => true,
|
||||
),
|
||||
|
||||
array(
|
||||
'title' => __('Server Secret', 'wc-tier-package-prices'),
|
||||
'desc' => __('The shared secret for secure communication with the license server.', 'wc-tier-package-prices'),
|
||||
'id' => 'wc_tpp_license_server_secret',
|
||||
'type' => 'password',
|
||||
'default' => '',
|
||||
'css' => 'min-width:400px;',
|
||||
'desc_tip' => true,
|
||||
),
|
||||
|
||||
array(
|
||||
'title' => __('License Status', 'wc-tier-package-prices'),
|
||||
'type' => 'wc_tpp_license_status',
|
||||
@@ -188,13 +198,14 @@ if (!class_exists('WC_TPP_Settings')) {
|
||||
|
||||
$license_key = sanitize_text_field(wp_unslash($_POST['license_key'] ?? ''));
|
||||
$server_url = esc_url_raw(wp_unslash($_POST['server_url'] ?? ''));
|
||||
$server_secret = sanitize_text_field(wp_unslash($_POST['server_secret'] ?? ''));
|
||||
|
||||
if (empty($license_key) || empty($server_url)) {
|
||||
wp_send_json_error(array('message' => __('License key and server URL are required.', 'wc-tier-package-prices')));
|
||||
if (empty($license_key) || empty($server_url) || empty($server_secret)) {
|
||||
wp_send_json_error(array('message' => __('License key, server URL, and server secret are required.', 'wc-tier-package-prices')));
|
||||
}
|
||||
|
||||
try {
|
||||
$client = $this->get_license_client($server_url);
|
||||
$client = $this->get_license_client($server_url, $server_secret);
|
||||
$domain = $this->get_current_domain();
|
||||
$result = $client->validate($license_key, $domain);
|
||||
|
||||
@@ -212,16 +223,37 @@ if (!class_exists('WC_TPP_Settings')) {
|
||||
'status' => $this->get_cached_license_status(),
|
||||
));
|
||||
|
||||
} catch (\Magdev\WcLicensedProductClient\Exception\RateLimitExceededException $e) {
|
||||
wp_send_json_error(array(
|
||||
'message' => sprintf(
|
||||
/* translators: %d: Number of seconds to wait */
|
||||
__('Rate limit exceeded. Please try again in %d seconds.', 'wc-tier-package-prices'),
|
||||
$e->retryAfter ?? 60
|
||||
),
|
||||
'code' => 'rate_limit_exceeded',
|
||||
'retry_after' => $e->retryAfter,
|
||||
));
|
||||
} catch (\Magdev\WcLicensedProductClient\Security\SignatureException $e) {
|
||||
delete_transient('wc_tpp_license_status');
|
||||
wp_send_json_error(array(
|
||||
'message' => __('Response signature verification failed. Please check your server secret.', 'wc-tier-package-prices'),
|
||||
'code' => 'signature_error',
|
||||
));
|
||||
} catch (\Magdev\WcLicensedProductClient\Exception\LicenseException $e) {
|
||||
delete_transient('wc_tpp_license_status');
|
||||
wp_send_json_error(array(
|
||||
'message' => $e->getMessage(),
|
||||
'code' => $e->errorCode ?? 'unknown',
|
||||
));
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
wp_send_json_error(array(
|
||||
'message' => $e->getMessage(),
|
||||
'code' => 'invalid_url',
|
||||
));
|
||||
} catch (\Exception $e) {
|
||||
delete_transient('wc_tpp_license_status');
|
||||
wp_send_json_error(array(
|
||||
'message' => $e->getMessage(),
|
||||
'message' => __('An unexpected error occurred. Please try again.', 'wc-tier-package-prices'),
|
||||
'code' => 'exception',
|
||||
));
|
||||
}
|
||||
@@ -239,13 +271,14 @@ if (!class_exists('WC_TPP_Settings')) {
|
||||
|
||||
$license_key = sanitize_text_field(wp_unslash($_POST['license_key'] ?? ''));
|
||||
$server_url = esc_url_raw(wp_unslash($_POST['server_url'] ?? ''));
|
||||
$server_secret = sanitize_text_field(wp_unslash($_POST['server_secret'] ?? ''));
|
||||
|
||||
if (empty($license_key) || empty($server_url)) {
|
||||
wp_send_json_error(array('message' => __('License key and server URL are required.', 'wc-tier-package-prices')));
|
||||
if (empty($license_key) || empty($server_url) || empty($server_secret)) {
|
||||
wp_send_json_error(array('message' => __('License key, server URL, and server secret are required.', 'wc-tier-package-prices')));
|
||||
}
|
||||
|
||||
try {
|
||||
$client = $this->get_license_client($server_url);
|
||||
$client = $this->get_license_client($server_url, $server_secret);
|
||||
$domain = $this->get_current_domain();
|
||||
$result = $client->activate($license_key, $domain);
|
||||
|
||||
@@ -269,14 +302,34 @@ if (!class_exists('WC_TPP_Settings')) {
|
||||
|
||||
wp_send_json_error(array('message' => $result->message));
|
||||
|
||||
} catch (\Magdev\WcLicensedProductClient\Exception\RateLimitExceededException $e) {
|
||||
wp_send_json_error(array(
|
||||
'message' => sprintf(
|
||||
/* translators: %d: Number of seconds to wait */
|
||||
__('Rate limit exceeded. Please try again in %d seconds.', 'wc-tier-package-prices'),
|
||||
$e->retryAfter ?? 60
|
||||
),
|
||||
'code' => 'rate_limit_exceeded',
|
||||
'retry_after' => $e->retryAfter,
|
||||
));
|
||||
} catch (\Magdev\WcLicensedProductClient\Security\SignatureException $e) {
|
||||
wp_send_json_error(array(
|
||||
'message' => __('Response signature verification failed. Please check your server secret.', 'wc-tier-package-prices'),
|
||||
'code' => 'signature_error',
|
||||
));
|
||||
} catch (\Magdev\WcLicensedProductClient\Exception\LicenseException $e) {
|
||||
wp_send_json_error(array(
|
||||
'message' => $e->getMessage(),
|
||||
'code' => $e->errorCode ?? 'unknown',
|
||||
));
|
||||
} catch (\Exception $e) {
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
wp_send_json_error(array(
|
||||
'message' => $e->getMessage(),
|
||||
'code' => 'invalid_url',
|
||||
));
|
||||
} catch (\Exception $e) {
|
||||
wp_send_json_error(array(
|
||||
'message' => __('An unexpected error occurred. Please try again.', 'wc-tier-package-prices'),
|
||||
'code' => 'exception',
|
||||
));
|
||||
}
|
||||
@@ -285,14 +338,18 @@ if (!class_exists('WC_TPP_Settings')) {
|
||||
/**
|
||||
* Get license client instance
|
||||
*
|
||||
* @param string $server_url License server URL.
|
||||
* Uses SecureLicenseClient for HMAC signature verification.
|
||||
*
|
||||
* @param string $server_url License server URL.
|
||||
* @param string $server_secret Shared secret for signature verification.
|
||||
* @return \Magdev\WcLicensedProductClient\LicenseClientInterface
|
||||
*/
|
||||
private function get_license_client(string $server_url): \Magdev\WcLicensedProductClient\LicenseClientInterface {
|
||||
private function get_license_client(string $server_url, string $server_secret): \Magdev\WcLicensedProductClient\LicenseClientInterface {
|
||||
$httpClient = \Symfony\Component\HttpClient\HttpClient::create();
|
||||
return new \Magdev\WcLicensedProductClient\LicenseClient(
|
||||
return new \Magdev\WcLicensedProductClient\SecureLicenseClient(
|
||||
httpClient: $httpClient,
|
||||
baseUrl: $server_url,
|
||||
serverSecret: $server_secret,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -402,6 +459,7 @@ if (!class_exists('WC_TPP_Settings')) {
|
||||
return {
|
||||
license_key: $('#wc_tpp_license_key').val(),
|
||||
server_url: $('#wc_tpp_license_server_url').val(),
|
||||
server_secret: $('#wc_tpp_license_server_secret').val(),
|
||||
nonce: '<?php echo esc_js($nonce); ?>'
|
||||
};
|
||||
}
|
||||
@@ -420,8 +478,8 @@ if (!class_exists('WC_TPP_Settings')) {
|
||||
|
||||
$validateBtn.on('click', function() {
|
||||
var data = getLicenseData();
|
||||
if (!data.license_key || !data.server_url) {
|
||||
alert('<?php echo esc_js(__('Please enter both license server URL and license key.', 'wc-tier-package-prices')); ?>');
|
||||
if (!data.license_key || !data.server_url || !data.server_secret) {
|
||||
alert('<?php echo esc_js(__('Please enter license server URL, license key, and server secret.', 'wc-tier-package-prices')); ?>');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -443,8 +501,8 @@ if (!class_exists('WC_TPP_Settings')) {
|
||||
|
||||
$activateBtn.on('click', function() {
|
||||
var data = getLicenseData();
|
||||
if (!data.license_key || !data.server_url) {
|
||||
alert('<?php echo esc_js(__('Please enter both license server URL and license key.', 'wc-tier-package-prices')); ?>');
|
||||
if (!data.license_key || !data.server_url || !data.server_secret) {
|
||||
alert('<?php echo esc_js(__('Please enter license server URL, license key, and server secret.', 'wc-tier-package-prices')); ?>');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -359,12 +359,33 @@ msgid "Permission denied."
|
||||
msgstr "Zugriff verweigert."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "License key and server URL are required."
|
||||
msgstr "Lizenzschlüssel und Server-URL sind erforderlich."
|
||||
msgid "Server Secret"
|
||||
msgstr "Server-Geheimnis"
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Please enter both license server URL and license key."
|
||||
msgstr "Bitte geben Sie sowohl die Lizenzserver-URL als auch den Lizenzschlüssel ein."
|
||||
msgid "The shared secret for secure communication with the license server."
|
||||
msgstr "Das gemeinsame Geheimnis für die sichere Kommunikation mit dem Lizenzserver."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "License key, server URL, and server secret are required."
|
||||
msgstr "Lizenzschlüssel, Server-URL und Server-Geheimnis sind erforderlich."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
#. translators: %d: Number of seconds to wait
|
||||
msgid "Rate limit exceeded. Please try again in %d seconds."
|
||||
msgstr "Anfragelimit überschritten. Bitte versuchen Sie es in %d Sekunden erneut."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Response signature verification failed. Please check your server secret."
|
||||
msgstr "Überprüfung der Antwortsignatur fehlgeschlagen. Bitte überprüfen Sie Ihr Server-Geheimnis."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es erneut."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Please enter license server URL, license key, and server secret."
|
||||
msgstr "Bitte geben Sie die Lizenzserver-URL, den Lizenzschlüssel und das Server-Geheimnis ein."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Validation failed."
|
||||
|
||||
Binary file not shown.
@@ -359,12 +359,33 @@ msgid "Permission denied."
|
||||
msgstr "Zugriff verweigert."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "License key and server URL are required."
|
||||
msgstr "Lizenzschlüssel und Server-URL sind erforderlich."
|
||||
msgid "Server Secret"
|
||||
msgstr "Server-Geheimnis"
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Please enter both license server URL and license key."
|
||||
msgstr "Bitte gib sowohl die Lizenzserver-URL als auch den Lizenzschlüssel ein."
|
||||
msgid "The shared secret for secure communication with the license server."
|
||||
msgstr "Das gemeinsame Geheimnis für die sichere Kommunikation mit dem Lizenzserver."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "License key, server URL, and server secret are required."
|
||||
msgstr "Lizenzschlüssel, Server-URL und Server-Geheimnis sind erforderlich."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
#. translators: %d: Number of seconds to wait
|
||||
msgid "Rate limit exceeded. Please try again in %d seconds."
|
||||
msgstr "Anfragelimit überschritten. Bitte versuche es in %d Sekunden erneut."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Response signature verification failed. Please check your server secret."
|
||||
msgstr "Überprüfung der Antwortsignatur fehlgeschlagen. Bitte überprüfe dein Server-Geheimnis."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es erneut."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Please enter license server URL, license key, and server secret."
|
||||
msgstr "Bitte gib die Lizenzserver-URL, den Lizenzschlüssel und das Server-Geheimnis ein."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Validation failed."
|
||||
|
||||
Binary file not shown.
@@ -359,12 +359,33 @@ msgid "Permission denied."
|
||||
msgstr "Zugriff verweigert."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "License key and server URL are required."
|
||||
msgstr "Lizenzschlüssel und Server-URL sind erforderlich."
|
||||
msgid "Server Secret"
|
||||
msgstr "Server-Geheimnis"
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Please enter both license server URL and license key."
|
||||
msgstr "Bitte geben Sie sowohl die Lizenzserver-URL als auch den Lizenzschlüssel ein."
|
||||
msgid "The shared secret for secure communication with the license server."
|
||||
msgstr "Das gemeinsame Geheimnis für die sichere Kommunikation mit dem Lizenzserver."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "License key, server URL, and server secret are required."
|
||||
msgstr "Lizenzschlüssel, Server-URL und Server-Geheimnis sind erforderlich."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
#. translators: %d: Number of seconds to wait
|
||||
msgid "Rate limit exceeded. Please try again in %d seconds."
|
||||
msgstr "Anfragelimit überschritten. Bitte versuchen Sie es in %d Sekunden erneut."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Response signature verification failed. Please check your server secret."
|
||||
msgstr "Überprüfung der Antwortsignatur fehlgeschlagen. Bitte überprüfen Sie Ihr Server-Geheimnis."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es erneut."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Please enter license server URL, license key, and server secret."
|
||||
msgstr "Bitte geben Sie die Lizenzserver-URL, den Lizenzschlüssel und das Server-Geheimnis ein."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Validation failed."
|
||||
|
||||
Binary file not shown.
@@ -359,12 +359,33 @@ msgid "Permission denied."
|
||||
msgstr "Zugriff verweigert."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "License key and server URL are required."
|
||||
msgstr "Lizenzschlüssel und Server-URL sind erforderlich."
|
||||
msgid "Server Secret"
|
||||
msgstr "Server-Geheimnis"
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Please enter both license server URL and license key."
|
||||
msgstr "Bitte gib sowohl die Lizenzserver-URL als auch den Lizenzschlüssel ein."
|
||||
msgid "The shared secret for secure communication with the license server."
|
||||
msgstr "Das gemeinsame Geheimnis für die sichere Kommunikation mit dem Lizenzserver."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "License key, server URL, and server secret are required."
|
||||
msgstr "Lizenzschlüssel, Server-URL und Server-Geheimnis sind erforderlich."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
#. translators: %d: Number of seconds to wait
|
||||
msgid "Rate limit exceeded. Please try again in %d seconds."
|
||||
msgstr "Anfragelimit überschritten. Bitte versuche es in %d Sekunden erneut."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Response signature verification failed. Please check your server secret."
|
||||
msgstr "Überprüfung der Antwortsignatur fehlgeschlagen. Bitte überprüfe dein Server-Geheimnis."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es erneut."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Please enter license server URL, license key, and server secret."
|
||||
msgstr "Bitte gib die Lizenzserver-URL, den Lizenzschlüssel und das Server-Geheimnis ein."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Validation failed."
|
||||
|
||||
Binary file not shown.
@@ -359,12 +359,33 @@ msgid "Permission denied."
|
||||
msgstr "Permission denied."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "License key and server URL are required."
|
||||
msgstr "License key and server URL are required."
|
||||
msgid "Server Secret"
|
||||
msgstr "Server Secret"
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Please enter both license server URL and license key."
|
||||
msgstr "Please enter both license server URL and license key."
|
||||
msgid "The shared secret for secure communication with the license server."
|
||||
msgstr "The shared secret for secure communication with the license server."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "License key, server URL, and server secret are required."
|
||||
msgstr "License key, server URL, and server secret are required."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
#. translators: %d: Number of seconds to wait
|
||||
msgid "Rate limit exceeded. Please try again in %d seconds."
|
||||
msgstr "Rate limit exceeded. Please try again in %d seconds."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Response signature verification failed. Please check your server secret."
|
||||
msgstr "Response signature verification failed. Please check your server secret."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "An unexpected error occurred. Please try again."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Please enter license server URL, license key, and server secret."
|
||||
msgstr "Please enter license server URL, license key, and server secret."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Validation failed."
|
||||
|
||||
Binary file not shown.
@@ -359,12 +359,33 @@ msgid "Permission denied."
|
||||
msgstr "Accès refusé."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "License key and server URL are required."
|
||||
msgstr "La clé de licence et l'URL du serveur sont requises."
|
||||
msgid "Server Secret"
|
||||
msgstr "Secret serveur"
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Please enter both license server URL and license key."
|
||||
msgstr "Veuillez entrer l'URL du serveur de licence et la clé de licence."
|
||||
msgid "The shared secret for secure communication with the license server."
|
||||
msgstr "Le secret partagé pour la communication sécurisée avec le serveur de licence."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "License key, server URL, and server secret are required."
|
||||
msgstr "La clé de licence, l'URL du serveur et le secret serveur sont requis."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
#. translators: %d: Number of seconds to wait
|
||||
msgid "Rate limit exceeded. Please try again in %d seconds."
|
||||
msgstr "Limite de requêtes dépassée. Veuillez réessayer dans %d secondes."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Response signature verification failed. Please check your server secret."
|
||||
msgstr "La vérification de la signature a échoué. Veuillez vérifier votre secret serveur."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Une erreur inattendue s'est produite. Veuillez réessayer."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Please enter license server URL, license key, and server secret."
|
||||
msgstr "Veuillez entrer l'URL du serveur de licence, la clé de licence et le secret serveur."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Validation failed."
|
||||
|
||||
Binary file not shown.
@@ -359,12 +359,33 @@ msgid "Permission denied."
|
||||
msgstr "Accesso negato."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "License key and server URL are required."
|
||||
msgstr "La chiave di licenza e l'URL del server sono obbligatori."
|
||||
msgid "Server Secret"
|
||||
msgstr "Segreto del server"
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Please enter both license server URL and license key."
|
||||
msgstr "Inserisci sia l'URL del server di licenza che la chiave di licenza."
|
||||
msgid "The shared secret for secure communication with the license server."
|
||||
msgstr "Il segreto condiviso per la comunicazione sicura con il server di licenza."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "License key, server URL, and server secret are required."
|
||||
msgstr "La chiave di licenza, l'URL del server e il segreto del server sono obbligatori."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
#. translators: %d: Number of seconds to wait
|
||||
msgid "Rate limit exceeded. Please try again in %d seconds."
|
||||
msgstr "Limite di richieste superato. Per favore riprova tra %d secondi."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Response signature verification failed. Please check your server secret."
|
||||
msgstr "Verifica della firma della risposta fallita. Per favore controlla il tuo segreto del server."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Si è verificato un errore imprevisto. Per favore riprova."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Please enter license server URL, license key, and server secret."
|
||||
msgstr "Inserisci l'URL del server di licenza, la chiave di licenza e il segreto del server."
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Validation failed."
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
# This file is distributed under the GPL v2 or later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: WooCommerce Tier and Package Prices 1.2.7\n"
|
||||
"Project-Id-Version: WooCommerce Tier and Package Prices 1.3.1\n"
|
||||
"Report-Msgid-Bugs-To: https://src.bundespruefstelle.ch/wc-tier-package-prices\n"
|
||||
"POT-Creation-Date: 2025-12-30 00:00+0000\n"
|
||||
"POT-Creation-Date: 2026-01-27 00:00+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
@@ -332,11 +332,32 @@ msgid "Permission denied."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "License key and server URL are required."
|
||||
msgid "Server Secret"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Please enter both license server URL and license key."
|
||||
msgid "The shared secret for secure communication with the license server."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "License key, server URL, and server secret are required."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
#. translators: %d: Number of seconds to wait
|
||||
msgid "Rate limit exceeded. Please try again in %d seconds."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Response signature verification failed. Please check your server secret."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
msgid "Please enter license server URL, license key, and server secret."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-wc-tpp-settings.php
|
||||
|
||||
1
lib/wc-licensed-product-client
Submodule
1
lib/wc-licensed-product-client
Submodule
Submodule lib/wc-licensed-product-client added at 56abe8a97c
@@ -4,7 +4,7 @@
|
||||
* Plugin Name: WooCommerce Tier and Package Prices
|
||||
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-tier-package-prices
|
||||
* Description: Add tier pricing and package prices to WooCommerce products with configurable quantities at fixed prices
|
||||
* Version: 1.3.0
|
||||
* Version: 1.4.0
|
||||
* Author: Marco Graetsch
|
||||
* Author URI: https://src.bundespruefstelle.ch/magdev
|
||||
* Text Domain: wc-tier-package-prices
|
||||
@@ -46,7 +46,7 @@ if (!function_exists('wc_tpp_php_version_notice')) {
|
||||
|
||||
// Define plugin constants
|
||||
if (!defined('WC_TPP_VERSION')) {
|
||||
define('WC_TPP_VERSION', '1.3.0');
|
||||
define('WC_TPP_VERSION', '1.4.0');
|
||||
}
|
||||
if (!defined('WC_TPP_PLUGIN_DIR')) {
|
||||
define('WC_TPP_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||
|
||||
Reference in New Issue
Block a user