All checks were successful
Create Release Package / build-release (push) Successful in 1m6s
Phase 1 implementation includes: - Custom Post Type: Buildings with address, contact, and details meta - Custom Post Type: Rooms with building relationship and gallery - Custom Taxonomy: Room Types (hierarchical) - Custom Taxonomy: Amenities (non-hierarchical with icons) - Admin columns, filters, and status badges - Gallery meta box with media library integration Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
563 lines
17 KiB
PHP
563 lines
17 KiB
PHP
<?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 );
|
|
}
|
|
}
|