Files
wp-bnb/src/PostTypes/Building.php
magdev f24a347bb1
All checks were successful
Create Release Package / build-release (push) Successful in 1m6s
Add core data structures for Buildings and Rooms (v0.1.0)
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>
2026-01-31 13:45:06 +01:00

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 );
}
}