Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f24a347bb1 |
46
CHANGELOG.md
46
CHANGELOG.md
@@ -5,6 +5,51 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [0.1.0] - 2026-01-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Custom Post Type: Buildings (`bnb_building`)
|
||||||
|
- Address fields (street, city, state, ZIP, country)
|
||||||
|
- Contact information (phone, email, website)
|
||||||
|
- Building details (total rooms, floors, year built)
|
||||||
|
- Check-in/check-out time configuration
|
||||||
|
- Featured image support
|
||||||
|
- Custom admin columns (city, country, room count)
|
||||||
|
- Sortable columns
|
||||||
|
- Custom Post Type: Rooms (`bnb_room`)
|
||||||
|
- Building relationship (parent building selection)
|
||||||
|
- Room details (number, floor, size, capacity)
|
||||||
|
- Guest capacity (total, max adults, max children)
|
||||||
|
- Beds description and bathroom count
|
||||||
|
- Room status (available, occupied, maintenance, blocked)
|
||||||
|
- Image gallery with drag-and-drop sorting
|
||||||
|
- Featured image support
|
||||||
|
- Custom admin columns (building, room number, type, capacity, status)
|
||||||
|
- Building filter dropdown in admin list
|
||||||
|
- Custom Taxonomy: Room Types (`bnb_room_type`)
|
||||||
|
- Hierarchical (category-like) structure
|
||||||
|
- Default types: Standard, Superior, Suite, Family, Accessible, Apartment
|
||||||
|
- Subtypes: Single, Double, Twin, Junior Suite, Executive Suite
|
||||||
|
- Base capacity meta field
|
||||||
|
- Sort order meta field
|
||||||
|
- Custom Taxonomy: Amenities (`bnb_amenity`)
|
||||||
|
- Non-hierarchical (tag-like) structure
|
||||||
|
- Default amenities: WiFi, Parking, Breakfast, TV, A/C, Pet Friendly, etc.
|
||||||
|
- Dashicon selection for visual display
|
||||||
|
- Custom column showing icon
|
||||||
|
- Admin enhancements
|
||||||
|
- Gallery meta box with media library integration
|
||||||
|
- Status badges with color coding
|
||||||
|
- Custom title placeholders for each post type
|
||||||
|
- Post type edit screens with proper asset loading
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated admin assets to handle post type edit screens
|
||||||
|
- Enhanced asset enqueuing to include jQuery UI Sortable for galleries
|
||||||
|
- Improved localization with additional i18n strings
|
||||||
|
|
||||||
## [0.0.1] - 2026-01-31
|
## [0.0.1] - 2026-01-31
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -35,4 +80,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Input sanitization and output escaping
|
- Input sanitization and output escaping
|
||||||
- Server secret masking in license settings
|
- Server secret masking in license settings
|
||||||
|
|
||||||
|
[0.1.0]: https://src.bundespruefstelle.ch/magdev/wp-bnb/releases/tag/v0.1.0
|
||||||
[0.0.1]: https://src.bundespruefstelle.ch/magdev/wp-bnb/releases/tag/v0.0.1
|
[0.0.1]: https://src.bundespruefstelle.ch/magdev/wp-bnb/releases/tag/v0.0.1
|
||||||
|
|||||||
51
CLAUDE.md
51
CLAUDE.md
@@ -215,8 +215,14 @@ wp-bnb/
|
|||||||
│ └── release.yml # CI/CD release pipeline
|
│ └── release.yml # CI/CD release pipeline
|
||||||
├── src/ # PHP source code (PSR-4: Magdev\WpBnb)
|
├── src/ # PHP source code (PSR-4: Magdev\WpBnb)
|
||||||
│ ├── Plugin.php # Main plugin singleton
|
│ ├── Plugin.php # Main plugin singleton
|
||||||
│ └── License/
|
│ ├── License/
|
||||||
│ └── Manager.php # License management
|
│ │ └── Manager.php # License management
|
||||||
|
│ ├── PostTypes/ # Custom post types
|
||||||
|
│ │ ├── Building.php # Building post type
|
||||||
|
│ │ └── Room.php # Room post type
|
||||||
|
│ └── Taxonomies/ # Custom taxonomies
|
||||||
|
│ ├── Amenity.php # Amenity taxonomy (tags)
|
||||||
|
│ └── RoomType.php # Room type taxonomy (categories)
|
||||||
├── lib/ # Git submodules
|
├── lib/ # Git submodules
|
||||||
│ └── wc-licensed-product-client/ # License client library
|
│ └── wc-licensed-product-client/ # License client library
|
||||||
├── vendor/ # Composer dependencies (auto-generated)
|
├── vendor/ # Composer dependencies (auto-generated)
|
||||||
@@ -303,3 +309,44 @@ Admin features always work; frontend requires valid license.
|
|||||||
- Settings page uses tabs with nonce-protected form submission
|
- Settings page uses tabs with nonce-protected form submission
|
||||||
- AJAX handlers require `check_ajax_referer()` and `current_user_can()` checks
|
- AJAX handlers require `check_ajax_referer()` and `current_user_can()` checks
|
||||||
- CI/CD workflow excludes `lib/` directory but includes `vendor/` in releases
|
- CI/CD workflow excludes `lib/` directory but includes `vendor/` in releases
|
||||||
|
|
||||||
|
### 2026-01-31 - Version 0.1.0 (Core Data Structures)
|
||||||
|
|
||||||
|
**Completed:**
|
||||||
|
|
||||||
|
- Created Custom Post Type: Buildings (`bnb_building`)
|
||||||
|
- Address meta box with full address fields
|
||||||
|
- Contact meta box with phone, email, website
|
||||||
|
- Details meta box with rooms count, floors, year built, check-in/out times
|
||||||
|
- Custom admin columns (city, country, room count)
|
||||||
|
- Sortable columns and country dropdown
|
||||||
|
- Created Custom Post Type: Rooms (`bnb_room`)
|
||||||
|
- Building relationship via meta field
|
||||||
|
- Room details: number, floor, size, capacity, beds, bathrooms
|
||||||
|
- Room status with color-coded badges
|
||||||
|
- Image gallery with media library and drag-and-drop sorting
|
||||||
|
- Building filter dropdown in admin list
|
||||||
|
- Custom admin columns with building link
|
||||||
|
- Created Custom Taxonomy: Room Types (`bnb_room_type`)
|
||||||
|
- Hierarchical structure with parent/child support
|
||||||
|
- Base capacity and sort order meta fields
|
||||||
|
- Default terms with subtypes (Standard > Single/Double/Twin, etc.)
|
||||||
|
- Created Custom Taxonomy: Amenities (`bnb_amenity`)
|
||||||
|
- Non-hierarchical (tag-like) structure
|
||||||
|
- Dashicon selection for visual display
|
||||||
|
- Icon column in taxonomy list
|
||||||
|
- Default amenities: WiFi, Parking, Breakfast, etc.
|
||||||
|
- Updated Plugin class to register post types and taxonomies
|
||||||
|
- Enhanced admin assets for post type edit screens
|
||||||
|
- Added gallery JavaScript with media library integration
|
||||||
|
- Updated activation hook to register CPTs before flushing rewrites
|
||||||
|
- Updated version to 0.1.0
|
||||||
|
|
||||||
|
**Learnings:**
|
||||||
|
|
||||||
|
- Taxonomies must be registered before post types that use them
|
||||||
|
- `show_in_menu => 'wp-bnb'` adds post types under the plugin's main menu
|
||||||
|
- Room-building relationship uses post meta, not hierarchical post types
|
||||||
|
- Gallery implementation uses `wp.media` frame with multiple selection
|
||||||
|
- Admin assets need conditional loading based on both hook suffix and post type
|
||||||
|
- Status badges use inline styles for color coding (avoiding extra CSS complexity)
|
||||||
|
|||||||
18
PLAN.md
18
PLAN.md
@@ -17,24 +17,24 @@ This document outlines the implementation plan for the WP BnB Management plugin.
|
|||||||
- [x] Basic CSS and JS assets
|
- [x] Basic CSS and JS assets
|
||||||
- [x] Documentation (README, PLAN, CLAUDE)
|
- [x] Documentation (README, PLAN, CLAUDE)
|
||||||
|
|
||||||
### v0.1.0 - Core Data Structures
|
### v0.1.0 - Core Data Structures (Current)
|
||||||
|
|
||||||
- [ ] Custom Post Type: Buildings
|
- [x] Custom Post Type: Buildings
|
||||||
- Meta fields: address, contact, description, images
|
- Meta fields: address, contact, description, images
|
||||||
- Admin columns and filtering
|
- Admin columns and filtering
|
||||||
- Gutenberg block for display
|
- Gutenberg block for display (planned for Phase 6)
|
||||||
|
|
||||||
- [ ] Custom Post Type: Rooms
|
- [x] Custom Post Type: Rooms
|
||||||
- Meta fields: building reference, capacity, amenities, images
|
- Meta fields: building reference, capacity, amenities, images
|
||||||
- Relationship to Buildings (parent)
|
- Relationship to Buildings (parent)
|
||||||
- Admin columns with building filter
|
- Admin columns with building filter
|
||||||
- Gutenberg block for display
|
- Gutenberg block for display (planned for Phase 6)
|
||||||
|
|
||||||
- [ ] Custom Taxonomy: Room Types
|
- [x] Custom Taxonomy: Room Types
|
||||||
- Standard, Suite, Family, Accessible, etc.
|
- Standard, Suite, Family, Accessible, etc.
|
||||||
- Hierarchical structure
|
- Hierarchical structure
|
||||||
|
|
||||||
- [ ] Custom Taxonomy: Amenities
|
- [x] Custom Taxonomy: Amenities
|
||||||
- WiFi, Parking, Breakfast, etc.
|
- WiFi, Parking, Breakfast, etc.
|
||||||
- Non-hierarchical (tags)
|
- Non-hierarchical (tags)
|
||||||
|
|
||||||
@@ -286,9 +286,9 @@ The plugin will provide extensive hooks for customization:
|
|||||||
## Version Milestones
|
## Version Milestones
|
||||||
|
|
||||||
| Version | Focus | Target |
|
| Version | Focus | Target |
|
||||||
|---------|-------|--------|
|
| ------- | --------------- | -------- |
|
||||||
| 0.0.1 | Initial setup | Complete |
|
| 0.0.1 | Initial setup | Complete |
|
||||||
| 0.1.0 | Data structures | TBD |
|
| 0.1.0 | Data structures | Complete |
|
||||||
| 0.2.0 | Pricing | TBD |
|
| 0.2.0 | Pricing | TBD |
|
||||||
| 0.3.0 | Bookings | TBD |
|
| 0.3.0 | Bookings | TBD |
|
||||||
| 0.4.0 | Guests | TBD |
|
| 0.4.0 | Guests | TBD |
|
||||||
|
|||||||
@@ -79,3 +79,115 @@
|
|||||||
.submit .button {
|
.submit .button {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Room Gallery */
|
||||||
|
.bnb-gallery-container {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bnb-gallery-images {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bnb-gallery-image {
|
||||||
|
position: relative;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border: 1px solid #c3c4c7;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bnb-gallery-image img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bnb-gallery-image .bnb-remove-image {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
right: 2px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bnb-gallery-image:hover .bnb-remove-image {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bnb-gallery-image .bnb-remove-image:hover {
|
||||||
|
background: #d63638;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status Badge */
|
||||||
|
.bnb-status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Room Details Meta Box */
|
||||||
|
#bnb_room_details .form-table td label {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Building Details Meta Box */
|
||||||
|
#bnb_building_details p {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bnb_building_details label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bnb_building_details input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Admin Columns */
|
||||||
|
.column-city,
|
||||||
|
.column-country,
|
||||||
|
.column-room_number,
|
||||||
|
.column-capacity,
|
||||||
|
.column-status {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-building {
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-rooms {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dashicons in columns */
|
||||||
|
.column-capacity .dashicons {
|
||||||
|
color: #646970;
|
||||||
|
font-size: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -91,9 +91,102 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize room gallery functionality.
|
||||||
|
*/
|
||||||
|
function initRoomGallery() {
|
||||||
|
var $container = $('#bnb-room-gallery');
|
||||||
|
var $addButton = $('#bnb-add-gallery-images');
|
||||||
|
var $input = $('#bnb_room_gallery');
|
||||||
|
var $imagesContainer = $container.find('.bnb-gallery-images');
|
||||||
|
|
||||||
|
if (!$addButton.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Media frame for selecting images.
|
||||||
|
var mediaFrame;
|
||||||
|
|
||||||
|
// Add images button click.
|
||||||
|
$addButton.on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// If frame exists, reopen it.
|
||||||
|
if (mediaFrame) {
|
||||||
|
mediaFrame.open();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create media frame.
|
||||||
|
mediaFrame = wp.media({
|
||||||
|
title: wpBnbAdmin.i18n.selectImages,
|
||||||
|
button: {
|
||||||
|
text: wpBnbAdmin.i18n.addToGallery
|
||||||
|
},
|
||||||
|
multiple: true,
|
||||||
|
library: {
|
||||||
|
type: 'image'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle selection.
|
||||||
|
mediaFrame.on('select', function() {
|
||||||
|
var selection = mediaFrame.state().get('selection');
|
||||||
|
selection.each(function(attachment) {
|
||||||
|
var data = attachment.toJSON();
|
||||||
|
var thumbnail = data.sizes.thumbnail ? data.sizes.thumbnail.url : data.url;
|
||||||
|
|
||||||
|
// Check if already in gallery.
|
||||||
|
if ($imagesContainer.find('[data-id="' + data.id + '"]').length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add image to gallery.
|
||||||
|
var $image = $('<div class="bnb-gallery-image" data-id="' + data.id + '">' +
|
||||||
|
'<img src="' + thumbnail + '" alt="">' +
|
||||||
|
'<button type="button" class="bnb-remove-image">×</button>' +
|
||||||
|
'</div>');
|
||||||
|
$imagesContainer.append($image);
|
||||||
|
});
|
||||||
|
|
||||||
|
updateGalleryInput();
|
||||||
|
});
|
||||||
|
|
||||||
|
mediaFrame.open();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove image button click.
|
||||||
|
$imagesContainer.on('click', '.bnb-remove-image', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$(this).closest('.bnb-gallery-image').remove();
|
||||||
|
updateGalleryInput();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make gallery sortable.
|
||||||
|
$imagesContainer.sortable({
|
||||||
|
items: '.bnb-gallery-image',
|
||||||
|
cursor: 'move',
|
||||||
|
update: function() {
|
||||||
|
updateGalleryInput();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the hidden input with gallery image IDs.
|
||||||
|
*/
|
||||||
|
function updateGalleryInput() {
|
||||||
|
var ids = [];
|
||||||
|
$imagesContainer.find('.bnb-gallery-image').each(function() {
|
||||||
|
ids.push($(this).data('id'));
|
||||||
|
});
|
||||||
|
$input.val(ids.join(','));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize on document ready.
|
// Initialize on document ready.
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
initLicenseManagement();
|
initLicenseManagement();
|
||||||
|
initRoomGallery();
|
||||||
});
|
});
|
||||||
|
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ declare( strict_types=1 );
|
|||||||
namespace Magdev\WpBnb;
|
namespace Magdev\WpBnb;
|
||||||
|
|
||||||
use Magdev\WpBnb\License\Manager as LicenseManager;
|
use Magdev\WpBnb\License\Manager as LicenseManager;
|
||||||
|
use Magdev\WpBnb\PostTypes\Building;
|
||||||
|
use Magdev\WpBnb\PostTypes\Room;
|
||||||
|
use Magdev\WpBnb\Taxonomies\Amenity;
|
||||||
|
use Magdev\WpBnb\Taxonomies\RoomType;
|
||||||
use Twig\Environment;
|
use Twig\Environment;
|
||||||
use Twig\Loader\FilesystemLoader;
|
use Twig\Loader\FilesystemLoader;
|
||||||
|
|
||||||
@@ -67,6 +71,31 @@ final class Plugin {
|
|||||||
|
|
||||||
// Add plugin action links.
|
// Add plugin action links.
|
||||||
add_filter( 'plugin_action_links_' . WP_BNB_BASENAME, array( $this, 'add_action_links' ) );
|
add_filter( 'plugin_action_links_' . WP_BNB_BASENAME, array( $this, 'add_action_links' ) );
|
||||||
|
|
||||||
|
// Register custom post types and taxonomies.
|
||||||
|
$this->register_post_types();
|
||||||
|
$this->register_taxonomies();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register custom post types.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function register_post_types(): void {
|
||||||
|
Building::init();
|
||||||
|
Room::init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register custom taxonomies.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function register_taxonomies(): void {
|
||||||
|
// Taxonomies must be registered before post types that use them.
|
||||||
|
Amenity::init();
|
||||||
|
RoomType::init();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,8 +158,14 @@ final class Plugin {
|
|||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function enqueue_admin_assets( string $hook_suffix ): void {
|
public function enqueue_admin_assets( string $hook_suffix ): void {
|
||||||
// Only load on plugin pages.
|
global $post_type;
|
||||||
if ( strpos( $hook_suffix, 'wp-bnb' ) === false ) {
|
|
||||||
|
// Check if we're on plugin pages or editing our custom post types.
|
||||||
|
$is_plugin_page = strpos( $hook_suffix, 'wp-bnb' ) !== false;
|
||||||
|
$is_our_post_type = in_array( $post_type, array( Building::POST_TYPE, Room::POST_TYPE ), true );
|
||||||
|
$is_edit_screen = in_array( $hook_suffix, array( 'post.php', 'post-new.php' ), true );
|
||||||
|
|
||||||
|
if ( ! $is_plugin_page && ! ( $is_our_post_type && $is_edit_screen ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,10 +176,18 @@ final class Plugin {
|
|||||||
WP_BNB_VERSION
|
WP_BNB_VERSION
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$script_deps = array( 'jquery' );
|
||||||
|
|
||||||
|
// Add media dependencies for room gallery.
|
||||||
|
if ( Room::POST_TYPE === $post_type && $is_edit_screen ) {
|
||||||
|
wp_enqueue_media();
|
||||||
|
$script_deps[] = 'jquery-ui-sortable';
|
||||||
|
}
|
||||||
|
|
||||||
wp_enqueue_script(
|
wp_enqueue_script(
|
||||||
'wp-bnb-admin',
|
'wp-bnb-admin',
|
||||||
WP_BNB_URL . 'assets/js/admin.js',
|
WP_BNB_URL . 'assets/js/admin.js',
|
||||||
array( 'jquery' ),
|
$script_deps,
|
||||||
WP_BNB_VERSION,
|
WP_BNB_VERSION,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
@@ -155,10 +198,14 @@ final class Plugin {
|
|||||||
array(
|
array(
|
||||||
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
||||||
'nonce' => wp_create_nonce( 'wp_bnb_admin_nonce' ),
|
'nonce' => wp_create_nonce( 'wp_bnb_admin_nonce' ),
|
||||||
|
'postType' => $post_type,
|
||||||
'i18n' => array(
|
'i18n' => array(
|
||||||
'validating' => __( 'Validating...', 'wp-bnb' ),
|
'validating' => __( 'Validating...', 'wp-bnb' ),
|
||||||
'activating' => __( 'Activating...', 'wp-bnb' ),
|
'activating' => __( 'Activating...', 'wp-bnb' ),
|
||||||
'error' => __( 'An error occurred. Please try again.', 'wp-bnb' ),
|
'error' => __( 'An error occurred. Please try again.', 'wp-bnb' ),
|
||||||
|
'selectImages' => __( 'Select Images', 'wp-bnb' ),
|
||||||
|
'addToGallery' => __( 'Add to Gallery', 'wp-bnb' ),
|
||||||
|
'confirmRemove' => __( 'Are you sure you want to remove this image?', 'wp-bnb' ),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
562
src/PostTypes/Building.php
Normal file
562
src/PostTypes/Building.php
Normal file
@@ -0,0 +1,562 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Building post type.
|
||||||
|
*
|
||||||
|
* Custom post type for BnB buildings/properties.
|
||||||
|
*
|
||||||
|
* @package Magdev\WpBnb\PostTypes
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare( strict_types=1 );
|
||||||
|
|
||||||
|
namespace Magdev\WpBnb\PostTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Building post type class.
|
||||||
|
*/
|
||||||
|
final class Building {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post type slug.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public const POST_TYPE = 'bnb_building';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Meta key prefix.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private const META_PREFIX = '_bnb_building_';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the post type.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function init(): void {
|
||||||
|
add_action( 'init', array( self::class, 'register' ) );
|
||||||
|
add_action( 'add_meta_boxes', array( self::class, 'add_meta_boxes' ) );
|
||||||
|
add_action( 'save_post_' . self::POST_TYPE, array( self::class, 'save_meta' ), 10, 2 );
|
||||||
|
add_filter( 'manage_' . self::POST_TYPE . '_posts_columns', array( self::class, 'add_columns' ) );
|
||||||
|
add_action( 'manage_' . self::POST_TYPE . '_posts_custom_column', array( self::class, 'render_column' ), 10, 2 );
|
||||||
|
add_filter( 'manage_edit-' . self::POST_TYPE . '_sortable_columns', array( self::class, 'sortable_columns' ) );
|
||||||
|
add_filter( 'enter_title_here', array( self::class, 'change_title_placeholder' ), 10, 2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the post type.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function register(): void {
|
||||||
|
$labels = array(
|
||||||
|
'name' => _x( 'Buildings', 'post type general name', 'wp-bnb' ),
|
||||||
|
'singular_name' => _x( 'Building', 'post type singular name', 'wp-bnb' ),
|
||||||
|
'menu_name' => _x( 'Buildings', 'admin menu', 'wp-bnb' ),
|
||||||
|
'name_admin_bar' => _x( 'Building', 'add new on admin bar', 'wp-bnb' ),
|
||||||
|
'add_new' => _x( 'Add New', 'building', 'wp-bnb' ),
|
||||||
|
'add_new_item' => __( 'Add New Building', 'wp-bnb' ),
|
||||||
|
'new_item' => __( 'New Building', 'wp-bnb' ),
|
||||||
|
'edit_item' => __( 'Edit Building', 'wp-bnb' ),
|
||||||
|
'view_item' => __( 'View Building', 'wp-bnb' ),
|
||||||
|
'all_items' => __( 'Buildings', 'wp-bnb' ),
|
||||||
|
'search_items' => __( 'Search Buildings', 'wp-bnb' ),
|
||||||
|
'parent_item_colon' => __( 'Parent Buildings:', 'wp-bnb' ),
|
||||||
|
'not_found' => __( 'No buildings found.', 'wp-bnb' ),
|
||||||
|
'not_found_in_trash' => __( 'No buildings found in Trash.', 'wp-bnb' ),
|
||||||
|
'featured_image' => __( 'Building Image', 'wp-bnb' ),
|
||||||
|
'set_featured_image' => __( 'Set building image', 'wp-bnb' ),
|
||||||
|
'remove_featured_image' => __( 'Remove building image', 'wp-bnb' ),
|
||||||
|
'use_featured_image' => __( 'Use as building image', 'wp-bnb' ),
|
||||||
|
'archives' => __( 'Building archives', 'wp-bnb' ),
|
||||||
|
'insert_into_item' => __( 'Insert into building', 'wp-bnb' ),
|
||||||
|
'uploaded_to_this_item' => __( 'Uploaded to this building', 'wp-bnb' ),
|
||||||
|
'filter_items_list' => __( 'Filter buildings list', 'wp-bnb' ),
|
||||||
|
'items_list_navigation' => __( 'Buildings list navigation', 'wp-bnb' ),
|
||||||
|
'items_list' => __( 'Buildings list', 'wp-bnb' ),
|
||||||
|
);
|
||||||
|
|
||||||
|
$args = array(
|
||||||
|
'labels' => $labels,
|
||||||
|
'public' => true,
|
||||||
|
'publicly_queryable' => true,
|
||||||
|
'show_ui' => true,
|
||||||
|
'show_in_menu' => 'wp-bnb',
|
||||||
|
'query_var' => true,
|
||||||
|
'rewrite' => array(
|
||||||
|
'slug' => 'building',
|
||||||
|
'with_front' => false,
|
||||||
|
),
|
||||||
|
'capability_type' => 'post',
|
||||||
|
'has_archive' => true,
|
||||||
|
'hierarchical' => false,
|
||||||
|
'menu_position' => null,
|
||||||
|
'menu_icon' => 'dashicons-building',
|
||||||
|
'supports' => array(
|
||||||
|
'title',
|
||||||
|
'editor',
|
||||||
|
'thumbnail',
|
||||||
|
'excerpt',
|
||||||
|
'revisions',
|
||||||
|
),
|
||||||
|
'show_in_rest' => true,
|
||||||
|
'rest_base' => 'buildings',
|
||||||
|
'rest_controller_class' => 'WP_REST_Posts_Controller',
|
||||||
|
);
|
||||||
|
|
||||||
|
register_post_type( self::POST_TYPE, $args );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add meta boxes.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function add_meta_boxes(): void {
|
||||||
|
add_meta_box(
|
||||||
|
'bnb_building_address',
|
||||||
|
__( 'Address', 'wp-bnb' ),
|
||||||
|
array( self::class, 'render_address_meta_box' ),
|
||||||
|
self::POST_TYPE,
|
||||||
|
'normal',
|
||||||
|
'high'
|
||||||
|
);
|
||||||
|
|
||||||
|
add_meta_box(
|
||||||
|
'bnb_building_contact',
|
||||||
|
__( 'Contact Information', 'wp-bnb' ),
|
||||||
|
array( self::class, 'render_contact_meta_box' ),
|
||||||
|
self::POST_TYPE,
|
||||||
|
'normal',
|
||||||
|
'high'
|
||||||
|
);
|
||||||
|
|
||||||
|
add_meta_box(
|
||||||
|
'bnb_building_details',
|
||||||
|
__( 'Building Details', 'wp-bnb' ),
|
||||||
|
array( self::class, 'render_details_meta_box' ),
|
||||||
|
self::POST_TYPE,
|
||||||
|
'side',
|
||||||
|
'default'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render address meta box.
|
||||||
|
*
|
||||||
|
* @param \WP_Post $post Current post object.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function render_address_meta_box( \WP_Post $post ): void {
|
||||||
|
wp_nonce_field( 'bnb_building_meta', 'bnb_building_meta_nonce' );
|
||||||
|
|
||||||
|
$street = get_post_meta( $post->ID, self::META_PREFIX . 'street', true );
|
||||||
|
$street2 = get_post_meta( $post->ID, self::META_PREFIX . 'street2', true );
|
||||||
|
$city = get_post_meta( $post->ID, self::META_PREFIX . 'city', true );
|
||||||
|
$state = get_post_meta( $post->ID, self::META_PREFIX . 'state', true );
|
||||||
|
$zip = get_post_meta( $post->ID, self::META_PREFIX . 'zip', true );
|
||||||
|
$country = get_post_meta( $post->ID, self::META_PREFIX . 'country', true );
|
||||||
|
?>
|
||||||
|
<table class="form-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bnb_building_street"><?php esc_html_e( 'Street Address', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="bnb_building_street" name="bnb_building_street"
|
||||||
|
value="<?php echo esc_attr( $street ); ?>" class="large-text">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bnb_building_street2"><?php esc_html_e( 'Street Address 2', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="bnb_building_street2" name="bnb_building_street2"
|
||||||
|
value="<?php echo esc_attr( $street2 ); ?>" class="large-text">
|
||||||
|
<p class="description"><?php esc_html_e( 'Apartment, suite, unit, etc. (optional)', 'wp-bnb' ); ?></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bnb_building_city"><?php esc_html_e( 'City', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="bnb_building_city" name="bnb_building_city"
|
||||||
|
value="<?php echo esc_attr( $city ); ?>" class="regular-text">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bnb_building_state"><?php esc_html_e( 'State / Canton', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="bnb_building_state" name="bnb_building_state"
|
||||||
|
value="<?php echo esc_attr( $state ); ?>" class="regular-text">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bnb_building_zip"><?php esc_html_e( 'ZIP / Postal Code', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="bnb_building_zip" name="bnb_building_zip"
|
||||||
|
value="<?php echo esc_attr( $zip ); ?>" class="regular-text">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bnb_building_country"><?php esc_html_e( 'Country', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<select id="bnb_building_country" name="bnb_building_country">
|
||||||
|
<option value=""><?php esc_html_e( '— Select Country —', 'wp-bnb' ); ?></option>
|
||||||
|
<?php foreach ( self::get_countries() as $code => $name ) : ?>
|
||||||
|
<option value="<?php echo esc_attr( $code ); ?>" <?php selected( $country, $code ); ?>>
|
||||||
|
<?php echo esc_html( $name ); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render contact meta box.
|
||||||
|
*
|
||||||
|
* @param \WP_Post $post Current post object.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function render_contact_meta_box( \WP_Post $post ): void {
|
||||||
|
$phone = get_post_meta( $post->ID, self::META_PREFIX . 'phone', true );
|
||||||
|
$email = get_post_meta( $post->ID, self::META_PREFIX . 'email', true );
|
||||||
|
$website = get_post_meta( $post->ID, self::META_PREFIX . 'website', true );
|
||||||
|
?>
|
||||||
|
<table class="form-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bnb_building_phone"><?php esc_html_e( 'Phone', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="tel" id="bnb_building_phone" name="bnb_building_phone"
|
||||||
|
value="<?php echo esc_attr( $phone ); ?>" class="regular-text">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bnb_building_email"><?php esc_html_e( 'Email', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="email" id="bnb_building_email" name="bnb_building_email"
|
||||||
|
value="<?php echo esc_attr( $email ); ?>" class="regular-text">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bnb_building_website"><?php esc_html_e( 'Website', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="url" id="bnb_building_website" name="bnb_building_website"
|
||||||
|
value="<?php echo esc_attr( $website ); ?>" class="regular-text"
|
||||||
|
placeholder="https://">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render details meta box.
|
||||||
|
*
|
||||||
|
* @param \WP_Post $post Current post object.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function render_details_meta_box( \WP_Post $post ): void {
|
||||||
|
$total_rooms = get_post_meta( $post->ID, self::META_PREFIX . 'total_rooms', true );
|
||||||
|
$floors = get_post_meta( $post->ID, self::META_PREFIX . 'floors', true );
|
||||||
|
$year_built = get_post_meta( $post->ID, self::META_PREFIX . 'year_built', true );
|
||||||
|
$check_in = get_post_meta( $post->ID, self::META_PREFIX . 'check_in_time', true );
|
||||||
|
$check_out = get_post_meta( $post->ID, self::META_PREFIX . 'check_out_time', true );
|
||||||
|
?>
|
||||||
|
<p>
|
||||||
|
<label for="bnb_building_total_rooms"><?php esc_html_e( 'Total Rooms', 'wp-bnb' ); ?></label>
|
||||||
|
<input type="number" id="bnb_building_total_rooms" name="bnb_building_total_rooms"
|
||||||
|
value="<?php echo esc_attr( $total_rooms ); ?>" class="small-text" min="1">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="bnb_building_floors"><?php esc_html_e( 'Number of Floors', 'wp-bnb' ); ?></label>
|
||||||
|
<input type="number" id="bnb_building_floors" name="bnb_building_floors"
|
||||||
|
value="<?php echo esc_attr( $floors ); ?>" class="small-text" min="1">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="bnb_building_year_built"><?php esc_html_e( 'Year Built', 'wp-bnb' ); ?></label>
|
||||||
|
<input type="number" id="bnb_building_year_built" name="bnb_building_year_built"
|
||||||
|
value="<?php echo esc_attr( $year_built ); ?>" class="small-text"
|
||||||
|
min="1800" max="<?php echo esc_attr( gmdate( 'Y' ) ); ?>">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="bnb_building_check_in_time"><?php esc_html_e( 'Check-in Time', 'wp-bnb' ); ?></label>
|
||||||
|
<input type="time" id="bnb_building_check_in_time" name="bnb_building_check_in_time"
|
||||||
|
value="<?php echo esc_attr( $check_in ?: '14:00' ); ?>">
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="bnb_building_check_out_time"><?php esc_html_e( 'Check-out Time', 'wp-bnb' ); ?></label>
|
||||||
|
<input type="time" id="bnb_building_check_out_time" name="bnb_building_check_out_time"
|
||||||
|
value="<?php echo esc_attr( $check_out ?: '11:00' ); ?>">
|
||||||
|
</p>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save post meta.
|
||||||
|
*
|
||||||
|
* @param int $post_id Post ID.
|
||||||
|
* @param \WP_Post $post Post object.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function save_meta( int $post_id, \WP_Post $post ): void {
|
||||||
|
// Verify nonce.
|
||||||
|
if ( ! isset( $_POST['bnb_building_meta_nonce'] ) ||
|
||||||
|
! wp_verify_nonce( sanitize_key( $_POST['bnb_building_meta_nonce'] ), 'bnb_building_meta' ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check autosave.
|
||||||
|
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check permissions.
|
||||||
|
if ( ! current_user_can( 'edit_post', $post_id ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address fields.
|
||||||
|
$text_fields = array(
|
||||||
|
'street',
|
||||||
|
'street2',
|
||||||
|
'city',
|
||||||
|
'state',
|
||||||
|
'zip',
|
||||||
|
'country',
|
||||||
|
'phone',
|
||||||
|
'website',
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ( $text_fields as $field ) {
|
||||||
|
$key = 'bnb_building_' . $field;
|
||||||
|
if ( isset( $_POST[ $key ] ) ) {
|
||||||
|
update_post_meta(
|
||||||
|
$post_id,
|
||||||
|
self::META_PREFIX . $field,
|
||||||
|
sanitize_text_field( wp_unslash( $_POST[ $key ] ) )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Email field (special sanitization).
|
||||||
|
if ( isset( $_POST['bnb_building_email'] ) ) {
|
||||||
|
update_post_meta(
|
||||||
|
$post_id,
|
||||||
|
self::META_PREFIX . 'email',
|
||||||
|
sanitize_email( wp_unslash( $_POST['bnb_building_email'] ) )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric fields.
|
||||||
|
$numeric_fields = array( 'total_rooms', 'floors', 'year_built' );
|
||||||
|
foreach ( $numeric_fields as $field ) {
|
||||||
|
$key = 'bnb_building_' . $field;
|
||||||
|
if ( isset( $_POST[ $key ] ) ) {
|
||||||
|
update_post_meta(
|
||||||
|
$post_id,
|
||||||
|
self::META_PREFIX . $field,
|
||||||
|
absint( $_POST[ $key ] )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time fields.
|
||||||
|
$time_fields = array( 'check_in_time', 'check_out_time' );
|
||||||
|
foreach ( $time_fields as $field ) {
|
||||||
|
$key = 'bnb_building_' . $field;
|
||||||
|
if ( isset( $_POST[ $key ] ) ) {
|
||||||
|
$time = sanitize_text_field( wp_unslash( $_POST[ $key ] ) );
|
||||||
|
if ( preg_match( '/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $time ) ) {
|
||||||
|
update_post_meta( $post_id, self::META_PREFIX . $field, $time );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom columns to the post list.
|
||||||
|
*
|
||||||
|
* @param array $columns Existing columns.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function add_columns( array $columns ): array {
|
||||||
|
$new_columns = array();
|
||||||
|
foreach ( $columns as $key => $value ) {
|
||||||
|
$new_columns[ $key ] = $value;
|
||||||
|
if ( 'title' === $key ) {
|
||||||
|
$new_columns['city'] = __( 'City', 'wp-bnb' );
|
||||||
|
$new_columns['country'] = __( 'Country', 'wp-bnb' );
|
||||||
|
$new_columns['rooms'] = __( 'Rooms', 'wp-bnb' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $new_columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render custom column content.
|
||||||
|
*
|
||||||
|
* @param string $column Column name.
|
||||||
|
* @param int $post_id Post ID.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function render_column( string $column, int $post_id ): void {
|
||||||
|
switch ( $column ) {
|
||||||
|
case 'city':
|
||||||
|
$city = get_post_meta( $post_id, self::META_PREFIX . 'city', true );
|
||||||
|
echo esc_html( $city ?: '—' );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'country':
|
||||||
|
$country = get_post_meta( $post_id, self::META_PREFIX . 'country', true );
|
||||||
|
if ( $country ) {
|
||||||
|
$countries = self::get_countries();
|
||||||
|
echo esc_html( $countries[ $country ] ?? $country );
|
||||||
|
} else {
|
||||||
|
echo '—';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'rooms':
|
||||||
|
$rooms = get_posts(
|
||||||
|
array(
|
||||||
|
'post_type' => 'bnb_room',
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
'meta_query' => array(
|
||||||
|
array(
|
||||||
|
'key' => '_bnb_room_building_id',
|
||||||
|
'value' => $post_id,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$count = count( $rooms );
|
||||||
|
if ( $count > 0 ) {
|
||||||
|
printf(
|
||||||
|
'<a href="%s">%s</a>',
|
||||||
|
esc_url(
|
||||||
|
admin_url(
|
||||||
|
'edit.php?post_type=bnb_room&building_id=' . $post_id
|
||||||
|
)
|
||||||
|
),
|
||||||
|
esc_html(
|
||||||
|
sprintf(
|
||||||
|
/* translators: %d: Number of rooms */
|
||||||
|
_n( '%d room', '%d rooms', $count, 'wp-bnb' ),
|
||||||
|
$count
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
echo '—';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add sortable columns.
|
||||||
|
*
|
||||||
|
* @param array $columns Existing sortable columns.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function sortable_columns( array $columns ): array {
|
||||||
|
$columns['city'] = 'city';
|
||||||
|
$columns['country'] = 'country';
|
||||||
|
return $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change title placeholder.
|
||||||
|
*
|
||||||
|
* @param string $placeholder Default placeholder.
|
||||||
|
* @param \WP_Post $post Current post.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function change_title_placeholder( string $placeholder, \WP_Post $post ): string {
|
||||||
|
if ( self::POST_TYPE === $post->post_type ) {
|
||||||
|
return __( 'Enter building name', 'wp-bnb' );
|
||||||
|
}
|
||||||
|
return $placeholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of countries.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public static function get_countries(): array {
|
||||||
|
return array(
|
||||||
|
'CH' => __( 'Switzerland', 'wp-bnb' ),
|
||||||
|
'DE' => __( 'Germany', 'wp-bnb' ),
|
||||||
|
'AT' => __( 'Austria', 'wp-bnb' ),
|
||||||
|
'FR' => __( 'France', 'wp-bnb' ),
|
||||||
|
'IT' => __( 'Italy', 'wp-bnb' ),
|
||||||
|
'LI' => __( 'Liechtenstein', 'wp-bnb' ),
|
||||||
|
'NL' => __( 'Netherlands', 'wp-bnb' ),
|
||||||
|
'BE' => __( 'Belgium', 'wp-bnb' ),
|
||||||
|
'LU' => __( 'Luxembourg', 'wp-bnb' ),
|
||||||
|
'GB' => __( 'United Kingdom', 'wp-bnb' ),
|
||||||
|
'US' => __( 'United States', 'wp-bnb' ),
|
||||||
|
'CA' => __( 'Canada', 'wp-bnb' ),
|
||||||
|
'ES' => __( 'Spain', 'wp-bnb' ),
|
||||||
|
'PT' => __( 'Portugal', 'wp-bnb' ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get formatted address.
|
||||||
|
*
|
||||||
|
* @param int $post_id Post ID.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function get_formatted_address( int $post_id ): string {
|
||||||
|
$street = get_post_meta( $post_id, self::META_PREFIX . 'street', true );
|
||||||
|
$street2 = get_post_meta( $post_id, self::META_PREFIX . 'street2', true );
|
||||||
|
$city = get_post_meta( $post_id, self::META_PREFIX . 'city', true );
|
||||||
|
$state = get_post_meta( $post_id, self::META_PREFIX . 'state', true );
|
||||||
|
$zip = get_post_meta( $post_id, self::META_PREFIX . 'zip', true );
|
||||||
|
$country = get_post_meta( $post_id, self::META_PREFIX . 'country', true );
|
||||||
|
|
||||||
|
$parts = array();
|
||||||
|
|
||||||
|
if ( $street ) {
|
||||||
|
$parts[] = $street;
|
||||||
|
}
|
||||||
|
if ( $street2 ) {
|
||||||
|
$parts[] = $street2;
|
||||||
|
}
|
||||||
|
if ( $zip || $city ) {
|
||||||
|
$parts[] = trim( $zip . ' ' . $city );
|
||||||
|
}
|
||||||
|
if ( $state ) {
|
||||||
|
$parts[] = $state;
|
||||||
|
}
|
||||||
|
if ( $country ) {
|
||||||
|
$countries = self::get_countries();
|
||||||
|
$parts[] = $countries[ $country ] ?? $country;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode( "\n", $parts );
|
||||||
|
}
|
||||||
|
}
|
||||||
660
src/PostTypes/Room.php
Normal file
660
src/PostTypes/Room.php
Normal file
@@ -0,0 +1,660 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Room post type.
|
||||||
|
*
|
||||||
|
* Custom post type for BnB rooms.
|
||||||
|
*
|
||||||
|
* @package Magdev\WpBnb\PostTypes
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare( strict_types=1 );
|
||||||
|
|
||||||
|
namespace Magdev\WpBnb\PostTypes;
|
||||||
|
|
||||||
|
use Magdev\WpBnb\Taxonomies\Amenity;
|
||||||
|
use Magdev\WpBnb\Taxonomies\RoomType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Room post type class.
|
||||||
|
*/
|
||||||
|
final class Room {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post type slug.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public const POST_TYPE = 'bnb_room';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Meta key prefix.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private const META_PREFIX = '_bnb_room_';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the post type.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function init(): void {
|
||||||
|
add_action( 'init', array( self::class, 'register' ) );
|
||||||
|
add_action( 'add_meta_boxes', array( self::class, 'add_meta_boxes' ) );
|
||||||
|
add_action( 'save_post_' . self::POST_TYPE, array( self::class, 'save_meta' ), 10, 2 );
|
||||||
|
add_filter( 'manage_' . self::POST_TYPE . '_posts_columns', array( self::class, 'add_columns' ) );
|
||||||
|
add_action( 'manage_' . self::POST_TYPE . '_posts_custom_column', array( self::class, 'render_column' ), 10, 2 );
|
||||||
|
add_filter( 'manage_edit-' . self::POST_TYPE . '_sortable_columns', array( self::class, 'sortable_columns' ) );
|
||||||
|
add_action( 'restrict_manage_posts', array( self::class, 'add_building_filter' ) );
|
||||||
|
add_action( 'pre_get_posts', array( self::class, 'filter_by_building' ) );
|
||||||
|
add_filter( 'enter_title_here', array( self::class, 'change_title_placeholder' ), 10, 2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the post type.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function register(): void {
|
||||||
|
$labels = array(
|
||||||
|
'name' => _x( 'Rooms', 'post type general name', 'wp-bnb' ),
|
||||||
|
'singular_name' => _x( 'Room', 'post type singular name', 'wp-bnb' ),
|
||||||
|
'menu_name' => _x( 'Rooms', 'admin menu', 'wp-bnb' ),
|
||||||
|
'name_admin_bar' => _x( 'Room', 'add new on admin bar', 'wp-bnb' ),
|
||||||
|
'add_new' => _x( 'Add New', 'room', 'wp-bnb' ),
|
||||||
|
'add_new_item' => __( 'Add New Room', 'wp-bnb' ),
|
||||||
|
'new_item' => __( 'New Room', 'wp-bnb' ),
|
||||||
|
'edit_item' => __( 'Edit Room', 'wp-bnb' ),
|
||||||
|
'view_item' => __( 'View Room', 'wp-bnb' ),
|
||||||
|
'all_items' => __( 'Rooms', 'wp-bnb' ),
|
||||||
|
'search_items' => __( 'Search Rooms', 'wp-bnb' ),
|
||||||
|
'parent_item_colon' => __( 'Parent Rooms:', 'wp-bnb' ),
|
||||||
|
'not_found' => __( 'No rooms found.', 'wp-bnb' ),
|
||||||
|
'not_found_in_trash' => __( 'No rooms found in Trash.', 'wp-bnb' ),
|
||||||
|
'featured_image' => __( 'Room Image', 'wp-bnb' ),
|
||||||
|
'set_featured_image' => __( 'Set room image', 'wp-bnb' ),
|
||||||
|
'remove_featured_image' => __( 'Remove room image', 'wp-bnb' ),
|
||||||
|
'use_featured_image' => __( 'Use as room image', 'wp-bnb' ),
|
||||||
|
'archives' => __( 'Room archives', 'wp-bnb' ),
|
||||||
|
'insert_into_item' => __( 'Insert into room', 'wp-bnb' ),
|
||||||
|
'uploaded_to_this_item' => __( 'Uploaded to this room', 'wp-bnb' ),
|
||||||
|
'filter_items_list' => __( 'Filter rooms list', 'wp-bnb' ),
|
||||||
|
'items_list_navigation' => __( 'Rooms list navigation', 'wp-bnb' ),
|
||||||
|
'items_list' => __( 'Rooms list', 'wp-bnb' ),
|
||||||
|
);
|
||||||
|
|
||||||
|
$args = array(
|
||||||
|
'labels' => $labels,
|
||||||
|
'public' => true,
|
||||||
|
'publicly_queryable' => true,
|
||||||
|
'show_ui' => true,
|
||||||
|
'show_in_menu' => 'wp-bnb',
|
||||||
|
'query_var' => true,
|
||||||
|
'rewrite' => array(
|
||||||
|
'slug' => 'room',
|
||||||
|
'with_front' => false,
|
||||||
|
),
|
||||||
|
'capability_type' => 'post',
|
||||||
|
'has_archive' => true,
|
||||||
|
'hierarchical' => false,
|
||||||
|
'menu_position' => null,
|
||||||
|
'menu_icon' => 'dashicons-admin-home',
|
||||||
|
'supports' => array(
|
||||||
|
'title',
|
||||||
|
'editor',
|
||||||
|
'thumbnail',
|
||||||
|
'excerpt',
|
||||||
|
'revisions',
|
||||||
|
),
|
||||||
|
'show_in_rest' => true,
|
||||||
|
'rest_base' => 'rooms',
|
||||||
|
'rest_controller_class' => 'WP_REST_Posts_Controller',
|
||||||
|
'taxonomies' => array( RoomType::TAXONOMY, Amenity::TAXONOMY ),
|
||||||
|
);
|
||||||
|
|
||||||
|
register_post_type( self::POST_TYPE, $args );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add meta boxes.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function add_meta_boxes(): void {
|
||||||
|
add_meta_box(
|
||||||
|
'bnb_room_building',
|
||||||
|
__( 'Building', 'wp-bnb' ),
|
||||||
|
array( self::class, 'render_building_meta_box' ),
|
||||||
|
self::POST_TYPE,
|
||||||
|
'side',
|
||||||
|
'high'
|
||||||
|
);
|
||||||
|
|
||||||
|
add_meta_box(
|
||||||
|
'bnb_room_details',
|
||||||
|
__( 'Room Details', 'wp-bnb' ),
|
||||||
|
array( self::class, 'render_details_meta_box' ),
|
||||||
|
self::POST_TYPE,
|
||||||
|
'normal',
|
||||||
|
'high'
|
||||||
|
);
|
||||||
|
|
||||||
|
add_meta_box(
|
||||||
|
'bnb_room_gallery',
|
||||||
|
__( 'Room Gallery', 'wp-bnb' ),
|
||||||
|
array( self::class, 'render_gallery_meta_box' ),
|
||||||
|
self::POST_TYPE,
|
||||||
|
'normal',
|
||||||
|
'default'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render building selection meta box.
|
||||||
|
*
|
||||||
|
* @param \WP_Post $post Current post object.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function render_building_meta_box( \WP_Post $post ): void {
|
||||||
|
wp_nonce_field( 'bnb_room_meta', 'bnb_room_meta_nonce' );
|
||||||
|
|
||||||
|
$building_id = get_post_meta( $post->ID, self::META_PREFIX . 'building_id', true );
|
||||||
|
$buildings = get_posts(
|
||||||
|
array(
|
||||||
|
'post_type' => Building::POST_TYPE,
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
'orderby' => 'title',
|
||||||
|
'order' => 'ASC',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
<p>
|
||||||
|
<label for="bnb_room_building_id"><?php esc_html_e( 'Select Building', 'wp-bnb' ); ?></label>
|
||||||
|
</p>
|
||||||
|
<select id="bnb_room_building_id" name="bnb_room_building_id" class="widefat" required>
|
||||||
|
<option value=""><?php esc_html_e( '— Select Building —', 'wp-bnb' ); ?></option>
|
||||||
|
<?php foreach ( $buildings as $building ) : ?>
|
||||||
|
<option value="<?php echo esc_attr( $building->ID ); ?>" <?php selected( $building_id, $building->ID ); ?>>
|
||||||
|
<?php echo esc_html( $building->post_title ); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<?php if ( empty( $buildings ) ) : ?>
|
||||||
|
<p class="description">
|
||||||
|
<?php
|
||||||
|
printf(
|
||||||
|
/* translators: %s: Link to add new building */
|
||||||
|
esc_html__( 'No buildings found. %s first.', 'wp-bnb' ),
|
||||||
|
'<a href="' . esc_url( admin_url( 'post-new.php?post_type=' . Building::POST_TYPE ) ) . '">' . esc_html__( 'Add a building', 'wp-bnb' ) . '</a>'
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render room details meta box.
|
||||||
|
*
|
||||||
|
* @param \WP_Post $post Current post object.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function render_details_meta_box( \WP_Post $post ): void {
|
||||||
|
$room_number = get_post_meta( $post->ID, self::META_PREFIX . 'room_number', true );
|
||||||
|
$floor = get_post_meta( $post->ID, self::META_PREFIX . 'floor', true );
|
||||||
|
$capacity = get_post_meta( $post->ID, self::META_PREFIX . 'capacity', true );
|
||||||
|
$max_adults = get_post_meta( $post->ID, self::META_PREFIX . 'max_adults', true );
|
||||||
|
$max_children = get_post_meta( $post->ID, self::META_PREFIX . 'max_children', true );
|
||||||
|
$size = get_post_meta( $post->ID, self::META_PREFIX . 'size', true );
|
||||||
|
$beds = get_post_meta( $post->ID, self::META_PREFIX . 'beds', true );
|
||||||
|
$bathrooms = get_post_meta( $post->ID, self::META_PREFIX . 'bathrooms', true );
|
||||||
|
$status = get_post_meta( $post->ID, self::META_PREFIX . 'status', true );
|
||||||
|
?>
|
||||||
|
<table class="form-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bnb_room_room_number"><?php esc_html_e( 'Room Number', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="bnb_room_room_number" name="bnb_room_room_number"
|
||||||
|
value="<?php echo esc_attr( $room_number ); ?>" class="regular-text">
|
||||||
|
<p class="description"><?php esc_html_e( 'Room identifier (e.g., 101, A-12, Suite B)', 'wp-bnb' ); ?></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bnb_room_floor"><?php esc_html_e( 'Floor', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="number" id="bnb_room_floor" name="bnb_room_floor"
|
||||||
|
value="<?php echo esc_attr( $floor ); ?>" class="small-text" min="0">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bnb_room_size"><?php esc_html_e( 'Size (m²)', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="number" id="bnb_room_size" name="bnb_room_size"
|
||||||
|
value="<?php echo esc_attr( $size ); ?>" class="small-text" min="1" step="0.1">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<?php esc_html_e( 'Capacity', 'wp-bnb' ); ?>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<label for="bnb_room_capacity"><?php esc_html_e( 'Total Guests', 'wp-bnb' ); ?></label>
|
||||||
|
<input type="number" id="bnb_room_capacity" name="bnb_room_capacity"
|
||||||
|
value="<?php echo esc_attr( $capacity ?: '2' ); ?>" class="small-text" min="1" max="20">
|
||||||
|
<br><br>
|
||||||
|
<label for="bnb_room_max_adults"><?php esc_html_e( 'Max Adults', 'wp-bnb' ); ?></label>
|
||||||
|
<input type="number" id="bnb_room_max_adults" name="bnb_room_max_adults"
|
||||||
|
value="<?php echo esc_attr( $max_adults ?: '2' ); ?>" class="small-text" min="1" max="10">
|
||||||
|
<br><br>
|
||||||
|
<label for="bnb_room_max_children"><?php esc_html_e( 'Max Children', 'wp-bnb' ); ?></label>
|
||||||
|
<input type="number" id="bnb_room_max_children" name="bnb_room_max_children"
|
||||||
|
value="<?php echo esc_attr( $max_children ?: '0' ); ?>" class="small-text" min="0" max="10">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bnb_room_beds"><?php esc_html_e( 'Beds', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="bnb_room_beds" name="bnb_room_beds"
|
||||||
|
value="<?php echo esc_attr( $beds ); ?>" class="regular-text">
|
||||||
|
<p class="description"><?php esc_html_e( 'Description of beds (e.g., 1 King, 2 Singles)', 'wp-bnb' ); ?></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bnb_room_bathrooms"><?php esc_html_e( 'Bathrooms', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="number" id="bnb_room_bathrooms" name="bnb_room_bathrooms"
|
||||||
|
value="<?php echo esc_attr( $bathrooms ?: '1' ); ?>" class="small-text" min="0" max="5" step="0.5">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="bnb_room_status"><?php esc_html_e( 'Room Status', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<select id="bnb_room_status" name="bnb_room_status">
|
||||||
|
<?php foreach ( self::get_room_statuses() as $key => $label ) : ?>
|
||||||
|
<option value="<?php echo esc_attr( $key ); ?>" <?php selected( $status ?: 'available', $key ); ?>>
|
||||||
|
<?php echo esc_html( $label ); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render gallery meta box.
|
||||||
|
*
|
||||||
|
* @param \WP_Post $post Current post object.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function render_gallery_meta_box( \WP_Post $post ): void {
|
||||||
|
$gallery_ids = get_post_meta( $post->ID, self::META_PREFIX . 'gallery', true );
|
||||||
|
$gallery_ids = $gallery_ids ? explode( ',', $gallery_ids ) : array();
|
||||||
|
?>
|
||||||
|
<div id="bnb-room-gallery" class="bnb-gallery-container">
|
||||||
|
<div class="bnb-gallery-images">
|
||||||
|
<?php foreach ( $gallery_ids as $image_id ) : ?>
|
||||||
|
<?php $image = wp_get_attachment_image_src( $image_id, 'thumbnail' ); ?>
|
||||||
|
<?php if ( $image ) : ?>
|
||||||
|
<div class="bnb-gallery-image" data-id="<?php echo esc_attr( $image_id ); ?>">
|
||||||
|
<img src="<?php echo esc_url( $image[0] ); ?>" alt="">
|
||||||
|
<button type="button" class="bnb-remove-image">×</button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" id="bnb_room_gallery" name="bnb_room_gallery"
|
||||||
|
value="<?php echo esc_attr( implode( ',', $gallery_ids ) ); ?>">
|
||||||
|
<button type="button" id="bnb-add-gallery-images" class="button">
|
||||||
|
<?php esc_html_e( 'Add Images', 'wp-bnb' ); ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p class="description"><?php esc_html_e( 'Add additional images for this room. Drag to reorder.', 'wp-bnb' ); ?></p>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save post meta.
|
||||||
|
*
|
||||||
|
* @param int $post_id Post ID.
|
||||||
|
* @param \WP_Post $post Post object.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function save_meta( int $post_id, \WP_Post $post ): void {
|
||||||
|
// Verify nonce.
|
||||||
|
if ( ! isset( $_POST['bnb_room_meta_nonce'] ) ||
|
||||||
|
! wp_verify_nonce( sanitize_key( $_POST['bnb_room_meta_nonce'] ), 'bnb_room_meta' ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check autosave.
|
||||||
|
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check permissions.
|
||||||
|
if ( ! current_user_can( 'edit_post', $post_id ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Building ID.
|
||||||
|
if ( isset( $_POST['bnb_room_building_id'] ) ) {
|
||||||
|
update_post_meta(
|
||||||
|
$post_id,
|
||||||
|
self::META_PREFIX . 'building_id',
|
||||||
|
absint( $_POST['bnb_room_building_id'] )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text fields.
|
||||||
|
$text_fields = array( 'room_number', 'beds', 'status' );
|
||||||
|
foreach ( $text_fields as $field ) {
|
||||||
|
$key = 'bnb_room_' . $field;
|
||||||
|
if ( isset( $_POST[ $key ] ) ) {
|
||||||
|
update_post_meta(
|
||||||
|
$post_id,
|
||||||
|
self::META_PREFIX . $field,
|
||||||
|
sanitize_text_field( wp_unslash( $_POST[ $key ] ) )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric fields.
|
||||||
|
$numeric_fields = array( 'floor', 'capacity', 'max_adults', 'max_children' );
|
||||||
|
foreach ( $numeric_fields as $field ) {
|
||||||
|
$key = 'bnb_room_' . $field;
|
||||||
|
if ( isset( $_POST[ $key ] ) ) {
|
||||||
|
update_post_meta(
|
||||||
|
$post_id,
|
||||||
|
self::META_PREFIX . $field,
|
||||||
|
absint( $_POST[ $key ] )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float fields.
|
||||||
|
$float_fields = array( 'size', 'bathrooms' );
|
||||||
|
foreach ( $float_fields as $field ) {
|
||||||
|
$key = 'bnb_room_' . $field;
|
||||||
|
if ( isset( $_POST[ $key ] ) ) {
|
||||||
|
update_post_meta(
|
||||||
|
$post_id,
|
||||||
|
self::META_PREFIX . $field,
|
||||||
|
floatval( $_POST[ $key ] )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gallery.
|
||||||
|
if ( isset( $_POST['bnb_room_gallery'] ) ) {
|
||||||
|
$gallery_ids = sanitize_text_field( wp_unslash( $_POST['bnb_room_gallery'] ) );
|
||||||
|
// Validate that each ID is numeric.
|
||||||
|
$ids = array_filter( explode( ',', $gallery_ids ), 'is_numeric' );
|
||||||
|
update_post_meta(
|
||||||
|
$post_id,
|
||||||
|
self::META_PREFIX . 'gallery',
|
||||||
|
implode( ',', $ids )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom columns to the post list.
|
||||||
|
*
|
||||||
|
* @param array $columns Existing columns.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function add_columns( array $columns ): array {
|
||||||
|
$new_columns = array();
|
||||||
|
foreach ( $columns as $key => $value ) {
|
||||||
|
if ( 'taxonomy-bnb_room_type' === $key ) {
|
||||||
|
continue; // Will add after title.
|
||||||
|
}
|
||||||
|
if ( 'taxonomy-bnb_amenity' === $key ) {
|
||||||
|
continue; // Will add after room type.
|
||||||
|
}
|
||||||
|
$new_columns[ $key ] = $value;
|
||||||
|
if ( 'title' === $key ) {
|
||||||
|
$new_columns['building'] = __( 'Building', 'wp-bnb' );
|
||||||
|
$new_columns['room_number'] = __( 'Room #', 'wp-bnb' );
|
||||||
|
$new_columns['taxonomy-bnb_room_type'] = __( 'Room Type', 'wp-bnb' );
|
||||||
|
$new_columns['capacity'] = __( 'Capacity', 'wp-bnb' );
|
||||||
|
$new_columns['status'] = __( 'Status', 'wp-bnb' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $new_columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render custom column content.
|
||||||
|
*
|
||||||
|
* @param string $column Column name.
|
||||||
|
* @param int $post_id Post ID.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function render_column( string $column, int $post_id ): void {
|
||||||
|
switch ( $column ) {
|
||||||
|
case 'building':
|
||||||
|
$building_id = get_post_meta( $post_id, self::META_PREFIX . 'building_id', true );
|
||||||
|
if ( $building_id ) {
|
||||||
|
$building = get_post( $building_id );
|
||||||
|
if ( $building ) {
|
||||||
|
printf(
|
||||||
|
'<a href="%s">%s</a>',
|
||||||
|
esc_url( get_edit_post_link( $building_id ) ),
|
||||||
|
esc_html( $building->post_title )
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
echo '—';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo '—';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'room_number':
|
||||||
|
$room_number = get_post_meta( $post_id, self::META_PREFIX . 'room_number', true );
|
||||||
|
echo esc_html( $room_number ?: '—' );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'capacity':
|
||||||
|
$capacity = get_post_meta( $post_id, self::META_PREFIX . 'capacity', true );
|
||||||
|
if ( $capacity ) {
|
||||||
|
printf(
|
||||||
|
'<span class="dashicons dashicons-groups"></span> %s',
|
||||||
|
esc_html( $capacity )
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
echo '—';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'status':
|
||||||
|
$status = get_post_meta( $post_id, self::META_PREFIX . 'status', true ) ?: 'available';
|
||||||
|
$statuses = self::get_room_statuses();
|
||||||
|
$colors = self::get_status_colors();
|
||||||
|
?>
|
||||||
|
<span class="bnb-status-badge" style="background-color: <?php echo esc_attr( $colors[ $status ] ?? '#ccc' ); ?>">
|
||||||
|
<?php echo esc_html( $statuses[ $status ] ?? $status ); ?>
|
||||||
|
</span>
|
||||||
|
<?php
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add sortable columns.
|
||||||
|
*
|
||||||
|
* @param array $columns Existing sortable columns.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function sortable_columns( array $columns ): array {
|
||||||
|
$columns['building'] = 'building';
|
||||||
|
$columns['room_number'] = 'room_number';
|
||||||
|
$columns['capacity'] = 'capacity';
|
||||||
|
return $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add building filter dropdown to admin list.
|
||||||
|
*
|
||||||
|
* @param string $post_type Current post type.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function add_building_filter( string $post_type ): void {
|
||||||
|
if ( self::POST_TYPE !== $post_type ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$buildings = get_posts(
|
||||||
|
array(
|
||||||
|
'post_type' => Building::POST_TYPE,
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
'orderby' => 'title',
|
||||||
|
'order' => 'ASC',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( empty( $buildings ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Filter display only.
|
||||||
|
$selected = isset( $_GET['building_id'] ) ? absint( $_GET['building_id'] ) : 0;
|
||||||
|
?>
|
||||||
|
<select name="building_id">
|
||||||
|
<option value=""><?php esc_html_e( 'All Buildings', 'wp-bnb' ); ?></option>
|
||||||
|
<?php foreach ( $buildings as $building ) : ?>
|
||||||
|
<option value="<?php echo esc_attr( $building->ID ); ?>" <?php selected( $selected, $building->ID ); ?>>
|
||||||
|
<?php echo esc_html( $building->post_title ); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter rooms by building in admin list.
|
||||||
|
*
|
||||||
|
* @param \WP_Query $query Current query.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function filter_by_building( \WP_Query $query ): void {
|
||||||
|
if ( ! is_admin() || ! $query->is_main_query() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( self::POST_TYPE !== $query->get( 'post_type' ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Filter query only.
|
||||||
|
if ( ! empty( $_GET['building_id'] ) ) {
|
||||||
|
$query->set(
|
||||||
|
'meta_query',
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
'key' => self::META_PREFIX . 'building_id',
|
||||||
|
'value' => absint( $_GET['building_id'] ),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change title placeholder.
|
||||||
|
*
|
||||||
|
* @param string $placeholder Default placeholder.
|
||||||
|
* @param \WP_Post $post Current post.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function change_title_placeholder( string $placeholder, \WP_Post $post ): string {
|
||||||
|
if ( self::POST_TYPE === $post->post_type ) {
|
||||||
|
return __( 'Enter room name', 'wp-bnb' );
|
||||||
|
}
|
||||||
|
return $placeholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get room status options.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public static function get_room_statuses(): array {
|
||||||
|
return array(
|
||||||
|
'available' => __( 'Available', 'wp-bnb' ),
|
||||||
|
'occupied' => __( 'Occupied', 'wp-bnb' ),
|
||||||
|
'maintenance' => __( 'Maintenance', 'wp-bnb' ),
|
||||||
|
'blocked' => __( 'Blocked', 'wp-bnb' ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get status color codes.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public static function get_status_colors(): array {
|
||||||
|
return array(
|
||||||
|
'available' => '#00a32a',
|
||||||
|
'occupied' => '#72aee6',
|
||||||
|
'maintenance' => '#dba617',
|
||||||
|
'blocked' => '#d63638',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get building for a room.
|
||||||
|
*
|
||||||
|
* @param int $room_id Room post ID.
|
||||||
|
* @return \WP_Post|null
|
||||||
|
*/
|
||||||
|
public static function get_building( int $room_id ): ?\WP_Post {
|
||||||
|
$building_id = get_post_meta( $room_id, self::META_PREFIX . 'building_id', true );
|
||||||
|
if ( ! $building_id ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return get_post( $building_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get rooms for a building.
|
||||||
|
*
|
||||||
|
* @param int $building_id Building post ID.
|
||||||
|
* @return array<\WP_Post>
|
||||||
|
*/
|
||||||
|
public static function get_rooms_for_building( int $building_id ): array {
|
||||||
|
return get_posts(
|
||||||
|
array(
|
||||||
|
'post_type' => self::POST_TYPE,
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
'meta_query' => array(
|
||||||
|
array(
|
||||||
|
'key' => self::META_PREFIX . 'building_id',
|
||||||
|
'value' => $building_id,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'orderby' => 'meta_value',
|
||||||
|
'meta_key' => self::META_PREFIX . 'room_number',
|
||||||
|
'order' => 'ASC',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
242
src/Taxonomies/Amenity.php
Normal file
242
src/Taxonomies/Amenity.php
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Amenity taxonomy.
|
||||||
|
*
|
||||||
|
* Non-hierarchical taxonomy for room amenities like WiFi, Parking, etc.
|
||||||
|
*
|
||||||
|
* @package Magdev\WpBnb\Taxonomies
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare( strict_types=1 );
|
||||||
|
|
||||||
|
namespace Magdev\WpBnb\Taxonomies;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Amenity taxonomy class.
|
||||||
|
*/
|
||||||
|
final class Amenity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Taxonomy slug.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public const TAXONOMY = 'bnb_amenity';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the taxonomy.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function init(): void {
|
||||||
|
add_action( 'init', array( self::class, 'register' ) );
|
||||||
|
add_action( 'bnb_amenity_add_form_fields', array( self::class, 'add_form_fields' ) );
|
||||||
|
add_action( 'bnb_amenity_edit_form_fields', array( self::class, 'edit_form_fields' ), 10, 2 );
|
||||||
|
add_action( 'created_bnb_amenity', array( self::class, 'save_term_meta' ), 10, 2 );
|
||||||
|
add_action( 'edited_bnb_amenity', array( self::class, 'save_term_meta' ), 10, 2 );
|
||||||
|
add_filter( 'manage_edit-bnb_amenity_columns', array( self::class, 'add_columns' ) );
|
||||||
|
add_filter( 'manage_bnb_amenity_custom_column', array( self::class, 'render_column' ), 10, 3 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the taxonomy.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function register(): void {
|
||||||
|
$labels = array(
|
||||||
|
'name' => _x( 'Amenities', 'taxonomy general name', 'wp-bnb' ),
|
||||||
|
'singular_name' => _x( 'Amenity', 'taxonomy singular name', 'wp-bnb' ),
|
||||||
|
'search_items' => __( 'Search Amenities', 'wp-bnb' ),
|
||||||
|
'popular_items' => __( 'Popular Amenities', 'wp-bnb' ),
|
||||||
|
'all_items' => __( 'All Amenities', 'wp-bnb' ),
|
||||||
|
'parent_item' => null,
|
||||||
|
'parent_item_colon' => null,
|
||||||
|
'edit_item' => __( 'Edit Amenity', 'wp-bnb' ),
|
||||||
|
'update_item' => __( 'Update Amenity', 'wp-bnb' ),
|
||||||
|
'add_new_item' => __( 'Add New Amenity', 'wp-bnb' ),
|
||||||
|
'new_item_name' => __( 'New Amenity Name', 'wp-bnb' ),
|
||||||
|
'separate_items_with_commas' => __( 'Separate amenities with commas', 'wp-bnb' ),
|
||||||
|
'add_or_remove_items' => __( 'Add or remove amenities', 'wp-bnb' ),
|
||||||
|
'choose_from_most_used' => __( 'Choose from the most used amenities', 'wp-bnb' ),
|
||||||
|
'not_found' => __( 'No amenities found.', 'wp-bnb' ),
|
||||||
|
'menu_name' => __( 'Amenities', 'wp-bnb' ),
|
||||||
|
'back_to_items' => __( '← Back to Amenities', 'wp-bnb' ),
|
||||||
|
);
|
||||||
|
|
||||||
|
$args = array(
|
||||||
|
'labels' => $labels,
|
||||||
|
'hierarchical' => false, // Non-hierarchical (like tags).
|
||||||
|
'public' => true,
|
||||||
|
'publicly_queryable' => true,
|
||||||
|
'show_ui' => true,
|
||||||
|
'show_in_menu' => true,
|
||||||
|
'show_in_nav_menus' => true,
|
||||||
|
'show_in_rest' => true,
|
||||||
|
'show_tagcloud' => true,
|
||||||
|
'show_in_quick_edit' => true,
|
||||||
|
'show_admin_column' => true,
|
||||||
|
'rewrite' => array(
|
||||||
|
'slug' => 'amenity',
|
||||||
|
'with_front' => false,
|
||||||
|
),
|
||||||
|
'query_var' => true,
|
||||||
|
'capabilities' => array(
|
||||||
|
'manage_terms' => 'manage_options',
|
||||||
|
'edit_terms' => 'manage_options',
|
||||||
|
'delete_terms' => 'manage_options',
|
||||||
|
'assign_terms' => 'edit_posts',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
register_taxonomy( self::TAXONOMY, array( 'bnb_room' ), $args );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom fields to the add term form.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function add_form_fields(): void {
|
||||||
|
?>
|
||||||
|
<div class="form-field term-icon-wrap">
|
||||||
|
<label for="amenity-icon"><?php esc_html_e( 'Icon', 'wp-bnb' ); ?></label>
|
||||||
|
<select name="amenity_icon" id="amenity-icon">
|
||||||
|
<?php foreach ( self::get_icon_options() as $value => $label ) : ?>
|
||||||
|
<option value="<?php echo esc_attr( $value ); ?>"><?php echo esc_html( $label ); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<p><?php esc_html_e( 'Select an icon to represent this amenity.', 'wp-bnb' ); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom fields to the edit term form.
|
||||||
|
*
|
||||||
|
* @param \WP_Term $term Current term object.
|
||||||
|
* @param string $taxonomy Current taxonomy slug.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function edit_form_fields( \WP_Term $term, string $taxonomy ): void {
|
||||||
|
$icon = get_term_meta( $term->term_id, 'amenity_icon', true );
|
||||||
|
?>
|
||||||
|
<tr class="form-field term-icon-wrap">
|
||||||
|
<th scope="row">
|
||||||
|
<label for="amenity-icon"><?php esc_html_e( 'Icon', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<select name="amenity_icon" id="amenity-icon">
|
||||||
|
<?php foreach ( self::get_icon_options() as $value => $label ) : ?>
|
||||||
|
<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $icon, $value ); ?>>
|
||||||
|
<?php echo esc_html( $label ); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<p class="description"><?php esc_html_e( 'Select an icon to represent this amenity.', 'wp-bnb' ); ?></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save term meta data.
|
||||||
|
*
|
||||||
|
* @param int $term_id Term ID.
|
||||||
|
* @param int $tt_id Term taxonomy ID.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function save_term_meta( int $term_id, int $tt_id ): void {
|
||||||
|
if ( isset( $_POST['amenity_icon'] ) ) {
|
||||||
|
update_term_meta(
|
||||||
|
$term_id,
|
||||||
|
'amenity_icon',
|
||||||
|
sanitize_text_field( wp_unslash( $_POST['amenity_icon'] ) )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom columns to the taxonomy list.
|
||||||
|
*
|
||||||
|
* @param array $columns Existing columns.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function add_columns( array $columns ): array {
|
||||||
|
$new_columns = array();
|
||||||
|
foreach ( $columns as $key => $value ) {
|
||||||
|
$new_columns[ $key ] = $value;
|
||||||
|
if ( 'name' === $key ) {
|
||||||
|
$new_columns['icon'] = __( 'Icon', 'wp-bnb' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $new_columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render custom column content.
|
||||||
|
*
|
||||||
|
* @param string $content Column content.
|
||||||
|
* @param string $column_name Column name.
|
||||||
|
* @param int $term_id Term ID.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function render_column( string $content, string $column_name, int $term_id ): string {
|
||||||
|
if ( 'icon' === $column_name ) {
|
||||||
|
$icon = get_term_meta( $term_id, 'amenity_icon', true );
|
||||||
|
if ( $icon ) {
|
||||||
|
return '<span class="dashicons dashicons-' . esc_attr( $icon ) . '"></span>';
|
||||||
|
}
|
||||||
|
return '—';
|
||||||
|
}
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get available icon options.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public static function get_icon_options(): array {
|
||||||
|
return array(
|
||||||
|
'' => __( '— Select Icon —', 'wp-bnb' ),
|
||||||
|
'wifi' => __( 'WiFi', 'wp-bnb' ),
|
||||||
|
'car' => __( 'Parking', 'wp-bnb' ),
|
||||||
|
'food' => __( 'Breakfast', 'wp-bnb' ),
|
||||||
|
'palmtree' => __( 'Garden/Pool', 'wp-bnb' ),
|
||||||
|
'pets' => __( 'Pet Friendly', 'wp-bnb' ),
|
||||||
|
'universal-access' => __( 'Accessibility', 'wp-bnb' ),
|
||||||
|
'tv' => __( 'Television', 'wp-bnb' ),
|
||||||
|
'superhero-alt' => __( 'Air Conditioning', 'wp-bnb' ),
|
||||||
|
'coffee' => __( 'Coffee/Tea', 'wp-bnb' ),
|
||||||
|
'admin-home' => __( 'Kitchen', 'wp-bnb' ),
|
||||||
|
'businessman' => __( 'Business Center', 'wp-bnb' ),
|
||||||
|
'heart' => __( 'Spa/Wellness', 'wp-bnb' ),
|
||||||
|
'groups' => __( 'Family Friendly', 'wp-bnb' ),
|
||||||
|
'location-alt' => __( 'Central Location', 'wp-bnb' ),
|
||||||
|
'building' => __( 'Elevator', 'wp-bnb' ),
|
||||||
|
'store' => __( 'Minibar', 'wp-bnb' ),
|
||||||
|
'admin-appearance' => __( 'Room Service', 'wp-bnb' ),
|
||||||
|
'shield' => __( 'Safe', 'wp-bnb' ),
|
||||||
|
'privacy' => __( 'Non-Smoking', 'wp-bnb' ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default amenities to seed on activation.
|
||||||
|
*
|
||||||
|
* @return array<string, array{icon: string}>
|
||||||
|
*/
|
||||||
|
public static function get_default_terms(): array {
|
||||||
|
return array(
|
||||||
|
__( 'WiFi', 'wp-bnb' ) => array( 'icon' => 'wifi' ),
|
||||||
|
__( 'Parking', 'wp-bnb' ) => array( 'icon' => 'car' ),
|
||||||
|
__( 'Breakfast Included', 'wp-bnb' ) => array( 'icon' => 'food' ),
|
||||||
|
__( 'Air Conditioning', 'wp-bnb' ) => array( 'icon' => 'superhero-alt' ),
|
||||||
|
__( 'Television', 'wp-bnb' ) => array( 'icon' => 'tv' ),
|
||||||
|
__( 'Pet Friendly', 'wp-bnb' ) => array( 'icon' => 'pets' ),
|
||||||
|
__( 'Wheelchair Accessible', 'wp-bnb' ) => array( 'icon' => 'universal-access' ),
|
||||||
|
__( 'Non-Smoking', 'wp-bnb' ) => array( 'icon' => 'privacy' ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
224
src/Taxonomies/RoomType.php
Normal file
224
src/Taxonomies/RoomType.php
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Room Type taxonomy.
|
||||||
|
*
|
||||||
|
* Hierarchical taxonomy for room types like Standard, Suite, Family, etc.
|
||||||
|
*
|
||||||
|
* @package Magdev\WpBnb\Taxonomies
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare( strict_types=1 );
|
||||||
|
|
||||||
|
namespace Magdev\WpBnb\Taxonomies;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Room Type taxonomy class.
|
||||||
|
*/
|
||||||
|
final class RoomType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Taxonomy slug.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public const TAXONOMY = 'bnb_room_type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the taxonomy.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function init(): void {
|
||||||
|
add_action( 'init', array( self::class, 'register' ) );
|
||||||
|
add_action( 'bnb_room_type_add_form_fields', array( self::class, 'add_form_fields' ) );
|
||||||
|
add_action( 'bnb_room_type_edit_form_fields', array( self::class, 'edit_form_fields' ), 10, 2 );
|
||||||
|
add_action( 'created_bnb_room_type', array( self::class, 'save_term_meta' ), 10, 2 );
|
||||||
|
add_action( 'edited_bnb_room_type', array( self::class, 'save_term_meta' ), 10, 2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the taxonomy.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function register(): void {
|
||||||
|
$labels = array(
|
||||||
|
'name' => _x( 'Room Types', 'taxonomy general name', 'wp-bnb' ),
|
||||||
|
'singular_name' => _x( 'Room Type', 'taxonomy singular name', 'wp-bnb' ),
|
||||||
|
'search_items' => __( 'Search Room Types', 'wp-bnb' ),
|
||||||
|
'all_items' => __( 'All Room Types', 'wp-bnb' ),
|
||||||
|
'parent_item' => __( 'Parent Room Type', 'wp-bnb' ),
|
||||||
|
'parent_item_colon' => __( 'Parent Room Type:', 'wp-bnb' ),
|
||||||
|
'edit_item' => __( 'Edit Room Type', 'wp-bnb' ),
|
||||||
|
'update_item' => __( 'Update Room Type', 'wp-bnb' ),
|
||||||
|
'add_new_item' => __( 'Add New Room Type', 'wp-bnb' ),
|
||||||
|
'new_item_name' => __( 'New Room Type Name', 'wp-bnb' ),
|
||||||
|
'menu_name' => __( 'Room Types', 'wp-bnb' ),
|
||||||
|
'back_to_items' => __( '← Back to Room Types', 'wp-bnb' ),
|
||||||
|
'not_found' => __( 'No room types found.', 'wp-bnb' ),
|
||||||
|
);
|
||||||
|
|
||||||
|
$args = array(
|
||||||
|
'labels' => $labels,
|
||||||
|
'hierarchical' => true, // Hierarchical (like categories).
|
||||||
|
'public' => true,
|
||||||
|
'publicly_queryable' => true,
|
||||||
|
'show_ui' => true,
|
||||||
|
'show_in_menu' => true,
|
||||||
|
'show_in_nav_menus' => true,
|
||||||
|
'show_in_rest' => true,
|
||||||
|
'show_in_quick_edit' => true,
|
||||||
|
'show_admin_column' => true,
|
||||||
|
'rewrite' => array(
|
||||||
|
'slug' => 'room-type',
|
||||||
|
'with_front' => false,
|
||||||
|
'hierarchical' => true,
|
||||||
|
),
|
||||||
|
'query_var' => true,
|
||||||
|
'capabilities' => array(
|
||||||
|
'manage_terms' => 'manage_options',
|
||||||
|
'edit_terms' => 'manage_options',
|
||||||
|
'delete_terms' => 'manage_options',
|
||||||
|
'assign_terms' => 'edit_posts',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
register_taxonomy( self::TAXONOMY, array( 'bnb_room' ), $args );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom fields to the add term form.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function add_form_fields(): void {
|
||||||
|
?>
|
||||||
|
<div class="form-field term-base-capacity-wrap">
|
||||||
|
<label for="room-type-base-capacity"><?php esc_html_e( 'Base Capacity', 'wp-bnb' ); ?></label>
|
||||||
|
<input type="number" name="room_type_base_capacity" id="room-type-base-capacity" value="2" min="1" max="20">
|
||||||
|
<p><?php esc_html_e( 'Default number of guests this room type typically accommodates.', 'wp-bnb' ); ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="form-field term-sort-order-wrap">
|
||||||
|
<label for="room-type-sort-order"><?php esc_html_e( 'Sort Order', 'wp-bnb' ); ?></label>
|
||||||
|
<input type="number" name="room_type_sort_order" id="room-type-sort-order" value="0" min="0">
|
||||||
|
<p><?php esc_html_e( 'Display order for this room type (lower numbers appear first).', 'wp-bnb' ); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom fields to the edit term form.
|
||||||
|
*
|
||||||
|
* @param \WP_Term $term Current term object.
|
||||||
|
* @param string $taxonomy Current taxonomy slug.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function edit_form_fields( \WP_Term $term, string $taxonomy ): void {
|
||||||
|
$base_capacity = get_term_meta( $term->term_id, 'room_type_base_capacity', true );
|
||||||
|
$sort_order = get_term_meta( $term->term_id, 'room_type_sort_order', true );
|
||||||
|
?>
|
||||||
|
<tr class="form-field term-base-capacity-wrap">
|
||||||
|
<th scope="row">
|
||||||
|
<label for="room-type-base-capacity"><?php esc_html_e( 'Base Capacity', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="number" name="room_type_base_capacity" id="room-type-base-capacity"
|
||||||
|
value="<?php echo esc_attr( $base_capacity ?: '2' ); ?>" min="1" max="20">
|
||||||
|
<p class="description"><?php esc_html_e( 'Default number of guests this room type typically accommodates.', 'wp-bnb' ); ?></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="form-field term-sort-order-wrap">
|
||||||
|
<th scope="row">
|
||||||
|
<label for="room-type-sort-order"><?php esc_html_e( 'Sort Order', 'wp-bnb' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="number" name="room_type_sort_order" id="room-type-sort-order"
|
||||||
|
value="<?php echo esc_attr( $sort_order ?: '0' ); ?>" min="0">
|
||||||
|
<p class="description"><?php esc_html_e( 'Display order for this room type (lower numbers appear first).', 'wp-bnb' ); ?></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save term meta data.
|
||||||
|
*
|
||||||
|
* @param int $term_id Term ID.
|
||||||
|
* @param int $tt_id Term taxonomy ID.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function save_term_meta( int $term_id, int $tt_id ): void {
|
||||||
|
if ( isset( $_POST['room_type_base_capacity'] ) ) {
|
||||||
|
update_term_meta(
|
||||||
|
$term_id,
|
||||||
|
'room_type_base_capacity',
|
||||||
|
absint( $_POST['room_type_base_capacity'] )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( isset( $_POST['room_type_sort_order'] ) ) {
|
||||||
|
update_term_meta(
|
||||||
|
$term_id,
|
||||||
|
'room_type_sort_order',
|
||||||
|
absint( $_POST['room_type_sort_order'] )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default room types to seed on activation.
|
||||||
|
*
|
||||||
|
* @return array<string, array{capacity: int, order: int, children?: array<string, array{capacity: int, order: int}>}>
|
||||||
|
*/
|
||||||
|
public static function get_default_terms(): array {
|
||||||
|
return array(
|
||||||
|
__( 'Standard', 'wp-bnb' ) => array(
|
||||||
|
'capacity' => 2,
|
||||||
|
'order' => 10,
|
||||||
|
'children' => array(
|
||||||
|
__( 'Single', 'wp-bnb' ) => array(
|
||||||
|
'capacity' => 1,
|
||||||
|
'order' => 11,
|
||||||
|
),
|
||||||
|
__( 'Double', 'wp-bnb' ) => array(
|
||||||
|
'capacity' => 2,
|
||||||
|
'order' => 12,
|
||||||
|
),
|
||||||
|
__( 'Twin', 'wp-bnb' ) => array(
|
||||||
|
'capacity' => 2,
|
||||||
|
'order' => 13,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
__( 'Superior', 'wp-bnb' ) => array(
|
||||||
|
'capacity' => 2,
|
||||||
|
'order' => 20,
|
||||||
|
),
|
||||||
|
__( 'Suite', 'wp-bnb' ) => array(
|
||||||
|
'capacity' => 2,
|
||||||
|
'order' => 30,
|
||||||
|
'children' => array(
|
||||||
|
__( 'Junior Suite', 'wp-bnb' ) => array(
|
||||||
|
'capacity' => 2,
|
||||||
|
'order' => 31,
|
||||||
|
),
|
||||||
|
__( 'Executive Suite', 'wp-bnb' ) => array(
|
||||||
|
'capacity' => 2,
|
||||||
|
'order' => 32,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
__( 'Family', 'wp-bnb' ) => array(
|
||||||
|
'capacity' => 4,
|
||||||
|
'order' => 40,
|
||||||
|
),
|
||||||
|
__( 'Accessible', 'wp-bnb' ) => array(
|
||||||
|
'capacity' => 2,
|
||||||
|
'order' => 50,
|
||||||
|
),
|
||||||
|
__( 'Apartment', 'wp-bnb' ) => array(
|
||||||
|
'capacity' => 4,
|
||||||
|
'order' => 60,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
wp-bnb.php
16
wp-bnb.php
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: WP BnB Management
|
* Plugin Name: WP BnB Management
|
||||||
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wp-bnb
|
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wp-bnb
|
||||||
* Description: A comprehensive Bed & Breakfast management system for WordPress. Manage buildings, rooms, bookings, and guests.
|
* Description: A comprehensive Bed & Breakfast management system for WordPress. Manage buildings, rooms, bookings, and guests.
|
||||||
* Version: 0.0.1
|
* Version: 0.1.0
|
||||||
* Requires at least: 6.0
|
* Requires at least: 6.0
|
||||||
* Requires PHP: 8.3
|
* Requires PHP: 8.3
|
||||||
* Author: Marco Graetsch
|
* Author: Marco Graetsch
|
||||||
@@ -24,7 +24,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Plugin version constant - MUST match Version in header above.
|
// Plugin version constant - MUST match Version in header above.
|
||||||
define( 'WP_BNB_VERSION', '0.0.1' );
|
define( 'WP_BNB_VERSION', '0.1.0' );
|
||||||
|
|
||||||
// Plugin path constants.
|
// Plugin path constants.
|
||||||
define( 'WP_BNB_PATH', plugin_dir_path( __FILE__ ) );
|
define( 'WP_BNB_PATH', plugin_dir_path( __FILE__ ) );
|
||||||
@@ -155,6 +155,18 @@ function wp_bnb_activate(): void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load Composer autoloader for activation.
|
||||||
|
$autoloader = WP_BNB_PATH . 'vendor/autoload.php';
|
||||||
|
if ( file_exists( $autoloader ) ) {
|
||||||
|
require_once $autoloader;
|
||||||
|
|
||||||
|
// Register post types and taxonomies before flushing rewrite rules.
|
||||||
|
\Magdev\WpBnb\Taxonomies\Amenity::register();
|
||||||
|
\Magdev\WpBnb\Taxonomies\RoomType::register();
|
||||||
|
\Magdev\WpBnb\PostTypes\Building::register();
|
||||||
|
\Magdev\WpBnb\PostTypes\Room::register();
|
||||||
|
}
|
||||||
|
|
||||||
// Set default options.
|
// Set default options.
|
||||||
add_option( 'wp_bnb_version', WP_BNB_VERSION );
|
add_option( 'wp_bnb_version', WP_BNB_VERSION );
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user