Initial plugin setup (v0.0.1)
All checks were successful
Create Release Package / build-release (push) Successful in 1m21s
All checks were successful
Create Release Package / build-release (push) Successful in 1m21s
- Main plugin file with PHP 8.3+ and WordPress 6.0+ version checks - Plugin singleton class with admin menu and settings pages - License Manager integration with SecureLicenseClient - License settings tab with validation and activation - Admin CSS and JavaScript for license management - Gitea CI/CD workflow for automated releases - Documentation: README.md, PLAN.md, CHANGELOG.md, CLAUDE.md - Composer dependencies: Twig 3.0, license client - Git submodule for wc-licensed-product-client library Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
15
.editorconfig
Normal file
15
.editorconfig
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# EditorConfig is awesome: https://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
indent_size = 2
|
||||||
230
.gitea/workflows/release.yml
Normal file
230
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
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 --no-check-lock --no-check-all
|
||||||
|
|
||||||
|
- name: Install Composer dependencies (production)
|
||||||
|
run: |
|
||||||
|
composer config platform.php 8.3.0
|
||||||
|
composer install --no-dev --optimize-autoloader --no-interaction
|
||||||
|
|
||||||
|
- name: Fix vendor symlink
|
||||||
|
run: |
|
||||||
|
# If client is a symlink, replace with actual files
|
||||||
|
if [ -L "vendor/magdev/wc-licensed-product-client" ]; then
|
||||||
|
echo "Found symlink, replacing with actual files..."
|
||||||
|
TARGET=$(readlink -f vendor/magdev/wc-licensed-product-client)
|
||||||
|
rm vendor/magdev/wc-licensed-product-client
|
||||||
|
cp -r "$TARGET" vendor/magdev/wc-licensed-product-client
|
||||||
|
fi
|
||||||
|
ls -la vendor/magdev/
|
||||||
|
|
||||||
|
- 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]+" wp-bnb.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="wp-bnb"
|
||||||
|
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}/.claude/*" \
|
||||||
|
-x "${PLUGIN_NAME}/CLAUDE.md" \
|
||||||
|
-x "${PLUGIN_NAME}/PLAN.md" \
|
||||||
|
-x "${PLUGIN_NAME}/wp-core" \
|
||||||
|
-x "${PLUGIN_NAME}/wp-core/*" \
|
||||||
|
-x "${PLUGIN_NAME}/wp-plugins" \
|
||||||
|
-x "${PLUGIN_NAME}/wp-plugins/*" \
|
||||||
|
-x "${PLUGIN_NAME}/releases/*" \
|
||||||
|
-x "${PLUGIN_NAME}/cache/*" \
|
||||||
|
-x "${PLUGIN_NAME}/composer.lock" \
|
||||||
|
-x "${PLUGIN_NAME}/*.log" \
|
||||||
|
-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}/*.po~" \
|
||||||
|
-x "${PLUGIN_NAME}/*.bak" \
|
||||||
|
-x "${PLUGIN_NAME}/lib/*" \
|
||||||
|
-x "${PLUGIN_NAME}/lib/*/.git/*" \
|
||||||
|
-x "${PLUGIN_NAME}/vendor/magdev/*/.git/*" \
|
||||||
|
-x "${PLUGIN_NAME}/vendor/magdev/*/CLAUDE.md" \
|
||||||
|
-x "*.DS_Store"
|
||||||
|
|
||||||
|
cd "${PLUGIN_NAME}"
|
||||||
|
echo "Created: ${RELEASE_FILE}"
|
||||||
|
ls -lh "${RELEASE_FILE}"
|
||||||
|
|
||||||
|
- name: Generate checksums
|
||||||
|
run: |
|
||||||
|
VERSION=${{ steps.version.outputs.version }}
|
||||||
|
PLUGIN_NAME="wp-bnb"
|
||||||
|
|
||||||
|
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="wp-bnb"
|
||||||
|
|
||||||
|
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: OK"
|
||||||
|
else
|
||||||
|
echo "ERROR: Main plugin file not found!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify vendor directory included
|
||||||
|
if unzip -l "releases/${PLUGIN_NAME}-${VERSION}.zip" | grep -q "${PLUGIN_NAME}/vendor/"; then
|
||||||
|
echo "Vendor directory: OK"
|
||||||
|
else
|
||||||
|
echo "ERROR: Vendor directory not found!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify lib directory excluded
|
||||||
|
if unzip -l "releases/${PLUGIN_NAME}-${VERSION}.zip" | grep -q "${PLUGIN_NAME}/lib/"; then
|
||||||
|
echo "WARNING: lib/ directory should be excluded"
|
||||||
|
else
|
||||||
|
echo "lib/ excluded: OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Extract changelog for release notes
|
||||||
|
id: changelog
|
||||||
|
run: |
|
||||||
|
VERSION=${{ steps.version.outputs.version }}
|
||||||
|
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="wp-bnb"
|
||||||
|
|
||||||
|
PRERELEASE="false"
|
||||||
|
if [[ "$TAG_NAME" == *-* ]]; then
|
||||||
|
PRERELEASE="true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
BODY=$(cat release_notes.txt)
|
||||||
|
|
||||||
|
# Check if release already exists and delete it
|
||||||
|
EXISTING_RELEASE=$(curl -s \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG_NAME}")
|
||||||
|
|
||||||
|
EXISTING_ID=$(echo "$EXISTING_RELEASE" | jq -r '.id // empty')
|
||||||
|
if [ -n "$EXISTING_ID" ] && [ "$EXISTING_ID" != "null" ]; then
|
||||||
|
echo "Deleting existing release ID: $EXISTING_ID"
|
||||||
|
curl -s -X DELETE \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/${EXISTING_ID}"
|
||||||
|
echo "Existing release deleted"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 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: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG_NAME}"
|
||||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# For development purposes
|
||||||
|
# Linked wordpress core and plugin folder
|
||||||
|
wp-plugins
|
||||||
|
wp-core
|
||||||
|
vendor/
|
||||||
|
releases/*
|
||||||
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
|
||||||
38
CHANGELOG.md
Normal file
38
CHANGELOG.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
## [0.0.1] - 2026-01-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Initial plugin structure and configuration
|
||||||
|
- Main plugin file with version checks (PHP 8.3+, WordPress 6.0+)
|
||||||
|
- Plugin singleton class with initialization hooks
|
||||||
|
- License Manager integration with SecureLicenseClient
|
||||||
|
- Admin menu with Dashboard and Settings pages
|
||||||
|
- Settings tabs for General and License configuration
|
||||||
|
- License validation and activation via AJAX
|
||||||
|
- Admin CSS and JavaScript assets
|
||||||
|
- Frontend CSS and JavaScript placeholders
|
||||||
|
- Composer configuration with dependencies:
|
||||||
|
- twig/twig ^3.0
|
||||||
|
- magdev/wc-licensed-product-client ^0.2
|
||||||
|
- PSR-4 autoloading for `Magdev\WpBnb` namespace
|
||||||
|
- Git submodule for license client library
|
||||||
|
- Gitea CI/CD workflow for automated releases
|
||||||
|
- Documentation: README.md, PLAN.md, CHANGELOG.md, CLAUDE.md
|
||||||
|
- Translation-ready strings with `wp-bnb` text domain
|
||||||
|
- Directory structure for templates, assets, and languages
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Nonce verification on all form submissions
|
||||||
|
- Capability checks for admin operations
|
||||||
|
- Input sanitization and output escaping
|
||||||
|
- Server secret masking in license settings
|
||||||
|
|
||||||
|
[0.0.1]: https://src.bundespruefstelle.ch/magdev/wp-bnb/releases/tag/v0.0.1
|
||||||
305
CLAUDE.md
Normal file
305
CLAUDE.md
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
# WordPress BnB Management
|
||||||
|
|
||||||
|
**Author:** Marco Graetsch
|
||||||
|
**Author URL:** <https://src.bundespruefstelle.ch/magdev>
|
||||||
|
**Author Email:** <magdev3.0@gmail.com>
|
||||||
|
**Repository URL:** <https://src.bundespruefstelle.ch/magdev/wp-bnb>
|
||||||
|
**Issues URL:** <https://src.bundespruefstelle.ch/magdev/wp-bnb/issues>
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This plugin enables Wordpress to act as an full management system for BnB hosts. It can handle multiple buildings with multiple rooms each, the booking of the rooms and the guests data required for renting a room. It also can handle different prices for short-, mid- and longterm rentings, configurable by the owner. In future versions - but not yet - there could be an optional possibility to pay the bookings upfront employing parts of WooCommerce, especially the payment and invoicing functions. Each Booking can have multiple additional Service options with either a price tag or can be marked as included.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Full data definition for the owner of the business
|
||||||
|
- Custom Post-types for Buildings and Rooms, while Rooms must be assigned to a building.
|
||||||
|
- Multiple pricing classes for short- (days), mid- (weeks) and longterm (months) bookings.
|
||||||
|
- Fully integrated using Gutenberg-Blocks, Widgets and Shortcodes
|
||||||
|
- Integrates with CF7 for forms (more Form plugins my follow in future versions)
|
||||||
|
|
||||||
|
#### Frontend
|
||||||
|
|
||||||
|
- Advanced Search and filtering for rooms, taking availability into account
|
||||||
|
- **Gutenberg-Blocks:** Building, Room, Booking
|
||||||
|
- **Sidebar Widgets:** Similar Rooms, Buildings
|
||||||
|
- **Shortcodes:** Similar Rooms, Buildings
|
||||||
|
|
||||||
|
#### Administration
|
||||||
|
|
||||||
|
- Dedicated pages for management of buildings, rooms, guests and bookings
|
||||||
|
- Dashboard with relevant statistics
|
||||||
|
|
||||||
|
### Key Fact: 100% AI-Generated
|
||||||
|
|
||||||
|
This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase was created through AI assistance.
|
||||||
|
|
||||||
|
## Temporary Roadmap
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
### Version 0.1.0 - Core Data Structures
|
||||||
|
|
||||||
|
- Custom Post Type: Buildings (address, contact, description, images)
|
||||||
|
- Custom Post Type: Rooms (building reference, capacity, amenities)
|
||||||
|
- Custom Taxonomy: Room Types (Standard, Suite, Family, etc.)
|
||||||
|
- Custom Taxonomy: Amenities (WiFi, Parking, Breakfast, etc.)
|
||||||
|
- Gutenberg blocks for Buildings and Rooms
|
||||||
|
- See `PLAN.md` for full implementation roadmap
|
||||||
|
|
||||||
|
## Technical Stack
|
||||||
|
|
||||||
|
- **Language:** PHP 8.3.x
|
||||||
|
- **PHP-Standards:** PSR-4
|
||||||
|
- **Framework:** Latest WordPress Plugin API
|
||||||
|
- **E-commerce:** WooCommerce 10.0+ (optional, not used yet)
|
||||||
|
- **Template Engine:** Twig 3.0 (via Composer)
|
||||||
|
- **Wordpress Form-Plugin:** CF7 (for Bookings et al)
|
||||||
|
- **Wordpress Base Theme** twentytwentyfive
|
||||||
|
- **Frontend:** Vanilla JavaScript
|
||||||
|
- **Styling:** Custom CSS
|
||||||
|
- **Dependency Management:** Composer
|
||||||
|
- **Internationalization:** WordPress i18n (.pot/.po/.mo files)
|
||||||
|
- **Canonical Plugin Name:** `wp-bnb`
|
||||||
|
- **License Client:** `magdev/wc-licensed-product-client`
|
||||||
|
|
||||||
|
### Security Best Practices
|
||||||
|
|
||||||
|
- All user inputs are sanitized (integers for quantities/prices)
|
||||||
|
- Nonce verification on form submissions
|
||||||
|
- Output escaping in templates (`esc_attr`, `esc_html`, `esc_js`)
|
||||||
|
- Direct file access prevention via `ABSPATH` check
|
||||||
|
- XSS-safe DOM construction in JavaScript (no `innerHTML` with user data)
|
||||||
|
- SQL injection prevention using `$wpdb->prepare()` throughout
|
||||||
|
|
||||||
|
### Translation Ready
|
||||||
|
|
||||||
|
All user-facing strings use:
|
||||||
|
|
||||||
|
```php
|
||||||
|
__('Text to translate', 'wp-bnb')
|
||||||
|
_e('Text to translate', 'wp-bnb')
|
||||||
|
```
|
||||||
|
|
||||||
|
Text domain: `wp-bnb`
|
||||||
|
|
||||||
|
#### Translation Template
|
||||||
|
|
||||||
|
- Base `.pot` file created: `languages/wp-bnb.pot`
|
||||||
|
- Ready for translation to any locale
|
||||||
|
- All translatable strings properly marked with text domain
|
||||||
|
|
||||||
|
#### Available Translations
|
||||||
|
|
||||||
|
- `en_US` - English (United States) [base language - .pot template]
|
||||||
|
- `de_CH` - German (Switzerland, formal)
|
||||||
|
|
||||||
|
To compile translations to .mo files for production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for po in languages/*.po; do msgfmt -o "${po%.po}.mo" "$po"; done
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create releases
|
||||||
|
|
||||||
|
- The `vendor/` directory MUST be included in releases (Dependencies required for runtime)
|
||||||
|
- **CRITICAL**: Build `vendor/` for the MINIMUM supported PHP version, not the development version
|
||||||
|
- Use `composer config platform.php 8.3.0` before building release packages
|
||||||
|
- Run `composer update --no-dev --optimize-autoloader` to rebuild dependencies
|
||||||
|
- **CRITICAL**: WordPress requires plugins in a subdirectory structure
|
||||||
|
- Run zip from the `plugins/` parent directory, NOT from within the plugin directory
|
||||||
|
- Package must extract to `wp-bnb/` subdirectory with main file at `wp-bnb/wp-bnb.php`
|
||||||
|
- Correct command: `cd /wp-content/plugins/ && zip -r wp-bnb/releases/wp-bnb-x.x.x.zip wp-bnb ...`
|
||||||
|
- Wrong: Running zip from inside the plugin directory creates files at root level
|
||||||
|
- **CRITICAL**: Exclude symlinks explicitly - zip follows symlinks by default
|
||||||
|
- Always use `-x "wp-bnb/wp-core" -x "wp-bnb/wp-core/*" -x "wp-bnb/wp-plugins" -x "wp-bnb/wp-plugins/*"` to exclude development symlinks
|
||||||
|
- Otherwise the entire linked directory contents will be included in the package
|
||||||
|
- Exclusion patterns must match the relative path structure used in zip command
|
||||||
|
- Always verify the package structure with `unzip -l` before distribution
|
||||||
|
- Check all files are prefixed with `wp-bnb/`
|
||||||
|
- Verify main file is at `wp-bnb/wp-bnb.php`
|
||||||
|
- Check for duplicate entries (indicates multiple builds in same archive)
|
||||||
|
- Test installation on the minimum supported PHP version before final deployment
|
||||||
|
- Releases are stored in `releases/` including checksums
|
||||||
|
- Track release changes in a single `CHANGELOG.md` file
|
||||||
|
- Bump the version number to either bugfix release versions or on new features minor release versions
|
||||||
|
- **CRITICAL**: WordPress reads version from TWO places - BOTH must be updated:
|
||||||
|
1. Plugin header comment `Version: x.x.x` - WordPress uses THIS for admin display
|
||||||
|
2. PHP constant `WP_BNB_VERSION` (line ~28) - Used internally by the plugin
|
||||||
|
- If only the constant is updated, WordPress will show the old version in Plugins list
|
||||||
|
|
||||||
|
**Important Git Notes:**
|
||||||
|
|
||||||
|
- Default branch while development is `dev`
|
||||||
|
- Create releases from branch `main` after merging branch `dev`
|
||||||
|
- Tags should use format `vX.X.X` (e.g., `v1.1.22`), start with v0.0.1
|
||||||
|
- 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)
|
||||||
|
|
||||||
|
#### What Gets Released
|
||||||
|
|
||||||
|
- All plugin source files
|
||||||
|
- Compiled vendor dependencies
|
||||||
|
- Translation files (.mo compiled from .po)
|
||||||
|
- Assets (CSS, JS)
|
||||||
|
- Documentation (README, CHANGELOG, etc.)
|
||||||
|
|
||||||
|
#### What's Excluded
|
||||||
|
|
||||||
|
- Git metadata (`.git/`)
|
||||||
|
- Development files (`.vscode/`, `.claude/`, `CLAUDE.md`, `wp-core`, `wp-plugins`)
|
||||||
|
- Logs and cache files
|
||||||
|
- Previous releases
|
||||||
|
- `composer.lock` (but `vendor/` is included)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**For AI Assistants:**
|
||||||
|
|
||||||
|
When starting a new session on this project:
|
||||||
|
|
||||||
|
1. Read this CLAUDE.md file first
|
||||||
|
2. Semantic versioning follows the `MAJOR.MINOR.BUGFIX` pattern
|
||||||
|
3. Check git log for recent changes
|
||||||
|
4. Verify you're on the `dev` branch before making changes
|
||||||
|
5. Run `composer install` if vendor/ is missing
|
||||||
|
6. Test changes before committing
|
||||||
|
7. Follow commit message format with Claude Code attribution
|
||||||
|
8. Update this session history section with learnings
|
||||||
|
9. Never commit backup files (`*.po~`, `*.bak`, etc.) - check `git status` before committing
|
||||||
|
10. Follow markdown linting rules (see below)
|
||||||
|
|
||||||
|
Always refer to this document when starting work on this project.
|
||||||
|
|
||||||
|
### Markdown Linting Rules
|
||||||
|
|
||||||
|
When editing CLAUDE.md or other markdown files, follow these rules to avoid linting errors:
|
||||||
|
|
||||||
|
1. **MD031 - Blank lines around fenced code blocks**: Always add a blank line before and after fenced code blocks, even when they follow list items. Example of correct format:
|
||||||
|
|
||||||
|
- **Item label**:
|
||||||
|
|
||||||
|
(blank line here)
|
||||||
|
\`\`\`php
|
||||||
|
code example
|
||||||
|
\`\`\`
|
||||||
|
(blank line here)
|
||||||
|
|
||||||
|
2. **MD056 - Table column count**: Table separators must have matching column counts and proper spacing. Use consistent dash lengths that match column header widths.
|
||||||
|
3. **MD009 - No trailing spaces**: Remove trailing whitespace from lines
|
||||||
|
4. **MD012 - No multiple consecutive blank lines**: Use only single blank lines between sections
|
||||||
|
5. **MD040 - Fenced code blocks should have a language specified**: Always add a language identifier to code blocks (e.g., `txt`, `bash`, `php`). For shortcode examples, use `txt`.
|
||||||
|
6. **MD032 - Lists should be surrounded by blank lines**: Add a blank line before AND after list blocks, including after bold labels like `**Attributes:**`.
|
||||||
|
7. **MD034 - Bare URLs**: Wrap URLs in angle brackets (e.g., `<https://example.com>`) or use markdown link syntax `[text](url)`.
|
||||||
|
8. **Author section formatting**: Use a heading (`### Name`) instead of bold (`**Name**`) for the author name to maintain consistent document structure.
|
||||||
|
|
||||||
|
## Project Architecture
|
||||||
|
|
||||||
|
### Directory Structure
|
||||||
|
|
||||||
|
```text
|
||||||
|
wp-bnb/
|
||||||
|
├── wp-bnb.php # Main plugin file (entry point)
|
||||||
|
├── composer.json # Composer configuration
|
||||||
|
├── composer.lock # Dependency lock file
|
||||||
|
├── CHANGELOG.md # Version history
|
||||||
|
├── CLAUDE.md # AI assistant documentation
|
||||||
|
├── PLAN.md # Implementation roadmap
|
||||||
|
├── README.md # User documentation
|
||||||
|
├── .editorconfig # Editor configuration
|
||||||
|
├── .gitignore # Git ignore patterns
|
||||||
|
├── .gitmodules # Git submodule configuration
|
||||||
|
├── .gitea/
|
||||||
|
│ └── workflows/
|
||||||
|
│ └── release.yml # CI/CD release pipeline
|
||||||
|
├── src/ # PHP source code (PSR-4: Magdev\WpBnb)
|
||||||
|
│ ├── Plugin.php # Main plugin singleton
|
||||||
|
│ └── License/
|
||||||
|
│ └── Manager.php # License management
|
||||||
|
├── lib/ # Git submodules
|
||||||
|
│ └── wc-licensed-product-client/ # License client library
|
||||||
|
├── vendor/ # Composer dependencies (auto-generated)
|
||||||
|
├── assets/
|
||||||
|
│ ├── css/
|
||||||
|
│ │ ├── admin.css # Admin styles
|
||||||
|
│ │ └── frontend.css # Frontend styles
|
||||||
|
│ └── js/
|
||||||
|
│ ├── admin.js # Admin scripts
|
||||||
|
│ └── frontend.js # Frontend scripts
|
||||||
|
├── templates/ # Twig templates (future)
|
||||||
|
├── languages/ # Translation files (future)
|
||||||
|
└── releases/ # Release packages (git-ignored)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementation Details
|
||||||
|
|
||||||
|
#### Main Plugin File (`wp-bnb.php`)
|
||||||
|
|
||||||
|
- Plugin header with metadata
|
||||||
|
- Version constants: `WP_BNB_VERSION`, `WP_BNB_PATH`, `WP_BNB_URL`
|
||||||
|
- PHP 8.3+ and WordPress 6.0+ version checks
|
||||||
|
- Composer autoloader loading
|
||||||
|
- Activation/deactivation hooks
|
||||||
|
|
||||||
|
#### Plugin Class (`src/Plugin.php`)
|
||||||
|
|
||||||
|
- Singleton pattern for global access
|
||||||
|
- Twig environment initialization
|
||||||
|
- Admin menu registration (Dashboard, Settings)
|
||||||
|
- Settings page with tabs (General, License)
|
||||||
|
- Asset enqueuing (admin and frontend)
|
||||||
|
- License-gated frontend components
|
||||||
|
|
||||||
|
#### License Manager (`src/License/Manager.php`)
|
||||||
|
|
||||||
|
- Integration with `SecureLicenseClient` or `LicenseClient`
|
||||||
|
- Option storage for license key, server URL, server secret
|
||||||
|
- License validation with domain binding
|
||||||
|
- License activation with domain
|
||||||
|
- Status caching (24-hour transient)
|
||||||
|
- AJAX handlers for admin operations
|
||||||
|
- Exception handling for all license states
|
||||||
|
|
||||||
|
#### Frontend Restriction Logic
|
||||||
|
|
||||||
|
```php
|
||||||
|
// In Plugin::init_components()
|
||||||
|
if ( ! is_admin() && LicenseManager::is_license_valid() ) {
|
||||||
|
$this->init_frontend(); // Load frontend components
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Admin features always work; frontend requires valid license.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session History
|
||||||
|
|
||||||
|
### 2026-01-31 - Version 0.0.1 (Initial Setup)
|
||||||
|
|
||||||
|
**Completed:**
|
||||||
|
|
||||||
|
- Explored existing project structure (configuration files only)
|
||||||
|
- Added git submodule for `wc-licensed-product-client` at `lib/wc-licensed-product-client`
|
||||||
|
- Installed Composer dependencies (Twig 3.0, license client, Symfony HttpClient)
|
||||||
|
- Studied license client documentation and wp-fedistream implementation
|
||||||
|
- Created main plugin file `wp-bnb.php` with version checks
|
||||||
|
- Created `Plugin` singleton class with admin integration
|
||||||
|
- Created `License\Manager` with SecureLicenseClient integration
|
||||||
|
- Implemented license settings page with validation/activation buttons
|
||||||
|
- Created admin CSS and JavaScript for license management
|
||||||
|
- Created Gitea CI/CD pipeline at `.gitea/workflows/release.yml`
|
||||||
|
- Created `PLAN.md` with full implementation roadmap (8 phases)
|
||||||
|
- Created `README.md` with user documentation
|
||||||
|
- Created `CHANGELOG.md` following Keep a Changelog format
|
||||||
|
- Updated `CLAUDE.md` with architecture details
|
||||||
|
|
||||||
|
**Learnings:**
|
||||||
|
|
||||||
|
- License client requires checkout of specific tag (v0.2.2) for Composer path repository
|
||||||
|
- Relative URL in `.gitmodules` (`../wc-licensed-product-client.git`) works for Gitea hosting
|
||||||
|
- wp-fedistream pattern: `LicenseManager::is_license_valid()` guards frontend initialization
|
||||||
|
- Settings page uses tabs with nonce-protected form submission
|
||||||
|
- AJAX handlers require `check_ajax_referer()` and `current_user_can()` checks
|
||||||
|
- CI/CD workflow excludes `lib/` directory but includes `vendor/` in releases
|
||||||
299
PLAN.md
Normal file
299
PLAN.md
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
# WP BnB Implementation Plan
|
||||||
|
|
||||||
|
This document outlines the implementation plan for the WP BnB Management plugin.
|
||||||
|
|
||||||
|
## Phase 1: Foundation (v0.0.x - v0.1.0)
|
||||||
|
|
||||||
|
### v0.0.1 - Initial Setup (Current)
|
||||||
|
|
||||||
|
- [x] Project structure and configuration files
|
||||||
|
- [x] Composer dependencies (Twig, License Client)
|
||||||
|
- [x] Git submodule for license client
|
||||||
|
- [x] Main plugin file with version checks
|
||||||
|
- [x] Plugin singleton class
|
||||||
|
- [x] License Manager integration
|
||||||
|
- [x] Admin menu and settings pages
|
||||||
|
- [x] CI/CD pipeline for releases
|
||||||
|
- [x] Basic CSS and JS assets
|
||||||
|
- [x] Documentation (README, PLAN, CLAUDE)
|
||||||
|
|
||||||
|
### v0.1.0 - Core Data Structures
|
||||||
|
|
||||||
|
- [ ] Custom Post Type: Buildings
|
||||||
|
- Meta fields: address, contact, description, images
|
||||||
|
- Admin columns and filtering
|
||||||
|
- Gutenberg block for display
|
||||||
|
|
||||||
|
- [ ] Custom Post Type: Rooms
|
||||||
|
- Meta fields: building reference, capacity, amenities, images
|
||||||
|
- Relationship to Buildings (parent)
|
||||||
|
- Admin columns with building filter
|
||||||
|
- Gutenberg block for display
|
||||||
|
|
||||||
|
- [ ] Custom Taxonomy: Room Types
|
||||||
|
- Standard, Suite, Family, Accessible, etc.
|
||||||
|
- Hierarchical structure
|
||||||
|
|
||||||
|
- [ ] Custom Taxonomy: Amenities
|
||||||
|
- WiFi, Parking, Breakfast, etc.
|
||||||
|
- Non-hierarchical (tags)
|
||||||
|
|
||||||
|
## Phase 2: Pricing System (v0.2.0)
|
||||||
|
|
||||||
|
### Pricing Classes
|
||||||
|
|
||||||
|
- [ ] Short-term pricing (per night, 1-6 nights)
|
||||||
|
- [ ] Mid-term pricing (per week, 1-4 weeks)
|
||||||
|
- [ ] Long-term pricing (per month, 1+ months)
|
||||||
|
|
||||||
|
### Price Configuration
|
||||||
|
|
||||||
|
- [ ] Room-level price settings
|
||||||
|
- [ ] Seasonal pricing periods
|
||||||
|
- [ ] Weekend/weekday differentiation
|
||||||
|
- [ ] Currency formatting and display
|
||||||
|
|
||||||
|
### Price Calculation
|
||||||
|
|
||||||
|
- [ ] Automatic tier detection based on duration
|
||||||
|
- [ ] Price breakdown display
|
||||||
|
- [ ] Discount handling
|
||||||
|
|
||||||
|
## Phase 3: Booking System (v0.3.0)
|
||||||
|
|
||||||
|
### Custom Post Type: Bookings
|
||||||
|
|
||||||
|
- [ ] Guest reference
|
||||||
|
- [ ] Room reference
|
||||||
|
- [ ] Check-in/check-out dates
|
||||||
|
- [ ] Status (pending, confirmed, checked-in, checked-out, cancelled)
|
||||||
|
- [ ] Price calculation and storage
|
||||||
|
- [ ] Notes field
|
||||||
|
|
||||||
|
### Calendar Integration
|
||||||
|
|
||||||
|
- [ ] Availability calendar per room
|
||||||
|
- [ ] Availability calendar per building
|
||||||
|
- [ ] Date range picker for bookings
|
||||||
|
- [ ] Conflict detection
|
||||||
|
|
||||||
|
### Booking Workflow
|
||||||
|
|
||||||
|
- [ ] Booking creation (admin)
|
||||||
|
- [ ] Status transitions
|
||||||
|
- [ ] Email notifications
|
||||||
|
- [ ] Booking confirmation
|
||||||
|
|
||||||
|
## Phase 4: Guest Management (v0.4.0)
|
||||||
|
|
||||||
|
### Custom Post Type: Guests
|
||||||
|
|
||||||
|
- [ ] Personal information (name, email, phone)
|
||||||
|
- [ ] Address fields
|
||||||
|
- [ ] ID/Passport information
|
||||||
|
- [ ] Booking history reference
|
||||||
|
- [ ] Notes and preferences
|
||||||
|
|
||||||
|
### Privacy & Compliance
|
||||||
|
|
||||||
|
- [ ] GDPR compliance features
|
||||||
|
- [ ] Data export functionality
|
||||||
|
- [ ] Data deletion on request
|
||||||
|
- [ ] Consent tracking
|
||||||
|
|
||||||
|
## Phase 5: Additional Services (v0.5.0)
|
||||||
|
|
||||||
|
### Service Options
|
||||||
|
|
||||||
|
- [ ] Custom Post Type: Services
|
||||||
|
- [ ] Price per service (or included)
|
||||||
|
- [ ] Per-booking or per-night pricing
|
||||||
|
- [ ] Service categories
|
||||||
|
|
||||||
|
### Booking Services
|
||||||
|
|
||||||
|
- [ ] Service selection during booking
|
||||||
|
- [ ] Automatic price calculation
|
||||||
|
- [ ] Service summary display
|
||||||
|
|
||||||
|
## Phase 6: Frontend Features (v0.6.0)
|
||||||
|
|
||||||
|
### Search & Filtering
|
||||||
|
|
||||||
|
- [ ] Room search with filters
|
||||||
|
- Date range (availability)
|
||||||
|
- Capacity
|
||||||
|
- Room type
|
||||||
|
- Amenities
|
||||||
|
- Price range
|
||||||
|
- Building
|
||||||
|
|
||||||
|
### Display Components
|
||||||
|
|
||||||
|
- [ ] Building list/grid shortcode
|
||||||
|
- [ ] Room list/grid shortcode
|
||||||
|
- [ ] Room detail template
|
||||||
|
- [ ] Availability widget
|
||||||
|
|
||||||
|
### Gutenberg Blocks
|
||||||
|
|
||||||
|
- [ ] Building block
|
||||||
|
- [ ] Room block
|
||||||
|
- [ ] Room search block
|
||||||
|
- [ ] Booking form block
|
||||||
|
|
||||||
|
### Widgets
|
||||||
|
|
||||||
|
- [ ] Similar rooms widget
|
||||||
|
- [ ] Building rooms widget
|
||||||
|
- [ ] Availability calendar widget
|
||||||
|
|
||||||
|
## Phase 7: Contact Form 7 Integration (v0.7.0)
|
||||||
|
|
||||||
|
### Booking Request Form
|
||||||
|
|
||||||
|
- [ ] Custom CF7 tags for rooms/dates
|
||||||
|
- [ ] Form validation
|
||||||
|
- [ ] Booking creation on submission
|
||||||
|
- [ ] Email notifications
|
||||||
|
|
||||||
|
### Inquiry Form
|
||||||
|
|
||||||
|
- [ ] General inquiry handling
|
||||||
|
- [ ] Room-specific inquiries
|
||||||
|
- [ ] Auto-response templates
|
||||||
|
|
||||||
|
## Phase 8: Dashboard & Reports (v0.8.0)
|
||||||
|
|
||||||
|
### Admin Dashboard
|
||||||
|
|
||||||
|
- [ ] Occupancy overview
|
||||||
|
- [ ] Upcoming check-ins/check-outs
|
||||||
|
- [ ] Revenue summary
|
||||||
|
- [ ] Quick actions
|
||||||
|
|
||||||
|
### Reports
|
||||||
|
|
||||||
|
- [ ] Occupancy report
|
||||||
|
- [ ] Revenue report
|
||||||
|
- [ ] Guest statistics
|
||||||
|
- [ ] Export functionality (CSV, PDF)
|
||||||
|
|
||||||
|
## Future Considerations (v1.0.0+)
|
||||||
|
|
||||||
|
### WooCommerce Integration (Optional)
|
||||||
|
|
||||||
|
- [ ] Payment processing
|
||||||
|
- [ ] Invoice generation
|
||||||
|
- [ ] Order management
|
||||||
|
- [ ] Refund handling
|
||||||
|
|
||||||
|
### API Endpoints
|
||||||
|
|
||||||
|
- [ ] REST API for rooms
|
||||||
|
- [ ] REST API for availability
|
||||||
|
- [ ] REST API for bookings
|
||||||
|
- [ ] Authentication and rate limiting
|
||||||
|
|
||||||
|
### Multi-language Support
|
||||||
|
|
||||||
|
- [ ] Full translation support
|
||||||
|
- [ ] WPML compatibility
|
||||||
|
- [ ] Polylang compatibility
|
||||||
|
|
||||||
|
### Advanced Features
|
||||||
|
|
||||||
|
- [ ] Channel manager integration
|
||||||
|
- [ ] iCal sync
|
||||||
|
- [ ] Automated pricing rules
|
||||||
|
- [ ] Loyalty program support
|
||||||
|
|
||||||
|
## Technical Architecture
|
||||||
|
|
||||||
|
### Directory Structure
|
||||||
|
|
||||||
|
```text
|
||||||
|
wp-bnb/
|
||||||
|
├── wp-bnb.php # Main plugin file
|
||||||
|
├── composer.json # Dependencies
|
||||||
|
├── src/ # PHP source (PSR-4)
|
||||||
|
│ ├── Plugin.php # Main plugin class
|
||||||
|
│ ├── License/ # License management
|
||||||
|
│ │ └── Manager.php
|
||||||
|
│ ├── PostTypes/ # Custom post types
|
||||||
|
│ │ ├── Building.php
|
||||||
|
│ │ ├── Room.php
|
||||||
|
│ │ ├── Booking.php
|
||||||
|
│ │ ├── Guest.php
|
||||||
|
│ │ └── Service.php
|
||||||
|
│ ├── Taxonomies/ # Custom taxonomies
|
||||||
|
│ │ ├── RoomType.php
|
||||||
|
│ │ └── Amenity.php
|
||||||
|
│ ├── Admin/ # Admin functionality
|
||||||
|
│ │ ├── Dashboard.php
|
||||||
|
│ │ ├── Settings.php
|
||||||
|
│ │ └── MetaBoxes.php
|
||||||
|
│ ├── Frontend/ # Frontend functionality
|
||||||
|
│ │ ├── Shortcodes.php
|
||||||
|
│ │ ├── Widgets.php
|
||||||
|
│ │ └── Search.php
|
||||||
|
│ ├── Blocks/ # Gutenberg blocks
|
||||||
|
│ │ ├── Building.php
|
||||||
|
│ │ ├── Room.php
|
||||||
|
│ │ └── Search.php
|
||||||
|
│ ├── Pricing/ # Pricing logic
|
||||||
|
│ │ ├── Calculator.php
|
||||||
|
│ │ └── PricingTier.php
|
||||||
|
│ ├── Booking/ # Booking logic
|
||||||
|
│ │ ├── Manager.php
|
||||||
|
│ │ ├── Calendar.php
|
||||||
|
│ │ └── Workflow.php
|
||||||
|
│ └── Integration/ # Third-party integrations
|
||||||
|
│ └── CF7.php
|
||||||
|
├── templates/ # Twig templates
|
||||||
|
│ ├── admin/
|
||||||
|
│ ├── frontend/
|
||||||
|
│ └── email/
|
||||||
|
├── assets/ # CSS, JS, images
|
||||||
|
│ ├── css/
|
||||||
|
│ ├── js/
|
||||||
|
│ └── images/
|
||||||
|
├── languages/ # Translation files
|
||||||
|
├── lib/ # Git submodules
|
||||||
|
│ └── wc-licensed-product-client/
|
||||||
|
└── vendor/ # Composer dependencies
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Tables (Custom)
|
||||||
|
|
||||||
|
For performance with large datasets, custom tables may be added:
|
||||||
|
|
||||||
|
- `{prefix}bnb_availability` - Room availability cache
|
||||||
|
- `{prefix}bnb_prices` - Price history and seasonal rates
|
||||||
|
- `{prefix}bnb_booking_services` - Many-to-many booking/service relation
|
||||||
|
|
||||||
|
### Hooks and Filters
|
||||||
|
|
||||||
|
The plugin will provide extensive hooks for customization:
|
||||||
|
|
||||||
|
- `wp_bnb_before_booking_create`
|
||||||
|
- `wp_bnb_after_booking_create`
|
||||||
|
- `wp_bnb_calculate_price`
|
||||||
|
- `wp_bnb_room_availability`
|
||||||
|
- `wp_bnb_booking_statuses`
|
||||||
|
- `wp_bnb_email_templates`
|
||||||
|
|
||||||
|
## Version Milestones
|
||||||
|
|
||||||
|
| Version | Focus | Target |
|
||||||
|
|---------|-------|--------|
|
||||||
|
| 0.0.1 | Initial setup | Complete |
|
||||||
|
| 0.1.0 | Data structures | TBD |
|
||||||
|
| 0.2.0 | Pricing | TBD |
|
||||||
|
| 0.3.0 | Bookings | TBD |
|
||||||
|
| 0.4.0 | Guests | TBD |
|
||||||
|
| 0.5.0 | Services | TBD |
|
||||||
|
| 0.6.0 | Frontend | TBD |
|
||||||
|
| 0.7.0 | CF7 Integration | TBD |
|
||||||
|
| 0.8.0 | Dashboard | TBD |
|
||||||
|
| 1.0.0 | Stable Release | TBD |
|
||||||
160
README.md
Normal file
160
README.md
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
# WP BnB Management
|
||||||
|
|
||||||
|
A comprehensive Bed & Breakfast management system for WordPress. Manage buildings, rooms, bookings, and guests all within your WordPress admin.
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
WP BnB Management enables WordPress to act as a full management system for B&B hosts. It handles multiple buildings with multiple rooms each, bookings, and guest data required for room rentals.
|
||||||
|
|
||||||
|
### Key Features
|
||||||
|
|
||||||
|
- **Multi-Property Support**: Manage multiple buildings, each with multiple rooms
|
||||||
|
- **Flexible Pricing**: Configure short-term (nights), mid-term (weeks), and long-term (months) pricing
|
||||||
|
- **Booking Management**: Track reservations from inquiry to checkout
|
||||||
|
- **Guest Management**: Store guest information securely with GDPR compliance
|
||||||
|
- **Additional Services**: Offer extras like breakfast, parking, or tours
|
||||||
|
- **Frontend Integration**: Gutenberg blocks, widgets, and shortcodes
|
||||||
|
- **Contact Form 7 Integration**: Accept booking requests through forms
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- WordPress 6.0 or higher
|
||||||
|
- PHP 8.3 or higher
|
||||||
|
- Valid license key
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Download the latest release from the [releases page](https://src.bundespruefstelle.ch/magdev/wp-bnb/releases)
|
||||||
|
2. Upload the plugin to your `/wp-content/plugins/` directory
|
||||||
|
3. Activate the plugin through the 'Plugins' menu in WordPress
|
||||||
|
4. Go to **WP BnB → Settings → License** to enter your license key
|
||||||
|
5. Configure your business settings under **WP BnB → Settings**
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### License Activation
|
||||||
|
|
||||||
|
1. Navigate to **WP BnB → Settings → License**
|
||||||
|
2. Enter your License Server URL
|
||||||
|
3. Enter your License Key (from purchase confirmation)
|
||||||
|
4. Enter the Server Secret (from purchase confirmation)
|
||||||
|
5. Click **Activate License**
|
||||||
|
|
||||||
|
### General Settings
|
||||||
|
|
||||||
|
- **Business Name**: Your B&B business name
|
||||||
|
- **Currency**: Select your preferred currency (CHF, EUR, USD, GBP)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Managing Buildings
|
||||||
|
|
||||||
|
1. Go to **WP BnB → Buildings**
|
||||||
|
2. Click **Add New**
|
||||||
|
3. Enter building details (name, address, description)
|
||||||
|
4. Add featured image
|
||||||
|
5. Publish
|
||||||
|
|
||||||
|
### Managing Rooms
|
||||||
|
|
||||||
|
1. Go to **WP BnB → Rooms**
|
||||||
|
2. Click **Add New**
|
||||||
|
3. Select the parent building
|
||||||
|
4. Configure room details and pricing
|
||||||
|
5. Add amenities and room type
|
||||||
|
6. Publish
|
||||||
|
|
||||||
|
### Managing Bookings
|
||||||
|
|
||||||
|
1. Go to **WP BnB → Bookings**
|
||||||
|
2. View and manage all reservations
|
||||||
|
3. Update booking status as guests progress
|
||||||
|
|
||||||
|
### Managing Guests
|
||||||
|
|
||||||
|
1. Go to **WP BnB → Guests**
|
||||||
|
2. View guest records and booking history
|
||||||
|
3. Manage guest information
|
||||||
|
|
||||||
|
## Shortcodes
|
||||||
|
|
||||||
|
Display buildings and rooms on your site using shortcodes:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
[wp_bnb_buildings]
|
||||||
|
[wp_bnb_rooms building="123"]
|
||||||
|
[wp_bnb_room_search]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gutenberg Blocks
|
||||||
|
|
||||||
|
The following blocks are available in the block editor:
|
||||||
|
|
||||||
|
- **Building** - Display a single building
|
||||||
|
- **Room** - Display a single room
|
||||||
|
- **Room Search** - Search and filter rooms
|
||||||
|
- **Booking Form** - Accept booking requests
|
||||||
|
|
||||||
|
## Widgets
|
||||||
|
|
||||||
|
Available sidebar widgets:
|
||||||
|
|
||||||
|
- **Similar Rooms** - Show rooms similar to the current one
|
||||||
|
- **Building Rooms** - List all rooms in a building
|
||||||
|
|
||||||
|
## Hooks and Filters
|
||||||
|
|
||||||
|
Developers can customize behavior using these hooks:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Modify price calculation
|
||||||
|
add_filter( 'wp_bnb_calculate_price', function( $price, $room_id, $dates ) {
|
||||||
|
// Custom pricing logic
|
||||||
|
return $price;
|
||||||
|
}, 10, 3 );
|
||||||
|
|
||||||
|
// Before booking creation
|
||||||
|
add_action( 'wp_bnb_before_booking_create', function( $booking_data ) {
|
||||||
|
// Custom validation or logging
|
||||||
|
} );
|
||||||
|
```
|
||||||
|
|
||||||
|
## Frequently Asked Questions
|
||||||
|
|
||||||
|
### Do I need a license to use this plugin?
|
||||||
|
|
||||||
|
Yes, a valid license is required to use the frontend features. The admin functionality works without a license for evaluation purposes.
|
||||||
|
|
||||||
|
### Can I manage multiple properties?
|
||||||
|
|
||||||
|
Yes, you can create unlimited buildings and rooms.
|
||||||
|
|
||||||
|
### Is the plugin GDPR compliant?
|
||||||
|
|
||||||
|
Yes, guest data can be exported and deleted on request, and consent is tracked appropriately.
|
||||||
|
|
||||||
|
### Does it integrate with WooCommerce?
|
||||||
|
|
||||||
|
WooCommerce integration for payments is planned for a future release.
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
See [CHANGELOG.md](CHANGELOG.md) for a detailed list of changes.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For support, please create an issue at:
|
||||||
|
<https://src.bundespruefstelle.ch/magdev/wp-bnb/issues>
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
**Marco Graetsch**
|
||||||
|
|
||||||
|
- Website: <https://src.bundespruefstelle.ch/magdev>
|
||||||
|
- Email: <magdev3.0@gmail.com>
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
GPL-2.0-or-later
|
||||||
|
|
||||||
|
This plugin is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or any later version.
|
||||||
81
assets/css/admin.css
Normal file
81
assets/css/admin.css
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
* WP BnB Admin Styles
|
||||||
|
*
|
||||||
|
* @package Magdev\WpBnb
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Dashboard */
|
||||||
|
.wp-bnb-dashboard {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #c3c4c7;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* License Status Badge */
|
||||||
|
.wp-bnb-license-status {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-bnb-license-status .dashicons {
|
||||||
|
font-size: 18px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* License Message */
|
||||||
|
#wp-bnb-license-message {
|
||||||
|
margin: 15px 0;
|
||||||
|
padding: 12px 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wp-bnb-license-message.success {
|
||||||
|
background: #d4edda;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wp-bnb-license-message.error {
|
||||||
|
background: #f8d7da;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spinner */
|
||||||
|
#wp-bnb-license-spinner {
|
||||||
|
float: none;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Settings Tabs */
|
||||||
|
.nav-tab-wrapper {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #c3c4c7;
|
||||||
|
border-top: none;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Tables */
|
||||||
|
.form-table th {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Submit Buttons */
|
||||||
|
.submit {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit .button {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
7
assets/css/frontend.css
Normal file
7
assets/css/frontend.css
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* WP BnB Frontend Styles
|
||||||
|
*
|
||||||
|
* @package Magdev\WpBnb
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Placeholder - Frontend styles will be added as features are implemented */
|
||||||
99
assets/js/admin.js
Normal file
99
assets/js/admin.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/**
|
||||||
|
* WP BnB Admin JavaScript
|
||||||
|
*
|
||||||
|
* @package Magdev\WpBnb
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function($) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize license management functionality.
|
||||||
|
*/
|
||||||
|
function initLicenseManagement() {
|
||||||
|
var $validateBtn = $('#wp-bnb-validate-license');
|
||||||
|
var $activateBtn = $('#wp-bnb-activate-license');
|
||||||
|
var $spinner = $('#wp-bnb-license-spinner');
|
||||||
|
var $message = $('#wp-bnb-license-message');
|
||||||
|
|
||||||
|
if (!$validateBtn.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate license button click.
|
||||||
|
$validateBtn.on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
performLicenseAction('wp_bnb_validate_license', wpBnbAdmin.i18n.validating);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Activate license button click.
|
||||||
|
$activateBtn.on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
performLicenseAction('wp_bnb_activate_license', wpBnbAdmin.i18n.activating);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a license AJAX action.
|
||||||
|
*
|
||||||
|
* @param {string} action AJAX action name.
|
||||||
|
* @param {string} loadingText Loading text to display.
|
||||||
|
*/
|
||||||
|
function performLicenseAction(action, loadingText) {
|
||||||
|
// Disable buttons and show spinner.
|
||||||
|
$validateBtn.prop('disabled', true);
|
||||||
|
$activateBtn.prop('disabled', true);
|
||||||
|
$spinner.addClass('is-active');
|
||||||
|
$message.hide();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: wpBnbAdmin.ajaxUrl,
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
action: action,
|
||||||
|
nonce: wpBnbAdmin.nonce
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
$spinner.removeClass('is-active');
|
||||||
|
$validateBtn.prop('disabled', false);
|
||||||
|
$activateBtn.prop('disabled', false);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
showMessage('success', response.data.message);
|
||||||
|
// Reload page after short delay to show updated status.
|
||||||
|
setTimeout(function() {
|
||||||
|
window.location.reload();
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
showMessage('error', response.data.message || wpBnbAdmin.i18n.error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
$spinner.removeClass('is-active');
|
||||||
|
$validateBtn.prop('disabled', false);
|
||||||
|
$activateBtn.prop('disabled', false);
|
||||||
|
showMessage('error', wpBnbAdmin.i18n.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a message.
|
||||||
|
*
|
||||||
|
* @param {string} type Message type (success or error).
|
||||||
|
* @param {string} message Message text.
|
||||||
|
*/
|
||||||
|
function showMessage(type, message) {
|
||||||
|
$message
|
||||||
|
.removeClass('success error')
|
||||||
|
.addClass(type)
|
||||||
|
.text(message)
|
||||||
|
.fadeIn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize on document ready.
|
||||||
|
$(document).ready(function() {
|
||||||
|
initLicenseManagement();
|
||||||
|
});
|
||||||
|
|
||||||
|
})(jQuery);
|
||||||
12
assets/js/frontend.js
Normal file
12
assets/js/frontend.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* WP BnB Frontend JavaScript
|
||||||
|
*
|
||||||
|
* @package Magdev\WpBnb
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Placeholder - Frontend scripts will be added as features are implemented
|
||||||
|
|
||||||
|
})();
|
||||||
41
composer.json
Normal file
41
composer.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "magdev/wp-bnb",
|
||||||
|
"description": "WordPress BnB management tool",
|
||||||
|
"type": "wordpress-plugin",
|
||||||
|
"license": "GPL-2.0-or-later",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Marco Graetsch",
|
||||||
|
"email": "magdev3.0@gmail.com",
|
||||||
|
"homepage": "https://src.bundespruefstelle.ch/magdev"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "path",
|
||||||
|
"url": "lib/wc-licensed-product-client",
|
||||||
|
"options": {
|
||||||
|
"symlink": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.3.0",
|
||||||
|
"twig/twig": "^3.0",
|
||||||
|
"magdev/wc-licensed-product-client": "^0.2"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Magdev\\WpBnb\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"optimize-autoloader": true,
|
||||||
|
"sort-packages": true,
|
||||||
|
"platform": {
|
||||||
|
"php": "8.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"prefer-stable": true
|
||||||
|
}
|
||||||
991
composer.lock
generated
Normal file
991
composer.lock
generated
Normal file
@@ -0,0 +1,991 @@
|
|||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"content-hash": "aed1e4dd36ea76994768a8379100314b",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "magdev/wc-licensed-product-client",
|
||||||
|
"version": "v0.2.2",
|
||||||
|
"dist": {
|
||||||
|
"type": "path",
|
||||||
|
"url": "lib/wc-licensed-product-client",
|
||||||
|
"reference": "c80aadef9e139540a51dcaefeb071a2f4d2317ed"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"transport-options": {
|
||||||
|
"symlink": false,
|
||||||
|
"relative": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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",
|
||||||
|
"version": "v3.6.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/deprecation-contracts.git",
|
||||||
|
"reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
|
||||||
|
"reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
|
||||||
|
"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": {
|
||||||
|
"files": [
|
||||||
|
"function.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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": "A generic function and convention to trigger deprecation notices",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/deprecation-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": "2024-09-25T14:21:43+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/http-client",
|
||||||
|
"version": "v7.4.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/http-client.git",
|
||||||
|
"reference": "84bb634857a893cc146cceb467e31b3f02c5fe9f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/http-client/zipball/84bb634857a893cc146cceb467e31b3f02c5fe9f",
|
||||||
|
"reference": "84bb634857a893cc146cceb467e31b3f02c5fe9f",
|
||||||
|
"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.5"
|
||||||
|
},
|
||||||
|
"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": "2026-01-27T16:16:02+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",
|
||||||
|
"version": "v1.33.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||||
|
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||||
|
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"ext-ctype": "*"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-ctype": "For best performance"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"thanks": {
|
||||||
|
"url": "https://github.com/symfony/polyfill",
|
||||||
|
"name": "symfony/polyfill"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Ctype\\": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Gert de Pagter",
|
||||||
|
"email": "BackEndTea@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony polyfill for ctype functions",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"ctype",
|
||||||
|
"polyfill",
|
||||||
|
"portable"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-ctype/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": "2024-09-09T11:45:10+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-mbstring",
|
||||||
|
"version": "v1.33.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
|
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
|
||||||
|
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-iconv": "*",
|
||||||
|
"php": ">=7.2"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"ext-mbstring": "*"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-mbstring": "For best performance"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"thanks": {
|
||||||
|
"url": "https://github.com/symfony/polyfill",
|
||||||
|
"name": "symfony/polyfill"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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 for the Mbstring extension",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"mbstring",
|
||||||
|
"polyfill",
|
||||||
|
"portable",
|
||||||
|
"shim"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-mbstring/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": "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",
|
||||||
|
"version": "v3.23.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/twigphp/Twig.git",
|
||||||
|
"reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9",
|
||||||
|
"reference": "a64dc5d2cc7d6cafb9347f6cd802d0d06d0351c9",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1.0",
|
||||||
|
"symfony/deprecation-contracts": "^2.5|^3",
|
||||||
|
"symfony/polyfill-ctype": "^1.8",
|
||||||
|
"symfony/polyfill-mbstring": "^1.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"psr/container": "^1.0|^2.0",
|
||||||
|
"symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/Resources/core.php",
|
||||||
|
"src/Resources/debug.php",
|
||||||
|
"src/Resources/escaper.php",
|
||||||
|
"src/Resources/string_loader.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Twig\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com",
|
||||||
|
"homepage": "http://fabien.potencier.org",
|
||||||
|
"role": "Lead Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Twig Team",
|
||||||
|
"role": "Contributors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Armin Ronacher",
|
||||||
|
"email": "armin.ronacher@active-4.com",
|
||||||
|
"role": "Project Founder"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Twig, the flexible, fast, and secure template language for PHP",
|
||||||
|
"homepage": "https://twig.symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"templating"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/twigphp/Twig/issues",
|
||||||
|
"source": "https://github.com/twigphp/Twig/tree/v3.23.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-01-23T21:00:41+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": {},
|
||||||
|
"prefer-stable": true,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": {
|
||||||
|
"php": ">=8.3.0"
|
||||||
|
},
|
||||||
|
"platform-dev": {},
|
||||||
|
"platform-overrides": {
|
||||||
|
"php": "8.3.0"
|
||||||
|
},
|
||||||
|
"plugin-api-version": "2.6.0"
|
||||||
|
}
|
||||||
1
lib/wc-licensed-product-client
Submodule
1
lib/wc-licensed-product-client
Submodule
Submodule lib/wc-licensed-product-client added at 56abe8a97c
533
src/License/Manager.php
Normal file
533
src/License/Manager.php
Normal file
@@ -0,0 +1,533 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* License Manager class.
|
||||||
|
*
|
||||||
|
* @package Magdev\WpBnb\License
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare( strict_types=1 );
|
||||||
|
|
||||||
|
namespace Magdev\WpBnb\License;
|
||||||
|
|
||||||
|
use Magdev\WcLicensedProductClient\SecureLicenseClient;
|
||||||
|
use Magdev\WcLicensedProductClient\LicenseClient;
|
||||||
|
use Magdev\WcLicensedProductClient\DTO\LicenseState;
|
||||||
|
use Magdev\WcLicensedProductClient\Exception\LicenseNotFoundException;
|
||||||
|
use Magdev\WcLicensedProductClient\Exception\LicenseExpiredException;
|
||||||
|
use Magdev\WcLicensedProductClient\Exception\LicenseRevokedException;
|
||||||
|
use Magdev\WcLicensedProductClient\Exception\LicenseInactiveException;
|
||||||
|
use Magdev\WcLicensedProductClient\Exception\DomainMismatchException;
|
||||||
|
use Magdev\WcLicensedProductClient\Exception\MaxActivationsReachedException;
|
||||||
|
use Magdev\WcLicensedProductClient\Exception\RateLimitExceededException;
|
||||||
|
use Magdev\WcLicensedProductClient\Security\SignatureException;
|
||||||
|
use Symfony\Component\HttpClient\HttpClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages license validation and activation.
|
||||||
|
*/
|
||||||
|
final class Manager {
|
||||||
|
|
||||||
|
// Option keys.
|
||||||
|
public const OPTION_LICENSE_KEY = 'wp_bnb_license_key';
|
||||||
|
public const OPTION_SERVER_URL = 'wp_bnb_license_server_url';
|
||||||
|
public const OPTION_SERVER_SECRET = 'wp_bnb_license_server_secret';
|
||||||
|
public const OPTION_LICENSE_STATUS = 'wp_bnb_license_status';
|
||||||
|
public const OPTION_LICENSE_DATA = 'wp_bnb_license_data';
|
||||||
|
public const OPTION_LAST_CHECK = 'wp_bnb_license_last_check';
|
||||||
|
|
||||||
|
// Transient keys.
|
||||||
|
private const TRANSIENT_LICENSE_CHECK = 'wp_bnb_license_check';
|
||||||
|
|
||||||
|
// Cache TTL (24 hours).
|
||||||
|
private const CACHE_TTL = 86400;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton instance.
|
||||||
|
*
|
||||||
|
* @var Manager|null
|
||||||
|
*/
|
||||||
|
private static ?Manager $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* License client.
|
||||||
|
*
|
||||||
|
* @var SecureLicenseClient|LicenseClient|null
|
||||||
|
*/
|
||||||
|
private SecureLicenseClient|LicenseClient|null $client = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get singleton instance.
|
||||||
|
*
|
||||||
|
* @return Manager
|
||||||
|
*/
|
||||||
|
public static function get_instance(): Manager {
|
||||||
|
if ( null === self::$instance ) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor.
|
||||||
|
*/
|
||||||
|
private function __construct() {
|
||||||
|
$this->init_hooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize WordPress hooks.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function init_hooks(): void {
|
||||||
|
// AJAX handlers.
|
||||||
|
add_action( 'wp_ajax_wp_bnb_validate_license', array( $this, 'ajax_validate_license' ) );
|
||||||
|
add_action( 'wp_ajax_wp_bnb_activate_license', array( $this, 'ajax_activate_license' ) );
|
||||||
|
add_action( 'wp_ajax_wp_bnb_check_status', array( $this, 'ajax_check_status' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the license client.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function init_client(): bool {
|
||||||
|
if ( null !== $this->client ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$server_url = self::get_server_url();
|
||||||
|
$server_secret = self::get_server_secret();
|
||||||
|
|
||||||
|
if ( empty( $server_url ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use SecureLicenseClient if server secret is configured.
|
||||||
|
if ( ! empty( $server_secret ) ) {
|
||||||
|
$this->client = new SecureLicenseClient(
|
||||||
|
httpClient: HttpClient::create(),
|
||||||
|
baseUrl: $server_url,
|
||||||
|
serverSecret: $server_secret,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->client = new LicenseClient(
|
||||||
|
httpClient: HttpClient::create(),
|
||||||
|
baseUrl: $server_url,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch ( \Throwable $e ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if license is valid.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function is_license_valid(): bool {
|
||||||
|
$status = get_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
|
||||||
|
return 'valid' === $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get license key.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function get_license_key(): string {
|
||||||
|
return get_option( self::OPTION_LICENSE_KEY, '' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get server URL.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function get_server_url(): string {
|
||||||
|
return get_option( self::OPTION_SERVER_URL, '' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get server secret.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function get_server_secret(): string {
|
||||||
|
return get_option( self::OPTION_SERVER_SECRET, '' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cached license status.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function get_cached_status(): string {
|
||||||
|
$server_url = self::get_server_url();
|
||||||
|
$license_key = self::get_license_key();
|
||||||
|
|
||||||
|
if ( empty( $server_url ) || empty( $license_key ) ) {
|
||||||
|
return 'unconfigured';
|
||||||
|
}
|
||||||
|
|
||||||
|
return get_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cached license data.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function get_cached_data(): array {
|
||||||
|
return get_option( self::OPTION_LICENSE_DATA, array() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get last check timestamp.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function get_last_check(): int {
|
||||||
|
return (int) get_option( self::OPTION_LAST_CHECK, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save license settings.
|
||||||
|
*
|
||||||
|
* @param array $data Settings data.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function save_settings( array $data ): bool {
|
||||||
|
if ( isset( $data['license_key'] ) ) {
|
||||||
|
update_option( self::OPTION_LICENSE_KEY, sanitize_text_field( $data['license_key'] ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $data['server_url'] ) ) {
|
||||||
|
update_option( self::OPTION_SERVER_URL, esc_url_raw( $data['server_url'] ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only update secret if provided (not empty).
|
||||||
|
if ( ! empty( $data['server_secret'] ) ) {
|
||||||
|
update_option( self::OPTION_SERVER_SECRET, sanitize_text_field( $data['server_secret'] ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear cached status when settings change.
|
||||||
|
delete_transient( self::TRANSIENT_LICENSE_CHECK );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all license data.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function clear_license_data(): void {
|
||||||
|
update_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
|
||||||
|
update_option( self::OPTION_LICENSE_DATA, array() );
|
||||||
|
update_option( self::OPTION_LAST_CHECK, 0 );
|
||||||
|
delete_transient( self::TRANSIENT_LICENSE_CHECK );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate license.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function validate(): array {
|
||||||
|
if ( ! $this->init_client() ) {
|
||||||
|
return array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => __( 'License client could not be initialized. Please check server URL and secret.', 'wp-bnb' ),
|
||||||
|
'status' => 'unconfigured',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$license_key = self::get_license_key();
|
||||||
|
if ( empty( $license_key ) ) {
|
||||||
|
return array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => __( 'Please enter a license key.', 'wp-bnb' ),
|
||||||
|
'status' => 'unconfigured',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$domain = $this->get_current_domain();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$license_info = $this->client->validate( $license_key, $domain );
|
||||||
|
|
||||||
|
// Store license data.
|
||||||
|
$license_data = array(
|
||||||
|
'product_id' => $license_info->productId,
|
||||||
|
'expires' => $license_info->expiresAt?->format( 'Y-m-d H:i:s' ),
|
||||||
|
);
|
||||||
|
|
||||||
|
update_option( self::OPTION_LICENSE_STATUS, 'valid' );
|
||||||
|
update_option( self::OPTION_LICENSE_DATA, $license_data );
|
||||||
|
update_option( self::OPTION_LAST_CHECK, time() );
|
||||||
|
set_transient( self::TRANSIENT_LICENSE_CHECK, 'valid', self::CACHE_TTL );
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'success' => true,
|
||||||
|
'message' => __( 'License is valid.', 'wp-bnb' ),
|
||||||
|
'status' => 'valid',
|
||||||
|
'data' => $license_data,
|
||||||
|
);
|
||||||
|
} catch ( LicenseNotFoundException $e ) {
|
||||||
|
return $this->handle_license_error( 'invalid', __( 'License key not found.', 'wp-bnb' ) );
|
||||||
|
} catch ( LicenseExpiredException $e ) {
|
||||||
|
return $this->handle_license_error( 'expired', __( 'License has expired.', 'wp-bnb' ) );
|
||||||
|
} catch ( LicenseRevokedException $e ) {
|
||||||
|
return $this->handle_license_error( 'revoked', __( 'License has been revoked.', 'wp-bnb' ) );
|
||||||
|
} catch ( LicenseInactiveException $e ) {
|
||||||
|
return $this->handle_license_error( 'inactive', __( 'License is inactive. Please activate it first.', 'wp-bnb' ) );
|
||||||
|
} catch ( DomainMismatchException $e ) {
|
||||||
|
return $this->handle_license_error( 'invalid', __( 'License is not valid for this domain.', 'wp-bnb' ) );
|
||||||
|
} catch ( SignatureException $e ) {
|
||||||
|
return $this->handle_license_error( 'invalid', __( 'Response signature verification failed. Please check server secret.', 'wp-bnb' ) );
|
||||||
|
} catch ( RateLimitExceededException $e ) {
|
||||||
|
return array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => sprintf(
|
||||||
|
/* translators: %d: Seconds to wait */
|
||||||
|
__( 'Rate limit exceeded. Please try again in %d seconds.', 'wp-bnb' ),
|
||||||
|
$e->retryAfter
|
||||||
|
),
|
||||||
|
'status' => self::get_cached_status(),
|
||||||
|
);
|
||||||
|
} catch ( \Throwable $e ) {
|
||||||
|
return $this->handle_license_error( 'invalid', __( 'An error occurred while validating the license.', 'wp-bnb' ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate license.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function activate(): array {
|
||||||
|
if ( ! $this->init_client() ) {
|
||||||
|
return array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => __( 'License client could not be initialized. Please check server URL and secret.', 'wp-bnb' ),
|
||||||
|
'status' => 'unconfigured',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$license_key = self::get_license_key();
|
||||||
|
if ( empty( $license_key ) ) {
|
||||||
|
return array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => __( 'Please enter a license key.', 'wp-bnb' ),
|
||||||
|
'status' => 'unconfigured',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$domain = $this->get_current_domain();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = $this->client->activate( $license_key, $domain );
|
||||||
|
|
||||||
|
if ( $result->success ) {
|
||||||
|
// Validate after activation to get full license info.
|
||||||
|
return $this->validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => __( 'Activation failed.', 'wp-bnb' ),
|
||||||
|
'status' => 'inactive',
|
||||||
|
);
|
||||||
|
} catch ( MaxActivationsReachedException $e ) {
|
||||||
|
return $this->handle_license_error( 'invalid', __( 'Maximum activations reached for this license.', 'wp-bnb' ) );
|
||||||
|
} catch ( LicenseNotFoundException $e ) {
|
||||||
|
return $this->handle_license_error( 'invalid', __( 'License key not found.', 'wp-bnb' ) );
|
||||||
|
} catch ( LicenseExpiredException $e ) {
|
||||||
|
return $this->handle_license_error( 'expired', __( 'License has expired.', 'wp-bnb' ) );
|
||||||
|
} catch ( SignatureException $e ) {
|
||||||
|
return $this->handle_license_error( 'invalid', __( 'Response signature verification failed. Please check server secret.', 'wp-bnb' ) );
|
||||||
|
} catch ( RateLimitExceededException $e ) {
|
||||||
|
return array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => sprintf(
|
||||||
|
/* translators: %d: Seconds to wait */
|
||||||
|
__( 'Rate limit exceeded. Please try again in %d seconds.', 'wp-bnb' ),
|
||||||
|
$e->retryAfter
|
||||||
|
),
|
||||||
|
'status' => self::get_cached_status(),
|
||||||
|
);
|
||||||
|
} catch ( \Throwable $e ) {
|
||||||
|
return $this->handle_license_error( 'invalid', __( 'An error occurred while activating the license.', 'wp-bnb' ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get license status.
|
||||||
|
*
|
||||||
|
* @param bool $force_refresh Whether to force a refresh from server.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_status( bool $force_refresh = false ): array {
|
||||||
|
// Check transient cache first.
|
||||||
|
if ( ! $force_refresh ) {
|
||||||
|
$cached = get_transient( self::TRANSIENT_LICENSE_CHECK );
|
||||||
|
if ( false !== $cached ) {
|
||||||
|
return array(
|
||||||
|
'success' => 'valid' === $cached,
|
||||||
|
'status' => $cached,
|
||||||
|
'data' => self::get_cached_data(),
|
||||||
|
'cached' => true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $this->init_client() ) {
|
||||||
|
return array(
|
||||||
|
'success' => false,
|
||||||
|
'status' => 'unconfigured',
|
||||||
|
'message' => __( 'License client not configured.', 'wp-bnb' ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$license_key = self::get_license_key();
|
||||||
|
if ( empty( $license_key ) ) {
|
||||||
|
return array(
|
||||||
|
'success' => false,
|
||||||
|
'status' => 'unconfigured',
|
||||||
|
'message' => __( 'No license key configured.', 'wp-bnb' ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$status_info = $this->client->status( $license_key );
|
||||||
|
|
||||||
|
$status = match ( $status_info->status ) {
|
||||||
|
LicenseState::Active => 'valid',
|
||||||
|
LicenseState::Inactive => 'inactive',
|
||||||
|
LicenseState::Expired => 'expired',
|
||||||
|
LicenseState::Revoked => 'revoked',
|
||||||
|
};
|
||||||
|
|
||||||
|
update_option( self::OPTION_LICENSE_STATUS, $status );
|
||||||
|
update_option( self::OPTION_LAST_CHECK, time() );
|
||||||
|
set_transient( self::TRANSIENT_LICENSE_CHECK, $status, self::CACHE_TTL );
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'success' => 'valid' === $status,
|
||||||
|
'status' => $status,
|
||||||
|
'data' => self::get_cached_data(),
|
||||||
|
'cached' => false,
|
||||||
|
);
|
||||||
|
} catch ( \Throwable $e ) {
|
||||||
|
return array(
|
||||||
|
'success' => false,
|
||||||
|
'status' => 'invalid',
|
||||||
|
'message' => __( 'Failed to check license status.', 'wp-bnb' ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle license error.
|
||||||
|
*
|
||||||
|
* @param string $status Status code.
|
||||||
|
* @param string $message Error message.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function handle_license_error( string $status, string $message ): array {
|
||||||
|
update_option( self::OPTION_LICENSE_STATUS, $status );
|
||||||
|
update_option( self::OPTION_LAST_CHECK, time() );
|
||||||
|
delete_transient( self::TRANSIENT_LICENSE_CHECK );
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => $message,
|
||||||
|
'status' => $status,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current domain.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function get_current_domain(): string {
|
||||||
|
$site_url = get_site_url();
|
||||||
|
$parsed = wp_parse_url( $site_url );
|
||||||
|
return $parsed['host'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX handler: Validate license.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function ajax_validate_license(): void {
|
||||||
|
check_ajax_referer( 'wp_bnb_admin_nonce', 'nonce' );
|
||||||
|
|
||||||
|
if ( ! current_user_can( 'manage_options' ) ) {
|
||||||
|
wp_send_json_error( array(
|
||||||
|
'message' => __( 'You do not have permission to perform this action.', 'wp-bnb' ),
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->validate();
|
||||||
|
|
||||||
|
if ( $result['success'] ) {
|
||||||
|
wp_send_json_success( $result );
|
||||||
|
} else {
|
||||||
|
wp_send_json_error( $result );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX handler: Activate license.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function ajax_activate_license(): void {
|
||||||
|
check_ajax_referer( 'wp_bnb_admin_nonce', 'nonce' );
|
||||||
|
|
||||||
|
if ( ! current_user_can( 'manage_options' ) ) {
|
||||||
|
wp_send_json_error( array(
|
||||||
|
'message' => __( 'You do not have permission to perform this action.', 'wp-bnb' ),
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->activate();
|
||||||
|
|
||||||
|
if ( $result['success'] ) {
|
||||||
|
wp_send_json_success( $result );
|
||||||
|
} else {
|
||||||
|
wp_send_json_error( $result );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX handler: Check status.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function ajax_check_status(): void {
|
||||||
|
check_ajax_referer( 'wp_bnb_admin_nonce', 'nonce' );
|
||||||
|
|
||||||
|
if ( ! current_user_can( 'manage_options' ) ) {
|
||||||
|
wp_send_json_error( array(
|
||||||
|
'message' => __( 'You do not have permission to perform this action.', 'wp-bnb' ),
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$force_refresh = isset( $_POST['force'] ) && 'true' === $_POST['force'];
|
||||||
|
$result = $this->get_status( $force_refresh );
|
||||||
|
|
||||||
|
if ( $result['success'] ) {
|
||||||
|
wp_send_json_success( $result );
|
||||||
|
} else {
|
||||||
|
wp_send_json_error( $result );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
617
src/Plugin.php
Normal file
617
src/Plugin.php
Normal file
@@ -0,0 +1,617 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Main Plugin class.
|
||||||
|
*
|
||||||
|
* @package Magdev\WpBnb
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare( strict_types=1 );
|
||||||
|
|
||||||
|
namespace Magdev\WpBnb;
|
||||||
|
|
||||||
|
use Magdev\WpBnb\License\Manager as LicenseManager;
|
||||||
|
use Twig\Environment;
|
||||||
|
use Twig\Loader\FilesystemLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main Plugin singleton class.
|
||||||
|
*/
|
||||||
|
final class Plugin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin instance.
|
||||||
|
*
|
||||||
|
* @var Plugin|null
|
||||||
|
*/
|
||||||
|
private static ?Plugin $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Twig environment.
|
||||||
|
*
|
||||||
|
* @var Environment|null
|
||||||
|
*/
|
||||||
|
private ?Environment $twig = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get plugin instance.
|
||||||
|
*
|
||||||
|
* @return Plugin
|
||||||
|
*/
|
||||||
|
public static function get_instance(): Plugin {
|
||||||
|
if ( null === self::$instance ) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor to enforce singleton.
|
||||||
|
*/
|
||||||
|
private function __construct() {
|
||||||
|
$this->init_hooks();
|
||||||
|
$this->init_components();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize WordPress hooks.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function init_hooks(): void {
|
||||||
|
// Load text domain.
|
||||||
|
add_action( 'init', array( $this, 'load_textdomain' ) );
|
||||||
|
|
||||||
|
// Register assets.
|
||||||
|
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
|
||||||
|
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_assets' ) );
|
||||||
|
|
||||||
|
// Add plugin action links.
|
||||||
|
add_filter( 'plugin_action_links_' . WP_BNB_BASENAME, array( $this, 'add_action_links' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize plugin components.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function init_components(): void {
|
||||||
|
// Initialize License Manager (always active for admin).
|
||||||
|
LicenseManager::get_instance();
|
||||||
|
|
||||||
|
// Initialize admin components.
|
||||||
|
if ( is_admin() ) {
|
||||||
|
$this->init_admin();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize frontend components only if licensed.
|
||||||
|
if ( ! is_admin() && LicenseManager::is_license_valid() ) {
|
||||||
|
$this->init_frontend();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize admin components.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function init_admin(): void {
|
||||||
|
// Admin menu and settings will be added here.
|
||||||
|
add_action( 'admin_menu', array( $this, 'register_admin_menu' ) );
|
||||||
|
add_action( 'admin_init', array( $this, 'register_settings' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize frontend components.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function init_frontend(): void {
|
||||||
|
// Frontend shortcodes, blocks, and widgets will be added here.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load plugin text domain.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function load_textdomain(): void {
|
||||||
|
load_plugin_textdomain(
|
||||||
|
'wp-bnb',
|
||||||
|
false,
|
||||||
|
dirname( WP_BNB_BASENAME ) . '/languages'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueue admin assets.
|
||||||
|
*
|
||||||
|
* @param string $hook_suffix Current admin page hook.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function enqueue_admin_assets( string $hook_suffix ): void {
|
||||||
|
// Only load on plugin pages.
|
||||||
|
if ( strpos( $hook_suffix, 'wp-bnb' ) === false ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_enqueue_style(
|
||||||
|
'wp-bnb-admin',
|
||||||
|
WP_BNB_URL . 'assets/css/admin.css',
|
||||||
|
array(),
|
||||||
|
WP_BNB_VERSION
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_enqueue_script(
|
||||||
|
'wp-bnb-admin',
|
||||||
|
WP_BNB_URL . 'assets/js/admin.js',
|
||||||
|
array( 'jquery' ),
|
||||||
|
WP_BNB_VERSION,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_localize_script(
|
||||||
|
'wp-bnb-admin',
|
||||||
|
'wpBnbAdmin',
|
||||||
|
array(
|
||||||
|
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
||||||
|
'nonce' => wp_create_nonce( 'wp_bnb_admin_nonce' ),
|
||||||
|
'i18n' => array(
|
||||||
|
'validating' => __( 'Validating...', 'wp-bnb' ),
|
||||||
|
'activating' => __( 'Activating...', 'wp-bnb' ),
|
||||||
|
'error' => __( 'An error occurred. Please try again.', 'wp-bnb' ),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueue frontend assets.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function enqueue_frontend_assets(): void {
|
||||||
|
// Only load if licensed.
|
||||||
|
if ( ! LicenseManager::is_license_valid() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_enqueue_style(
|
||||||
|
'wp-bnb-frontend',
|
||||||
|
WP_BNB_URL . 'assets/css/frontend.css',
|
||||||
|
array(),
|
||||||
|
WP_BNB_VERSION
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_enqueue_script(
|
||||||
|
'wp-bnb-frontend',
|
||||||
|
WP_BNB_URL . 'assets/js/frontend.js',
|
||||||
|
array(),
|
||||||
|
WP_BNB_VERSION,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add plugin action links.
|
||||||
|
*
|
||||||
|
* @param array $links Existing plugin links.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function add_action_links( array $links ): array {
|
||||||
|
$plugin_links = array(
|
||||||
|
'<a href="' . esc_url( admin_url( 'admin.php?page=wp-bnb-settings' ) ) . '">' . esc_html__( 'Settings', 'wp-bnb' ) . '</a>',
|
||||||
|
);
|
||||||
|
return array_merge( $plugin_links, $links );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register admin menu.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function register_admin_menu(): void {
|
||||||
|
// Main menu.
|
||||||
|
add_menu_page(
|
||||||
|
__( 'WP BnB', 'wp-bnb' ),
|
||||||
|
__( 'WP BnB', 'wp-bnb' ),
|
||||||
|
'manage_options',
|
||||||
|
'wp-bnb',
|
||||||
|
array( $this, 'render_dashboard_page' ),
|
||||||
|
'dashicons-building',
|
||||||
|
30
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dashboard submenu.
|
||||||
|
add_submenu_page(
|
||||||
|
'wp-bnb',
|
||||||
|
__( 'Dashboard', 'wp-bnb' ),
|
||||||
|
__( 'Dashboard', 'wp-bnb' ),
|
||||||
|
'manage_options',
|
||||||
|
'wp-bnb',
|
||||||
|
array( $this, 'render_dashboard_page' )
|
||||||
|
);
|
||||||
|
|
||||||
|
// Settings submenu.
|
||||||
|
add_submenu_page(
|
||||||
|
'wp-bnb',
|
||||||
|
__( 'Settings', 'wp-bnb' ),
|
||||||
|
__( 'Settings', 'wp-bnb' ),
|
||||||
|
'manage_options',
|
||||||
|
'wp-bnb-settings',
|
||||||
|
array( $this, 'render_settings_page' )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register plugin settings.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function register_settings(): void {
|
||||||
|
// License settings are handled by LicenseManager.
|
||||||
|
// Additional settings will be added here.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render dashboard page.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function render_dashboard_page(): void {
|
||||||
|
$license_status = LicenseManager::get_cached_status();
|
||||||
|
?>
|
||||||
|
<div class="wrap">
|
||||||
|
<h1><?php esc_html_e( 'WP BnB Dashboard', 'wp-bnb' ); ?></h1>
|
||||||
|
|
||||||
|
<?php if ( 'valid' !== $license_status ) : ?>
|
||||||
|
<div class="notice notice-warning">
|
||||||
|
<p>
|
||||||
|
<?php
|
||||||
|
printf(
|
||||||
|
/* translators: %s: Link to settings page */
|
||||||
|
esc_html__( 'Your license is not active. Please %s to unlock all features.', 'wp-bnb' ),
|
||||||
|
'<a href="' . esc_url( admin_url( 'admin.php?page=wp-bnb-settings&tab=license' ) ) . '">' . esc_html__( 'activate your license', 'wp-bnb' ) . '</a>'
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="wp-bnb-dashboard">
|
||||||
|
<p><?php esc_html_e( 'Welcome to WP BnB Management. Use the menu on the left to manage your buildings, rooms, bookings, and guests.', 'wp-bnb' ); ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render settings page.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function render_settings_page(): void {
|
||||||
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Tab switching only.
|
||||||
|
$active_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general';
|
||||||
|
|
||||||
|
// Handle form submission.
|
||||||
|
if ( isset( $_POST['wp_bnb_settings_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['wp_bnb_settings_nonce'] ), 'wp_bnb_save_settings' ) ) {
|
||||||
|
$this->save_settings( $active_tab );
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class="wrap">
|
||||||
|
<h1><?php esc_html_e( 'WP BnB Settings', 'wp-bnb' ); ?></h1>
|
||||||
|
|
||||||
|
<nav class="nav-tab-wrapper">
|
||||||
|
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-settings&tab=general' ) ); ?>"
|
||||||
|
class="nav-tab <?php echo 'general' === $active_tab ? 'nav-tab-active' : ''; ?>">
|
||||||
|
<?php esc_html_e( 'General', 'wp-bnb' ); ?>
|
||||||
|
</a>
|
||||||
|
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-settings&tab=license' ) ); ?>"
|
||||||
|
class="nav-tab <?php echo 'license' === $active_tab ? 'nav-tab-active' : ''; ?>">
|
||||||
|
<?php esc_html_e( 'License', 'wp-bnb' ); ?>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="tab-content">
|
||||||
|
<?php
|
||||||
|
switch ( $active_tab ) {
|
||||||
|
case 'license':
|
||||||
|
$this->render_license_settings();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$this->render_general_settings();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render general settings tab.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function render_general_settings(): void {
|
||||||
|
?>
|
||||||
|
<form method="post" action="">
|
||||||
|
<?php wp_nonce_field( 'wp_bnb_save_settings', 'wp_bnb_settings_nonce' ); ?>
|
||||||
|
|
||||||
|
<table class="form-table" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="wp_bnb_business_name"><?php esc_html_e( 'Business Name', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="wp_bnb_business_name" id="wp_bnb_business_name"
|
||||||
|
value="<?php echo esc_attr( get_option( 'wp_bnb_business_name', '' ) ); ?>"
|
||||||
|
class="regular-text">
|
||||||
|
<p class="description"><?php esc_html_e( 'The name of your B&B business.', 'wp-bnb' ); ?></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="wp_bnb_currency"><?php esc_html_e( 'Currency', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<select name="wp_bnb_currency" id="wp_bnb_currency">
|
||||||
|
<?php
|
||||||
|
$currencies = array(
|
||||||
|
'CHF' => __( 'Swiss Franc (CHF)', 'wp-bnb' ),
|
||||||
|
'EUR' => __( 'Euro (EUR)', 'wp-bnb' ),
|
||||||
|
'USD' => __( 'US Dollar (USD)', 'wp-bnb' ),
|
||||||
|
'GBP' => __( 'British Pound (GBP)', 'wp-bnb' ),
|
||||||
|
);
|
||||||
|
$current = get_option( 'wp_bnb_currency', 'CHF' );
|
||||||
|
foreach ( $currencies as $code => $label ) :
|
||||||
|
?>
|
||||||
|
<option value="<?php echo esc_attr( $code ); ?>" <?php selected( $current, $code ); ?>>
|
||||||
|
<?php echo esc_html( $label ); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<?php submit_button( __( 'Save Settings', 'wp-bnb' ) ); ?>
|
||||||
|
</form>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render license settings tab.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function render_license_settings(): void {
|
||||||
|
$license_key = LicenseManager::get_license_key();
|
||||||
|
$server_url = LicenseManager::get_server_url();
|
||||||
|
$license_status = LicenseManager::get_cached_status();
|
||||||
|
$license_data = LicenseManager::get_cached_data();
|
||||||
|
$last_check = LicenseManager::get_last_check();
|
||||||
|
?>
|
||||||
|
<form method="post" action="">
|
||||||
|
<?php wp_nonce_field( 'wp_bnb_save_settings', 'wp_bnb_settings_nonce' ); ?>
|
||||||
|
|
||||||
|
<h2><?php esc_html_e( 'License Status', 'wp-bnb' ); ?></h2>
|
||||||
|
|
||||||
|
<table class="form-table" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><?php esc_html_e( 'Status', 'wp-bnb' ); ?></th>
|
||||||
|
<td>
|
||||||
|
<?php $this->render_license_status_badge( $license_status ); ?>
|
||||||
|
<?php if ( $last_check > 0 ) : ?>
|
||||||
|
<p class="description">
|
||||||
|
<?php
|
||||||
|
printf(
|
||||||
|
/* translators: %s: Time ago string */
|
||||||
|
esc_html__( 'Last checked: %s', 'wp-bnb' ),
|
||||||
|
esc_html( human_time_diff( $last_check, time() ) . ' ' . __( 'ago', 'wp-bnb' ) )
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php if ( ! empty( $license_data['expires'] ) ) : ?>
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><?php esc_html_e( 'Expires', 'wp-bnb' ); ?></th>
|
||||||
|
<td>
|
||||||
|
<?php echo esc_html( wp_date( get_option( 'date_format' ), strtotime( $license_data['expires'] ) ) ); ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2><?php esc_html_e( 'License Configuration', 'wp-bnb' ); ?></h2>
|
||||||
|
|
||||||
|
<table class="form-table" role="presentation">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="wp_bnb_license_server_url"><?php esc_html_e( 'License Server URL', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="url" name="wp_bnb_license_server_url" id="wp_bnb_license_server_url"
|
||||||
|
value="<?php echo esc_attr( $server_url ); ?>"
|
||||||
|
class="regular-text" placeholder="https://example.com">
|
||||||
|
<p class="description"><?php esc_html_e( 'The URL of the license server.', 'wp-bnb' ); ?></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="wp_bnb_license_key"><?php esc_html_e( 'License Key', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="wp_bnb_license_key" id="wp_bnb_license_key"
|
||||||
|
value="<?php echo esc_attr( $license_key ); ?>"
|
||||||
|
class="regular-text" placeholder="XXXX-XXXX-XXXX-XXXX">
|
||||||
|
<p class="description"><?php esc_html_e( 'Your license key from the purchase confirmation email.', 'wp-bnb' ); ?></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="wp_bnb_license_server_secret"><?php esc_html_e( 'Server Secret', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="password" name="wp_bnb_license_server_secret" id="wp_bnb_license_server_secret"
|
||||||
|
value="" class="regular-text"
|
||||||
|
placeholder="<?php echo ! empty( LicenseManager::get_server_secret() ) ? '••••••••••••••••' : ''; ?>">
|
||||||
|
<p class="description"><?php esc_html_e( 'Leave empty to keep the current secret. The shared secret for secure communication.', 'wp-bnb' ); ?></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p class="submit">
|
||||||
|
<?php submit_button( __( 'Save License Settings', 'wp-bnb' ), 'primary', 'submit', false ); ?>
|
||||||
|
<button type="button" id="wp-bnb-validate-license" class="button button-secondary">
|
||||||
|
<?php esc_html_e( 'Validate License', 'wp-bnb' ); ?>
|
||||||
|
</button>
|
||||||
|
<button type="button" id="wp-bnb-activate-license" class="button button-secondary">
|
||||||
|
<?php esc_html_e( 'Activate License', 'wp-bnb' ); ?>
|
||||||
|
</button>
|
||||||
|
<span class="spinner" id="wp-bnb-license-spinner"></span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div id="wp-bnb-license-message" style="display: none;"></div>
|
||||||
|
</form>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render license status badge.
|
||||||
|
*
|
||||||
|
* @param string $status License status.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function render_license_status_badge( string $status ): void {
|
||||||
|
$badges = array(
|
||||||
|
'valid' => array(
|
||||||
|
'class' => 'dashicons-yes-alt',
|
||||||
|
'color' => '#00a32a',
|
||||||
|
'label' => __( 'Valid', 'wp-bnb' ),
|
||||||
|
),
|
||||||
|
'invalid' => array(
|
||||||
|
'class' => 'dashicons-dismiss',
|
||||||
|
'color' => '#d63638',
|
||||||
|
'label' => __( 'Invalid', 'wp-bnb' ),
|
||||||
|
),
|
||||||
|
'expired' => array(
|
||||||
|
'class' => 'dashicons-warning',
|
||||||
|
'color' => '#dba617',
|
||||||
|
'label' => __( 'Expired', 'wp-bnb' ),
|
||||||
|
),
|
||||||
|
'revoked' => array(
|
||||||
|
'class' => 'dashicons-dismiss',
|
||||||
|
'color' => '#d63638',
|
||||||
|
'label' => __( 'Revoked', 'wp-bnb' ),
|
||||||
|
),
|
||||||
|
'inactive' => array(
|
||||||
|
'class' => 'dashicons-marker',
|
||||||
|
'color' => '#72aee6',
|
||||||
|
'label' => __( 'Inactive', 'wp-bnb' ),
|
||||||
|
),
|
||||||
|
'unchecked' => array(
|
||||||
|
'class' => 'dashicons-info',
|
||||||
|
'color' => '#72aee6',
|
||||||
|
'label' => __( 'Not checked', 'wp-bnb' ),
|
||||||
|
),
|
||||||
|
'unconfigured' => array(
|
||||||
|
'class' => 'dashicons-admin-generic',
|
||||||
|
'color' => '#646970',
|
||||||
|
'label' => __( 'Not configured', 'wp-bnb' ),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$badge = $badges[ $status ] ?? $badges['unconfigured'];
|
||||||
|
?>
|
||||||
|
<span class="wp-bnb-license-status" style="color: <?php echo esc_attr( $badge['color'] ); ?>;">
|
||||||
|
<span class="dashicons <?php echo esc_attr( $badge['class'] ); ?>"></span>
|
||||||
|
<?php echo esc_html( $badge['label'] ); ?>
|
||||||
|
</span>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save settings based on active tab.
|
||||||
|
*
|
||||||
|
* @param string $tab Active tab.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function save_settings( string $tab ): void {
|
||||||
|
switch ( $tab ) {
|
||||||
|
case 'license':
|
||||||
|
$this->save_license_settings();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$this->save_general_settings();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save general settings.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function save_general_settings(): void {
|
||||||
|
if ( isset( $_POST['wp_bnb_business_name'] ) ) {
|
||||||
|
update_option( 'wp_bnb_business_name', sanitize_text_field( wp_unslash( $_POST['wp_bnb_business_name'] ) ) );
|
||||||
|
}
|
||||||
|
if ( isset( $_POST['wp_bnb_currency'] ) ) {
|
||||||
|
update_option( 'wp_bnb_currency', sanitize_text_field( wp_unslash( $_POST['wp_bnb_currency'] ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
add_settings_error( 'wp_bnb_settings', 'settings_saved', __( 'Settings saved.', 'wp-bnb' ), 'success' );
|
||||||
|
settings_errors( 'wp_bnb_settings' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save license settings.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function save_license_settings(): void {
|
||||||
|
$data = array(
|
||||||
|
'license_key' => isset( $_POST['wp_bnb_license_key'] ) ? sanitize_text_field( wp_unslash( $_POST['wp_bnb_license_key'] ) ) : '',
|
||||||
|
'server_url' => isset( $_POST['wp_bnb_license_server_url'] ) ? esc_url_raw( wp_unslash( $_POST['wp_bnb_license_server_url'] ) ) : '',
|
||||||
|
'server_secret' => isset( $_POST['wp_bnb_license_server_secret'] ) ? sanitize_text_field( wp_unslash( $_POST['wp_bnb_license_server_secret'] ) ) : '',
|
||||||
|
);
|
||||||
|
|
||||||
|
LicenseManager::save_settings( $data );
|
||||||
|
|
||||||
|
add_settings_error( 'wp_bnb_settings', 'settings_saved', __( 'License settings saved.', 'wp-bnb' ), 'success' );
|
||||||
|
settings_errors( 'wp_bnb_settings' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Twig environment.
|
||||||
|
*
|
||||||
|
* @return Environment
|
||||||
|
*/
|
||||||
|
public function get_twig(): Environment {
|
||||||
|
if ( null === $this->twig ) {
|
||||||
|
$loader = new FilesystemLoader( WP_BNB_PATH . 'templates' );
|
||||||
|
$this->twig = new Environment(
|
||||||
|
$loader,
|
||||||
|
array(
|
||||||
|
'cache' => WP_DEBUG ? false : WP_BNB_PATH . 'cache/twig',
|
||||||
|
'debug' => WP_DEBUG,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $this->twig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a Twig template.
|
||||||
|
*
|
||||||
|
* @param string $template Template name.
|
||||||
|
* @param array $context Template context.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function render( string $template, array $context = array() ): string {
|
||||||
|
return $this->get_twig()->render( $template, $context );
|
||||||
|
}
|
||||||
|
}
|
||||||
177
wp-bnb.php
Normal file
177
wp-bnb.php
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Plugin Name: WP BnB Management
|
||||||
|
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wp-bnb
|
||||||
|
* Description: A comprehensive Bed & Breakfast management system for WordPress. Manage buildings, rooms, bookings, and guests.
|
||||||
|
* Version: 0.0.1
|
||||||
|
* Requires at least: 6.0
|
||||||
|
* Requires PHP: 8.3
|
||||||
|
* Author: Marco Graetsch
|
||||||
|
* Author URI: https://src.bundespruefstelle.ch/magdev
|
||||||
|
* License: GPL-2.0-or-later
|
||||||
|
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||||
|
* Text Domain: wp-bnb
|
||||||
|
* Domain Path: /languages
|
||||||
|
*
|
||||||
|
* @package Magdev\WpBnb
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare( strict_types=1 );
|
||||||
|
|
||||||
|
// Prevent direct file access.
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugin version constant - MUST match Version in header above.
|
||||||
|
define( 'WP_BNB_VERSION', '0.0.1' );
|
||||||
|
|
||||||
|
// Plugin path constants.
|
||||||
|
define( 'WP_BNB_PATH', plugin_dir_path( __FILE__ ) );
|
||||||
|
define( 'WP_BNB_URL', plugin_dir_url( __FILE__ ) );
|
||||||
|
define( 'WP_BNB_BASENAME', plugin_basename( __FILE__ ) );
|
||||||
|
|
||||||
|
// Minimum requirements.
|
||||||
|
define( 'WP_BNB_MIN_PHP_VERSION', '8.3.0' );
|
||||||
|
define( 'WP_BNB_MIN_WP_VERSION', '6.0' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check PHP version requirements.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function wp_bnb_check_php_version(): bool {
|
||||||
|
return version_compare( PHP_VERSION, WP_BNB_MIN_PHP_VERSION, '>=' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check WordPress version requirements.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function wp_bnb_check_wp_version(): bool {
|
||||||
|
return version_compare( get_bloginfo( 'version' ), WP_BNB_MIN_WP_VERSION, '>=' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display admin notice for PHP version requirement.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function wp_bnb_php_version_notice(): void {
|
||||||
|
?>
|
||||||
|
<div class="notice notice-error">
|
||||||
|
<p>
|
||||||
|
<?php
|
||||||
|
printf(
|
||||||
|
/* translators: 1: Required PHP version, 2: Current PHP version */
|
||||||
|
esc_html__( 'WP BnB Management requires PHP version %1$s or higher. You are running PHP %2$s.', 'wp-bnb' ),
|
||||||
|
esc_html( WP_BNB_MIN_PHP_VERSION ),
|
||||||
|
esc_html( PHP_VERSION )
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display admin notice for WordPress version requirement.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function wp_bnb_wp_version_notice(): void {
|
||||||
|
?>
|
||||||
|
<div class="notice notice-error">
|
||||||
|
<p>
|
||||||
|
<?php
|
||||||
|
printf(
|
||||||
|
/* translators: 1: Required WordPress version, 2: Current WordPress version */
|
||||||
|
esc_html__( 'WP BnB Management requires WordPress version %1$s or higher. You are running WordPress %2$s.', 'wp-bnb' ),
|
||||||
|
esc_html( WP_BNB_MIN_WP_VERSION ),
|
||||||
|
esc_html( get_bloginfo( 'version' ) )
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the plugin.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function wp_bnb_init(): void {
|
||||||
|
// Check PHP version.
|
||||||
|
if ( ! wp_bnb_check_php_version() ) {
|
||||||
|
add_action( 'admin_notices', 'wp_bnb_php_version_notice' );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check WordPress version.
|
||||||
|
if ( ! wp_bnb_check_wp_version() ) {
|
||||||
|
add_action( 'admin_notices', 'wp_bnb_wp_version_notice' );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load Composer autoloader.
|
||||||
|
$autoloader = WP_BNB_PATH . 'vendor/autoload.php';
|
||||||
|
if ( ! file_exists( $autoloader ) ) {
|
||||||
|
add_action( 'admin_notices', function (): void {
|
||||||
|
?>
|
||||||
|
<div class="notice notice-error">
|
||||||
|
<p>
|
||||||
|
<?php esc_html_e( 'WP BnB Management: Composer autoloader not found. Please run "composer install" in the plugin directory.', 'wp-bnb' ); ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
} );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once $autoloader;
|
||||||
|
|
||||||
|
// Initialize the plugin.
|
||||||
|
\Magdev\WpBnb\Plugin::get_instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook initialization to plugins_loaded.
|
||||||
|
add_action( 'plugins_loaded', 'wp_bnb_init' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin activation hook.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function wp_bnb_activate(): void {
|
||||||
|
// Check requirements before activation.
|
||||||
|
if ( ! wp_bnb_check_php_version() || ! wp_bnb_check_wp_version() ) {
|
||||||
|
deactivate_plugins( plugin_basename( __FILE__ ) );
|
||||||
|
wp_die(
|
||||||
|
esc_html__( 'WP BnB Management requires PHP 8.3+ and WordPress 6.0+.', 'wp-bnb' ),
|
||||||
|
esc_html__( 'Plugin Activation Error', 'wp-bnb' ),
|
||||||
|
array( 'back_link' => true )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default options.
|
||||||
|
add_option( 'wp_bnb_version', WP_BNB_VERSION );
|
||||||
|
|
||||||
|
// Flush rewrite rules for custom post types.
|
||||||
|
flush_rewrite_rules();
|
||||||
|
}
|
||||||
|
|
||||||
|
register_activation_hook( __FILE__, 'wp_bnb_activate' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin deactivation hook.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function wp_bnb_deactivate(): void {
|
||||||
|
// Flush rewrite rules.
|
||||||
|
flush_rewrite_rules();
|
||||||
|
}
|
||||||
|
|
||||||
|
register_deactivation_hook( __FILE__, 'wp_bnb_deactivate' );
|
||||||
Reference in New Issue
Block a user