Plugin Directory

Changeset 3495560


Ignore:
Timestamp:
03/31/2026 12:46:25 PM (18 hours ago)
Author:
bookiflex
Message:

release: v1.2.0

Location:
bookiflex
Files:
18 added
6 deleted
32 edited
1 copied

Legend:

Unmodified
Added
Removed
  • bookiflex/tags/1.2.0/assets/manifest.json

    r3493377 r3495560  
    11{
    2   "version": "1.1.1",
    3   "generated": "2026-03-28T14:15:22.310Z",
     2  "version": "1.2.0",
     3  "generated": "2026-03-31T12:43:23.451Z",
    44  "external": {
    55    "vue": "assets/vendor/vue/vue.global.prod.js",
     
    1515    },
    1616    "admin": {
    17       "js": "assets/free/admin/index.B_cl0VpC.js",
    18       "css": "assets/free/admin/style.B9Y7Sqrs.css"
     17      "js": "assets/free/admin/index.D9SCTotr.js",
     18      "css": "assets/free/admin/style.CEmLATbJ.css"
    1919    },
    2020    "widgets": {
    2121      "controller": {
    22         "js": "assets/common/widgets/controller/index.B2RbsrGV.js"
     22        "js": "assets/common/widgets/controller/index.B3SGvgXg.js"
    2323      },
    2424      "booking": {
  • bookiflex/tags/1.2.0/bookiflex.php

    r3493377 r3495560  
    66 * Description: Direct Booking Widget for Apartments and Short-Term Rentals
    77 * Plugin URI: https://bookiflex.com
    8  * Version: 1.1.1
     8 * Version: 1.2.0
    99 * Author: BookiFlex
    1010 * License: GPL-2.0-or-later
     
    8080}
    8181// Define plugin constants
    82 define( 'BOOKIFLEX_VERSION', '1.1.1' );
     82define( 'BOOKIFLEX_VERSION', '1.2.0' );
    8383define( 'BOOKIFLEX_FILE', __FILE__ );
    8484define( 'BOOKIFLEX_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
  • bookiflex/tags/1.2.0/readme.txt

    r3493377 r3495560  
    55Tested up to: 6.9
    66Requires PHP: 8.1
    7 Stable tag: 1.1.1
     7Stable tag: 1.2.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    149149== Changelog ==
    150150
    151 = 1.1.1 =
    152 * Fix a problem with Notifications when VAT is enabled
    153 
    154 = 1.1.0 =
    155 * Improve interface behavior (search widget)
    156 * Fix minor bugs
    157 
    158 = 1.0.2 =
    159 * Fix onboarding flow
    160 
    161 = 1.0.0 =
    162 * Initial release
    163 * Floating booking button and modal widget
    164 * Room setup, pricing, availability, and email notifications
     151= 1.2.0 - 31 March 2026 =
     152* Improved: Redesigned admin panel for better usability and cleaner interface
     153* Improved: Added class attributes to modal trigger elements for easier customization
     154* Fixed: Offers now load correctly when clicking custom trigger elements
     155* Fixed: Admin nonce now refreshes automatically to prevent expired session errors
     156
     157[See changelog for all versions](https://plugins.svn.wordpress.org/bookiflex/trunk/CHANGELOG.txt).
    165158
    166159
  • bookiflex/tags/1.2.0/src/Admin/AssetsManager.php

    r3487384 r3495560  
    4646        // Vue — single path, dev manifest overrides prod vue path
    4747        $this->enqueueScript( 'bookiflex-vue', $this->assetUrl( $this->asset( 'external', 'vue' ) ), ['wp-i18n'] );
     48        // Carbon fields style
     49        $this->enqueueStyle( 'carbon-fields-style', $this->assetUrl( 'assets/vendor/carbon_fields/style.css' ), ['carbon-fields-metaboxes'] );
     50        // Radio card field
     51        $this->enqueueScript(
     52            'bookiflex-radio-card',
     53            $this->assetUrl( 'assets/vendor/carbon_fields/radio-card.js' ),
     54            ['carbon-fields-vendor', 'carbon-fields-core', 'carbon-fields-metaboxes'],
     55            true
     56        );
    4857        $this->enqueueAdminApp();
    4958        wp_set_script_translations( 'bookiflex-admin-app', 'bookiflex', $this->plugin->getPluginDir() . 'languages' );
  • bookiflex/tags/1.2.0/src/Admin/CPT/AccommodationType.php

    r3478607 r3495560  
    1515
    1616use BookiFlex\Core\Admin\CPT\AccommodationType as BaseCPT;
     17use BookiFlex\Core\Contract\PluginInterface;
    1718use BookiFlex\Core\Model\AccommodationType\BookingUnitTypeInterface;
    1819use BookiFlex\Core\Util\ProFeature;
     
    2122defined( 'ABSPATH' ) || exit;
    2223class AccommodationType extends BaseCPT {
     24    public function register( PluginInterface $plugin ) : void {
     25        parent::register( $plugin );
     26        $this->customizeMenuOrder( __( 'Display order', 'bookiflex' ), __( 'Controls the display order of this rental unit on the booking widget. Lower numbers appear first.', 'bookiflex' ) );
     27        $this->customizeExcerpt( __( 'Description', 'bookiflex' ), __( 'A brief description of this unit. This text may be shown to guests during the booking process.', 'bookiflex' ) );
     28    }
     29
    2330    public function registerMetaBoxes() : void {
    2431        $overviewFields = [
    25             Field::make( 'text', self::FIELD_SHORT_CODE, __( 'Short code', 'bookiflex' ) )->set_width( 34 )->set_attribute( 'maxLength', '10' )->set_attribute( 'readOnly', 'true' )->set_attribute( 'placeholder', __( 'e.g. STD', 'bookiflex' ) )->help_text( __( 'A short identifier for this rental unit (generated automatically). For example: APT for Apartment, CAB for Cabin. This code may be used internally or in reports.', 'bookiflex' ) ),
     32            Field::make( 'text', self::FIELD_ADDRESS, __( 'Address', 'bookiflex' ) )->set_required()->set_help_text( __( 'The full address of the property.', 'bookiflex' ) ),
    2633            Field::make( 'text', self::FIELD_LIVING_SPACE, __( 'Living space', 'bookiflex' ) )->set_width( 33 )->set_attribute( 'placeholder', __( 'e.g. 30 m², 250 ft², 45', 'bookiflex' ) )->help_text( __( 'Specify the total living area of the rental unit (in square meters, square feet, or any unit you prefer). This information is often displayed on the unit page.', 'bookiflex' ) ),
    2734            Field::make( 'text', self::FIELD_ROOMS_COUNT, __( 'Number of rooms', 'bookiflex' ) )->set_width( 33 )->set_attribute( 'type', 'number' )->set_attribute( 'min', '1' )->set_attribute( 'placeholder', __( 'e.g. 3', 'bookiflex' ) )->help_text( __( 'Total number of separate rooms in this rental unit (bedrooms, living room, kitchen, etc.).', 'bookiflex' ) ),
     
    3239        $guestsBedFields = [Field::make( 'separator', 'bflex_main_placement_separator', __( 'Main placements', 'bookiflex' ) ), Field::make( 'select', self::FIELD_MAIN_SLEEPING_CAPACITY, __( 'Main sleeping capacity', 'bookiflex' ) )->set_width( 50 )->set_options( array_combine( range( 1, 10 ), range( 1, 10 ) ) )->set_default_value( 2 )->help_text( __( 'Enter the total number of main sleeping places (e.g., 1 double bed = 2 places).', 'bookiflex' ) ), Field::make( 'set', self::FIELD_MAIN_SLEEPING_ALLOWED_OCCUPANCY, __( 'Allowed occupancy', 'bookiflex' ) )->set_width( 50 )->set_options( array_combine( range( 1, 10 ), range( 1, 10 ) ) )->help_text( __( 'Select how many guests can stay in this accommodation. Each selected option can have its own price. <strong>Only selected occupancy options will be available for booking and pricing.</strong>', 'bookiflex' ) )];
    3340        $guestsBedFields = array_merge( $guestsBedFields, $this->addNotice() );
    34         Container::make( 'post_meta', 'at_configuration', __( 'Configuration', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->add_tab( __( 'Overview', 'bookiflex' ), $overviewFields )->add_tab( __( 'Guests & beds', 'bookiflex' ), $guestsBedFields )->add_tab( __( 'Contact information', 'bookiflex' ), [
    35             Field::make( 'separator', 'bflex_unit_address_separator', __( 'Rental unit address', 'bookiflex' ) ),
    36             Field::make( 'text', self::FIELD_ADDRESS, __( 'Address', 'bookiflex' ) )->set_required()->set_help_text( __( 'The full address of the property.', 'bookiflex' ) ),
    37             Field::make( 'text', self::FIELD_LONGITUDE, __( 'Longitude', 'bookiflex' ) )->set_width( 50 )->set_help_text( __( 'Used to display the property on a map.', 'bookiflex' ) ),
    38             Field::make( 'text', self::FIELD_LATITUDE, __( 'Latitude', 'bookiflex' ) )->set_width( 50 )->set_help_text( __( 'Used to display the property on a map.', 'bookiflex' ) ),
    39             Field::make( 'separator', 'bflex_unit_email_separator', __( 'Email information', 'bookiflex' ) ),
     41        Container::make( 'post_meta', 'at_short_code', __( 'Short code', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->set_context( 'side' )->add_fields( [Field::make( 'text', self::FIELD_SHORT_CODE, '' )->set_attribute( 'maxLength', '10' )->set_attribute( 'readOnly', 'true' )->set_attribute( 'placeholder', __( 'e.g. STD', 'bookiflex' ) )->help_text( __( 'A short identifier for this rental unit (generated automatically). For example: APT for Apartment, CAB for Cabin. This code may be used internally or in reports.', 'bookiflex' ) )] );
     42        Container::make( 'post_meta', 'at_configuration', __( 'Configuration', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->set_layout( 'tabbed-vertical' )->add_tab( __( 'Overview', 'bookiflex' ), $overviewFields )->add_tab( __( 'Guests & beds', 'bookiflex' ), $guestsBedFields )->add_tab( __( 'Email information', 'bookiflex' ), [
    4043            Field::make( 'text', self::FIELD_PHONE, __( 'Phone Number', 'bookiflex' ) )->set_width( 50 )->set_attribute( 'type', 'tel' )->set_help_text( __( 'For guest support and communication (if not specified, the default one will be used).', 'bookiflex' ) ),
    4144            Field::make( 'text', self::FIELD_EMAIL, __( 'Email', 'bookiflex' ) )->set_width( 50 )->set_attribute( 'type', 'email' )->set_help_text( __( 'For guest support and communication (if not specified, the default one will be used).', 'bookiflex' ) ),
    42             Field::make( 'textarea', self::FIELD_CHECKIN_INSTRUCTIONS, __( 'Check-in instructions', 'bookiflex' ) )->set_rows( 4 )->help_text( __( 'Important information sent to guests after booking confirmation. Include details like door codes, parking instructions, key pickup location, or check-in procedures specific to this rental unit.', 'bookiflex' ) )
     45            Field::make( 'textarea', self::FIELD_CHECKIN_INSTRUCTIONS, __( 'Check-in instructions', 'bookiflex' ) )->set_rows( 4 )->help_text( __( 'Important information sent to guests after booking confirmation. Include details like door codes, parking instructions, key pickup location, or check-in procedures specific to this rental unit.', 'bookiflex' ) ),
     46            Field::make( 'separator', 'bflex_unit_address_separator', __( 'Map coordinates', 'bookiflex' ) ),
     47            Field::make( 'text', self::FIELD_LONGITUDE, __( 'Longitude', 'bookiflex' ) )->set_width( 50 )->set_help_text( __( 'Used to display the property on a map.', 'bookiflex' ) ),
     48            Field::make( 'text', self::FIELD_LATITUDE, __( 'Latitude', 'bookiflex' ) )->set_width( 50 )->set_help_text( __( 'Used to display the property on a map.', 'bookiflex' ) )
    4349        ] );
    4450        Container::make( 'post_meta', __( 'Gallery', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->add_fields( [Field::make( 'media_gallery', self::FIELD_ACCOMMODATION_GALLERY, __( 'Gallery', 'bookiflex' ) )] );
  • bookiflex/tags/1.2.0/src/Admin/CPT/CancellationPolicy.php

    r3478607 r3495560  
    1717use BookiFlex\Core\Contract\CancellationPolicy\CancellationPolicyTypeInterface;
    1818use BookiFlex\Core\Util\ProFeature;
     19use BookiFlex\Core\Admin\Field\RadioCard;
     20use BookiFlex\Core\Admin\Field\SectionNotice;
    1921use Carbon_Fields\Container;
    2022use Carbon_Fields\Field;
     
    2224class CancellationPolicy extends BaseCPT {
    2325    public function registerMetaBoxes() : void {
    24         // Build policy options
     26        // Build policy options with descriptions for radio cards
    2527        $policyOptions = [
    26             self::RULE_FULLY_FLEXIBLE => __( 'Fully flexible', 'bookiflex' ),
    27             self::RULE_STRICT         => __( 'Strict', 'bookiflex' ),
    28             self::RULE_FIRST_NIGHT    => __( 'First night penalty', 'bookiflex' ),
    29             self::RULE_NON_REFUNDABLE => __( 'Non-refundable', 'bookiflex' ),
     28            self::RULE_FULLY_FLEXIBLE => [
     29                'title'       => __( 'Fully flexible', 'bookiflex' ),
     30                'description' => __( 'Free cancellation at any time, no penalty will be charged.', 'bookiflex' ),
     31            ],
     32            self::RULE_STRICT         => [
     33                'title'       => __( 'Strict', 'bookiflex' ),
     34                'description' => __( 'Configurable cancellation with custom time window and penalty percentage.', 'bookiflex' ),
     35            ],
     36            self::RULE_FIRST_NIGHT    => [
     37                'title'       => __( 'First night penalty', 'bookiflex' ),
     38                'description' => __( 'Cancellation at any time will incur a penalty equal to the first night\'s stay.', 'bookiflex' ),
     39            ],
     40            self::RULE_NON_REFUNDABLE => [
     41                'title'       => __( 'Non-refundable', 'bookiflex' ),
     42                'description' => __( 'Cancellation at any time will incur a 100% charge of the booking amount.', 'bookiflex' ),
     43            ],
    3044        ];
    3145        $fields = [
    32             Field::make( 'html', 'cancellation_policy_html' )->set_html( sprintf( '<p>%s</p>', __( 'A cancellation policy defines how and when cancellation penalties apply. Later, this policy can be assigned to a rate plan.', 'bookiflex' ) ) ),
    33             Field::make( 'radio', self::FIELD_PREDEFINED_RULE, __( 'Rule', 'bookiflex' ) )->set_width( 50 )->set_help_text( __( 'Chose one of predefined policies.', 'bookiflex' ) )->set_options( $policyOptions ),
    34             Field::make( 'html', self::FIELD_PREDEFINED_RULE . '_html' )->set_width( 50 )->set_html( sprintf( '<p><strong>%s</strong> - %s</p>', __( 'Fully flexible', 'bookiflex' ), __( 'Free cancellation at any time, no penalty will be charged.', 'bookiflex' ) ) . sprintf( '<p><strong>%s</strong> - %s</p>', __( 'Strict', 'bookiflex' ), __( 'Configurable cancellation with custom time window and penalty percentage.', 'bookiflex' ) ) . sprintf( '<p><strong>%s</strong> - %s</p>', __( 'First night penalty', 'bookiflex' ), __( 'Cancellation at any time will incur a penalty equal to the first night\'s stay.', 'bookiflex' ) ) . sprintf( '<p><strong>%s</strong> - %s</p>', __( 'Non-refundable', 'bookiflex' ), __( 'Cancellation at any time will incur a 100% charge of the booking amount.', 'bookiflex' ) ) ),
     46            SectionNotice::make( 'cancellation_policy_html', __( 'A cancellation policy defines how and when cancellation penalties apply. Later, this policy can be assigned to a rate plan.', 'bookiflex' ) ),
     47            ...RadioCard::make(
     48                self::FIELD_PREDEFINED_RULE,
     49                __( 'Rule', 'bookiflex' ),
     50                $policyOptions,
     51                self::RULE_FULLY_FLEXIBLE
     52            ),
     53            Field::make( 'separator', 'bflex__cp__strict_separator', __( 'Configuration', 'bookiflex' ) )->set_conditional_logic( [[
     54                'field' => self::FIELD_PREDEFINED_RULE,
     55                'value' => self::RULE_STRICT,
     56            ]] ),
    3557            Field::make( 'select', self::FIELD_STRICT_TIME_WINDOW, __( 'Cancellation window', 'bookiflex' ) )->set_conditional_logic( [[
    3658                'field' => self::FIELD_PREDEFINED_RULE,
  • bookiflex/tags/1.2.0/src/Admin/CPT/PaymentType.php

    r3478607 r3495560  
    1515
    1616use BookiFlex\Core\Admin\CPT\PaymentType as BaseCPT;
     17use BookiFlex\Core\Admin\Field\RadioCard;
     18use BookiFlex\Core\Admin\Field\SectionNotice;
     19use BookiFlex\Core\Contract\PluginInterface;
    1720use BookiFlex\Core\Model\PaymentType\PaymentTypeInterface;
    1821use BookiFlex\Core\Util\ProFeature;
     
    2124defined( 'ABSPATH' ) || exit;
    2225class PaymentType extends BaseCPT {
     26    public function register( PluginInterface $plugin ) : void {
     27        parent::register( $plugin );
     28        $this->customizeExcerpt( __( 'Description', 'bookiflex' ), __( 'A brief description of this payment type. This text may be shown to guests during the booking process.', 'bookiflex' ) );
     29    }
     30
    2331    public function registerMetaBoxes() : void {
    2432        $paymentGateways = $this->getPaymentGateways();
    2533        // Build payment settings fields
    2634        $paymentSettingsFields = [
    27             Field::make( 'radio', self::FIELD_CALCULATOR_TYPE, __( 'How will the payment be collected?', 'bookiflex' ) )->set_width( 50 )->set_help_text( __( 'Choose when the booking gets confirmed. Options requiring payment will hold the booking until the guest pays within the allowed time — if not paid, the booking is canceled and availability is restored. Options without payment requirement confirm the booking immediately.', 'bookiflex' ) )->set_options( [
    28                 PaymentTypeInterface::PAYMENT_ON_ARRIVAL_TYPE   => __( 'Guest pays on arrival', 'bookiflex' ),
    29                 PaymentTypeInterface::BANK_PAYMENT_TYPE         => __( 'Bank transfer (confirmed without payment)', 'bookiflex' ),
    30                 PaymentTypeInterface::BANK_PAYMENT_PAYABLE_TYPE => __( 'Bank transfer (payment required to confirm)', 'bookiflex' ),
    31                 PaymentTypeInterface::INTERNET_PAYMENT_TYPE     => __( 'Guest pays online (via payment gateway)', 'bookiflex' ),
    32             ] ),
     35            SectionNotice::make( 'payment_type_notice', __( 'Choose when the booking gets confirmed. Options requiring payment will hold the booking until the guest pays within the allowed time — if not paid, the booking is canceled and availability is restored. Options without payment requirement confirm the booking immediately.', 'bookiflex' ) ),
     36            ...RadioCard::make(
     37                self::FIELD_CALCULATOR_TYPE,
     38                __( 'How will the payment be collected?', 'bookiflex' ),
     39                [
     40                    PaymentTypeInterface::PAYMENT_ON_ARRIVAL_TYPE   => [
     41                        'title'       => __( 'Guest pays on arrival', 'bookiflex' ),
     42                        'description' => __( 'Booking is confirmed immediately. No online payment required.', 'bookiflex' ),
     43                    ],
     44                    PaymentTypeInterface::BANK_PAYMENT_TYPE         => [
     45                        'title'       => __( 'Bank transfer (no payment required)', 'bookiflex' ),
     46                        'description' => __( 'Booking is confirmed immediately. Bank details are shared with the guest.', 'bookiflex' ),
     47                    ],
     48                    PaymentTypeInterface::BANK_PAYMENT_PAYABLE_TYPE => [
     49                        'title'       => __( 'Bank transfer (payment required)', 'bookiflex' ),
     50                        'description' => __( 'Booking is held until the guest completes the bank transfer within the allowed time.', 'bookiflex' ),
     51                    ],
     52                    PaymentTypeInterface::INTERNET_PAYMENT_TYPE     => [
     53                        'title'       => __( 'Online payment', 'bookiflex' ),
     54                        'description' => __( 'Booking is held until the guest pays online via the selected payment gateway.', 'bookiflex' ),
     55                    ],
     56                ],
     57                PaymentTypeInterface::PAYMENT_ON_ARRIVAL_TYPE
     58            ),
    3359            // Payment Gateway Settings
    3460            Field::make( 'separator', 'bflex_payment_gateway_separator', __( 'Payment Gateway Settings', 'bookiflex' ) )->set_conditional_logic( [[
     
    6692        ] );
    6793        $paymentSettingsFields = array_merge( $paymentSettingsFields, $this->addBasePaymentSettingsFields() );
    68         Container::make( 'post_meta', __( 'Configuration', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->add_tab( __( 'Payment Method Settings', 'bookiflex' ), $paymentSettingsFields );
     94        Container::make( 'post_meta', __( 'Configuration', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->add_fields( $paymentSettingsFields );
    6995    }
    7096
  • bookiflex/tags/1.2.0/src/Admin/CPT/RatePlan.php

    r3478607 r3495560  
    1818use BookiFlex\Core\Admin\CPT\PaymentType;
    1919use BookiFlex\Core\Admin\CPT\RatePlan as BaseCPT;
     20use BookiFlex\Core\Admin\Field\SectionNotice;
    2021use BookiFlex\Core\Contract\PluginInterface;
    2122use BookiFlex\Core\Model\AccommodationType\AccommodationPlacementTypeInterface;
     
    2324use BookiFlex\Core\Model\RatePlan\BoardTypeInterface;
    2425use BookiFlex\Core\Util\ProFeature;
     26use BookiFlex\Core\Admin\Field\RadioCard;
    2527use BookiFlex\Extensions\Onboarding\Contract\OnboardingPresetsProviderInterface;
    2628use Carbon_Fields\Container;
     
    3537    public function register( PluginInterface $plugin ) : void {
    3638        parent::register( $plugin );
     39        $this->customizeMenuOrder( __( 'Display order', 'bookiflex' ), __( 'Controls the display order of this rate plan. Lower numbers appear first.', 'bookiflex' ) );
     40        if ( $this->supportInheritance() ) {
     41            $this->customizeParentSelector( __( 'Parent rate plan', 'bookiflex' ), __( 'Select a parent rate plan to inherit prices and restrictions from.', 'bookiflex' ) );
     42        }
     43        $this->customizeExcerpt( __( 'Description', 'bookiflex' ), __( 'A brief description of this rate plan. This text may be shown to guests during the booking process.', 'bookiflex' ) );
    3744        $this->plugin->addAction(
    3845            'carbon_fields_post_meta_container_saved',
     
    4956
    5057    public function registerMetaBoxes() : void {
    51         $generalFields = [
    52             Field::make( 'text', self::FIELD_SHORT_CODE, __( 'Short code', 'bookiflex' ) )->set_width( 50 )->set_attribute( 'maxLength', '10' )->set_attribute( 'readOnly', 'true' )->help_text( __( 'A short identifier for this rate plan (generated automatically). For example: BRP for Base rate plan, SAM for Summer vacation rate plan. This code may be used internally or in reports.', 'bookiflex' ) ),
    53             Field::make( 'hidden', self::FIELD_BOARD_TYPE, '' )->set_default_value( BoardTypeInterface::ROOM_ONLY ),
    54             Field::make( 'separator', 'bflex_rental_units_separator', __( 'Rental units', 'bookiflex' ) ),
    55             Field::make( 'html', 'rental-units-html' )->set_html( sprintf( '<p>%s</p>', __( 'Select the rental units this rate plan applies to. You\'ll be able to define prices and restrictions for each one separately.', 'bookiflex' ) ) ),
    56             Field::make( 'association', self::FIELD_ACCOMMODATION_TYPES, '' )->set_required()->set_types( [[
    57                 'type'      => 'post',
    58                 'post_type' => AccommodationType::POST_TYPE,
    59             ]] )
    60         ];
    61         Container::make( 'post_meta', 'rate-plan-fields', __( 'Configuration', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->add_tab( __( 'General', 'bookiflex' ), $generalFields )->add_tab( __( 'Booking terms', 'bookiflex' ), $this->buildBookingTermsFields() );
     58        $generalFields = [Field::make( 'hidden', self::FIELD_BOARD_TYPE, '' )->set_default_value( BoardTypeInterface::ROOM_ONLY ), SectionNotice::make( 'bflex__rp__accommodation_types-html', sprintf( '<strong>%s</strong> %s', __( 'Select the rental units this rate plan applies to.', 'bookiflex' ), __( 'You\'ll be able to define prices and restrictions for each one separately.', 'bookiflex' ) ) ), Field::make( 'association', self::FIELD_ACCOMMODATION_TYPES, '' )->set_required()->set_types( [[
     59            'type'      => 'post',
     60            'post_type' => AccommodationType::POST_TYPE,
     61        ]] )];
     62        Container::make( 'post_meta', 'rate-plan-short-code', __( 'Short code', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->set_context( 'side' )->add_fields( [Field::make( 'text', self::FIELD_SHORT_CODE, '' )->set_attribute( 'maxLength', '10' )->set_attribute( 'readOnly', 'true' )->help_text( __( 'A short identifier for this rate plan (generated automatically). For example: BRP for Base rate plan, SAM for Summer vacation rate plan. This code may be used internally or in reports.', 'bookiflex' ) )] );
     63        Container::make( 'post_meta', 'rate-plan-fields', __( 'Configuration', 'bookiflex' ) )->set_layout( 'tabbed-vertical' )->where( 'post_type', '=', self::POST_TYPE )->add_tab( __( 'General', 'bookiflex' ), $generalFields )->add_tab( __( 'Booking terms', 'bookiflex' ), $this->buildBookingTermsFields() );
    6264        $this->showNotice();
    6365    }
     
    7476    private function buildBookingTermsFields() : array {
    7577        $fields = [];
    76         $fields[] = Field::make( 'html', 'booking-terms-html' )->set_html( sprintf( '<p>%s</p>', __( 'Choose how guests pay and what cancellation rules apply to this rate plan.', 'bookiflex' ) ) );
    77         $fields[] = Field::make( 'radio', self::FIELD_BOOKING_TERMS, '' )->set_options( function () {
    78             return $this->getAvailableBookingTermsOptions();
    79         } )->set_default_value( $this->getDefaultBookingTermsValue() );
    80         // Add conditional description blocks for each preset
     78        $fields[] = SectionNotice::make( 'booking-terms-html', __( 'Choose how guests pay and what cancellation rules apply to this rate plan.', 'bookiflex' ) );
     79        // Build RadioCard options from presets
     80        $options = [];
    8181        $presets = $this->presetsProvider->getBookingPresets();
    8282        foreach ( $presets as $preset ) {
    83             $html = sprintf( '<div style="padding: 12px 16px; background: #f0f6fc; border-left: 4px solid #0073aa; margin: 8px 0;">
    84                     <p style="margin: 0 0 6px;">%s</p>
    85                     <p style="margin: 0; color: #555; font-style: italic;">%s</p>
    86                 </div>', esc_html( $preset['shortDescription'] ), esc_html( $preset['recommendation'] ) );
    87             $fields[] = Field::make( 'html', 'preset_' . $preset['id'] . '_desc' )->set_html( $html )->set_conditional_logic( [[
    88                 'field' => self::FIELD_BOOKING_TERMS,
    89                 'value' => $preset['id'],
    90             ]] );
    91         }
    92         // Custom description
    93         $fields[] = Field::make( 'html', 'custom_booking_terms_desc' )->set_html( sprintf( '<div style="padding: 12px 16px; background: #fff8e5; border-left: 4px solid #dba617; margin: 8px 0;">
    94                     <p style="margin: 0;">%s</p>
    95                 </div>', esc_html__( 'Manually select the payment method(s) and cancellation policy for this rate plan.', 'bookiflex' ) ) )->set_conditional_logic( [[
     83            $paymentTypeId = $this->findCptByPresetKey( PaymentType::POST_TYPE, $preset['paymentTypeKey'] );
     84            $cancellationPolicyId = $this->findCptByPresetKey( CancellationPolicy::POST_TYPE, $preset['cancellationPolicyKey'] );
     85            if ( $paymentTypeId && $cancellationPolicyId ) {
     86                $title = $preset['title'];
     87                if ( !empty( $preset['popular'] ) ) {
     88                    $title .= ' ' . __( '(recommended)', 'bookiflex' );
     89                }
     90                $description = $preset['shortDescription'];
     91                if ( !empty( $preset['recommendation'] ) ) {
     92                    $description .= ' ' . $preset['recommendation'];
     93                }
     94                $options[$preset['id']] = [
     95                    'title'       => $title,
     96                    'description' => $description,
     97                ];
     98            }
     99        }
     100        $options['custom'] = [
     101            'title'       => __( 'Custom', 'bookiflex' ),
     102            'description' => __( 'Manually select the payment method(s) and cancellation policy for this rate plan.', 'bookiflex' ),
     103        ];
     104        $default = $this->getDefaultBookingTermsValue();
     105        array_push( $fields, ...RadioCard::make(
     106            self::FIELD_BOOKING_TERMS,
     107            __( 'Booking terms', 'bookiflex' ),
     108            $options,
     109            $default,
     110            true
     111        ) );
     112        // Association fields — visible only for Custom
     113        $fields[] = Field::make( 'separator', 'bflex__cp__strict_separator', __( 'Configuration', 'bookiflex' ) )->set_conditional_logic( [[
    96114            'field' => self::FIELD_BOOKING_TERMS,
    97115            'value' => 'custom',
    98116        ]] );
    99         // Association fields — visible only for Custom
    100117        $fields[] = Field::make( 'association', self::FIELD_PAYMENT_TYPES, __( 'Payment types', 'bookiflex' ) )->set_types( [[
    101118            'type'      => 'post',
     
    116133
    117134    /**
    118      * Get available booking terms options for radio field.
    119      *
    120      * Only includes presets for which both PaymentType and CancellationPolicy CPTs
    121      * with matching `_bflex_preset_key` exist in the database.
    122      *
    123      * @return array<string, string>
    124      */
    125     private function getAvailableBookingTermsOptions() : array {
     135     * Get default value for booking terms radio.
     136     *
     137     * Returns the first available preset id, or 'custom' if none available.
     138     *
     139     * @return string
     140     */
     141    private function getDefaultBookingTermsValue() : string {
    126142        $presets = $this->presetsProvider->getBookingPresets();
    127         $options = [];
    128143        foreach ( $presets as $preset ) {
    129144            $paymentTypeId = $this->findCptByPresetKey( PaymentType::POST_TYPE, $preset['paymentTypeKey'] );
    130145            $cancellationPolicyId = $this->findCptByPresetKey( CancellationPolicy::POST_TYPE, $preset['cancellationPolicyKey'] );
    131146            if ( $paymentTypeId && $cancellationPolicyId ) {
    132                 $label = $preset['title'];
    133                 if ( !empty( $preset['popular'] ) ) {
    134                     $label .= ' ' . __( '(recommended)', 'bookiflex' );
    135                 }
    136                 $options[$preset['id']] = $label;
    137             }
    138         }
    139         $options['custom'] = __( 'Custom', 'bookiflex' );
    140         return $options;
    141     }
    142 
    143     /**
    144      * Get default value for booking terms radio.
    145      *
    146      * Returns the first available preset id, or 'custom' if none available.
    147      *
    148      * @return string
    149      */
    150     private function getDefaultBookingTermsValue() : string {
    151         $options = $this->getAvailableBookingTermsOptions();
    152         $keys = array_keys( $options );
    153         // Return first non-custom option, or 'custom'
    154         foreach ( $keys as $key ) {
    155             if ( $key !== 'custom' ) {
    156                 return $key;
     147                return $preset['id'];
    157148            }
    158149        }
  • bookiflex/tags/1.2.0/src/Core/Admin/CPT/AbstractCPT.php

    r3478607 r3495560  
    4343            $query->set('order', 'ASC');
    4444        }
     45    }
     46
     47    protected function customizeMenuOrder(string $title, string $description): void
     48    {
     49        $this->plugin->addAction('add_meta_boxes', function () use ($title, $description) {
     50            add_meta_box(
     51                'bflex_' . static::POST_TYPE . '_menu_order',
     52                $title,
     53                function ($post) use ($description) {
     54                    echo '<input name="menu_order" type="number" min="0" id="menu_order" value="' . esc_attr($post->menu_order) . '" style="width:100%" />';
     55                    echo '<p class="description">' . esc_html($description) . '</p>';
     56                },
     57                static::POST_TYPE,
     58                'side'
     59            );
     60        });
     61    }
     62
     63    protected function customizeParentSelector(string $title, string $description): void
     64    {
     65        $this->plugin->addAction('add_meta_boxes', function () use ($title, $description) {
     66            add_meta_box(
     67                'bflex_' . static::POST_TYPE . '_parent',
     68                $title,
     69                function ($post) use ($description) {
     70                    $dropdown = wp_dropdown_pages([
     71                        'post_type'        => $post->post_type,
     72                        'exclude_tree'     => $post->ID,
     73                        'selected'         => $post->post_parent,
     74                        'name'             => 'parent_id',
     75                        'show_option_none' => __('(no parent)', 'bookiflex'),
     76                        'sort_column'      => 'menu_order, post_title',
     77                        'echo'             => 0,
     78                    ]);
     79                    if ($dropdown) {
     80                        echo $dropdown;
     81                    }
     82                    echo '<p class="description">' . esc_html($description) . '</p>';
     83                },
     84                static::POST_TYPE,
     85                'side'
     86            );
     87        });
     88    }
     89
     90    protected function customizeExcerpt(string $title, string $description): void
     91    {
     92        $this->plugin->addAction('add_meta_boxes', function () use ($title, $description) {
     93            add_meta_box(
     94                'bflex_' . static::POST_TYPE . '_excerpt',
     95                $title,
     96                function ($post) use ($title, $description) {
     97                    ?>
     98                    <label class="screen-reader-text" for="excerpt"><?php echo esc_html($title); ?></label>
     99                    <textarea rows="1" cols="40" name="excerpt" id="excerpt"><?php echo esc_html($post->post_excerpt); ?></textarea>
     100                    <p><?php echo esc_html($description); ?></p>
     101                    <?php
     102                },
     103                static::POST_TYPE,
     104                'normal'
     105            );
     106        });
    45107    }
    46108
  • bookiflex/tags/1.2.0/src/Core/Admin/CPT/AccommodationType.php

    r3478607 r3495560  
    5656        // Register event hooks from trait
    5757        $this->registerEventHooks();
     58
     59        // Thumbnail column in list table
     60        $this->plugin->addFilter('manage_' . self::POST_TYPE . '_posts_columns', [$this, 'addThumbnailColumn']);
     61        $this->plugin->addAction('manage_' . self::POST_TYPE . '_posts_custom_column', [$this, 'renderThumbnailColumn'], 10, 2);
     62    }
     63
     64    /**
     65     * Insert "Image" column before "title"
     66     */
     67    public function addThumbnailColumn(array $columns): array
     68    {
     69        $result = [];
     70
     71        foreach ($columns as $key => $label) {
     72            if ($key === 'title') {
     73                $result['bflex_thumbnail'] = __('Image', 'bookiflex');
     74            }
     75            $result[$key] = $label;
     76        }
     77
     78        return $result;
     79    }
     80
     81    /**
     82     * Render thumbnail or placeholder in list column
     83     */
     84    public function renderThumbnailColumn(string $column, int $postId): void
     85    {
     86        if ($column !== 'bflex_thumbnail') {
     87            return;
     88        }
     89
     90        if (has_post_thumbnail($postId)) {
     91            echo get_the_post_thumbnail($postId, [80, 80]);
     92        } else {
     93            echo '<span class="bflex-thumbnail-placeholder dashicons dashicons-format-image"></span>';
     94        }
    5895    }
    5996
     
    99136            'hierarchical'       => false,
    100137            'menu_position'      => 0,
    101             'supports'           => ['title', 'excerpt', 'thumbnail', 'page-attributes'],
     138            'supports'           => ['title', 'thumbnail'],
    102139            'capability_type'    => self::POST_TYPE,
    103140            'capabilities'       => [
  • bookiflex/tags/1.2.0/src/Core/Admin/CPT/CancellationPolicy.php

    r3478607 r3495560  
    1616namespace BookiFlex\Core\Admin\CPT;
    1717
     18use BookiFlex\Core\Contract\PluginInterface;
    1819use BookiFlex\Core\Contract\RoleCapabilitiesInterface;
    1920
     
    4344    public const FIELD_STRICT_TIME_WINDOW = 'bflex__cp__strict_time_window';
    4445    public const FIELD_STRICT_PENALTY_PERCENT = 'bflex__cp__strict_penalty_percent';
     46
     47    public function register(PluginInterface $plugin): void
     48    {
     49        parent::register($plugin);
     50
     51        $this->customizeExcerpt(
     52            __('Description', 'bookiflex'),
     53            __('An optional note for your own reference. This text is not shown to guests.', 'bookiflex')
     54        );
     55    }
    4556
    4657    public function registerPostType(): void
     
    8091            'hierarchical'       => false,
    8192            'menu_position'      => 2,
    82             'supports'           => ['title', 'excerpt'],
     93            'supports'           => ['title'],
    8394            'capability_type'    => self::POST_TYPE,
    8495            'capabilities'       => [
  • bookiflex/tags/1.2.0/src/Core/Admin/CPT/PaymentType.php

    r3478607 r3495560  
    7878            'hierarchical'       => false,
    7979            'menu_position'      => 3,
    80             'supports'           => ['title', 'excerpt'],
     80            'supports'           => ['title'],
    8181            'capability_type'    => self::POST_TYPE,
    8282            'capabilities'       => [
  • bookiflex/tags/1.2.0/src/Core/Admin/CPT/RatePlan.php

    r3478607 r3495560  
    8585            'hierarchical'       => $this->supportInheritance(),
    8686            'menu_position'      => 1,
    87             'supports'           => ['title', 'excerpt', 'page-attributes'],
     87            'supports'           => ['title'],
    8888            'capability_type'    => self::POST_TYPE,
    8989            'capabilities'       => [
  • bookiflex/tags/1.2.0/vendor/composer/autoload_classmap.php

    r3478614 r3495560  
    2424    'BookiFlex\\Core\\Admin\\CTL\\CustomerTableList' => $baseDir . '/src/Core/Admin/CTL/CustomerTableList.php',
    2525    'BookiFlex\\Core\\Admin\\CTL\\ReservationTableList' => $baseDir . '/src/Core/Admin/CTL/ReservationTableList.php',
     26    'BookiFlex\\Core\\Admin\\Field\\RadioCard' => $baseDir . '/src/Core/Admin/Field/RadioCard.php',
     27    'BookiFlex\\Core\\Admin\\Field\\SectionNotice' => $baseDir . '/src/Core/Admin/Field/SectionNotice.php',
    2628    'BookiFlex\\Core\\Admin\\Lifecycle\\AbstractExtensionActivator' => $baseDir . '/src/Core/Admin/Lifecycle/AbstractExtensionActivator.php',
    2729    'BookiFlex\\Core\\Admin\\Lifecycle\\AbstractUninstaller' => $baseDir . '/src/Core/Admin/Lifecycle/AbstractUninstaller.php',
  • bookiflex/tags/1.2.0/vendor/composer/autoload_static.php

    r3483810 r3495560  
    456456        'BookiFlex\\Core\\Admin\\CTL\\CustomerTableList' => __DIR__ . '/../..' . '/src/Core/Admin/CTL/CustomerTableList.php',
    457457        'BookiFlex\\Core\\Admin\\CTL\\ReservationTableList' => __DIR__ . '/../..' . '/src/Core/Admin/CTL/ReservationTableList.php',
     458        'BookiFlex\\Core\\Admin\\Field\\RadioCard' => __DIR__ . '/../..' . '/src/Core/Admin/Field/RadioCard.php',
     459        'BookiFlex\\Core\\Admin\\Field\\SectionNotice' => __DIR__ . '/../..' . '/src/Core/Admin/Field/SectionNotice.php',
    458460        'BookiFlex\\Core\\Admin\\Lifecycle\\AbstractExtensionActivator' => __DIR__ . '/../..' . '/src/Core/Admin/Lifecycle/AbstractExtensionActivator.php',
    459461        'BookiFlex\\Core\\Admin\\Lifecycle\\AbstractUninstaller' => __DIR__ . '/../..' . '/src/Core/Admin/Lifecycle/AbstractUninstaller.php',
  • bookiflex/tags/1.2.0/vendor/composer/installed.php

    r3493377 r3495560  
    22    'root' => array(
    33        'name' => 'bookiflex/plugin',
    4         'pretty_version' => 'v1.1.1',
    5         'version' => '1.1.1.0',
    6         'reference' => '198f1b412b1060aca0e245573237bc45105d1d82',
     4        'pretty_version' => 'v1.2.0',
     5        'version' => '1.2.0.0',
     6        'reference' => 'b01ef3c27da8b2bf4bde963764fcc52c71eeb6db',
    77        'type' => 'wordpress-plugin',
    88        'install_path' => __DIR__ . '/../../',
     
    2121        ),
    2222        'bookiflex/plugin' => array(
    23             'pretty_version' => 'v1.1.1',
    24             'version' => '1.1.1.0',
    25             'reference' => '198f1b412b1060aca0e245573237bc45105d1d82',
     23            'pretty_version' => 'v1.2.0',
     24            'version' => '1.2.0.0',
     25            'reference' => 'b01ef3c27da8b2bf4bde963764fcc52c71eeb6db',
    2626            'type' => 'wordpress-plugin',
    2727            'install_path' => __DIR__ . '/../../',
  • bookiflex/trunk/assets/manifest.json

    r3493377 r3495560  
    11{
    2   "version": "1.1.1",
    3   "generated": "2026-03-28T14:15:22.310Z",
     2  "version": "1.2.0",
     3  "generated": "2026-03-31T12:43:23.451Z",
    44  "external": {
    55    "vue": "assets/vendor/vue/vue.global.prod.js",
     
    1515    },
    1616    "admin": {
    17       "js": "assets/free/admin/index.B_cl0VpC.js",
    18       "css": "assets/free/admin/style.B9Y7Sqrs.css"
     17      "js": "assets/free/admin/index.D9SCTotr.js",
     18      "css": "assets/free/admin/style.CEmLATbJ.css"
    1919    },
    2020    "widgets": {
    2121      "controller": {
    22         "js": "assets/common/widgets/controller/index.B2RbsrGV.js"
     22        "js": "assets/common/widgets/controller/index.B3SGvgXg.js"
    2323      },
    2424      "booking": {
  • bookiflex/trunk/bookiflex.php

    r3493377 r3495560  
    66 * Description: Direct Booking Widget for Apartments and Short-Term Rentals
    77 * Plugin URI: https://bookiflex.com
    8  * Version: 1.1.1
     8 * Version: 1.2.0
    99 * Author: BookiFlex
    1010 * License: GPL-2.0-or-later
     
    8080}
    8181// Define plugin constants
    82 define( 'BOOKIFLEX_VERSION', '1.1.1' );
     82define( 'BOOKIFLEX_VERSION', '1.2.0' );
    8383define( 'BOOKIFLEX_FILE', __FILE__ );
    8484define( 'BOOKIFLEX_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
  • bookiflex/trunk/readme.txt

    r3493377 r3495560  
    55Tested up to: 6.9
    66Requires PHP: 8.1
    7 Stable tag: 1.1.1
     7Stable tag: 1.2.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    149149== Changelog ==
    150150
    151 = 1.1.1 =
    152 * Fix a problem with Notifications when VAT is enabled
    153 
    154 = 1.1.0 =
    155 * Improve interface behavior (search widget)
    156 * Fix minor bugs
    157 
    158 = 1.0.2 =
    159 * Fix onboarding flow
    160 
    161 = 1.0.0 =
    162 * Initial release
    163 * Floating booking button and modal widget
    164 * Room setup, pricing, availability, and email notifications
     151= 1.2.0 - 31 March 2026 =
     152* Improved: Redesigned admin panel for better usability and cleaner interface
     153* Improved: Added class attributes to modal trigger elements for easier customization
     154* Fixed: Offers now load correctly when clicking custom trigger elements
     155* Fixed: Admin nonce now refreshes automatically to prevent expired session errors
     156
     157[See changelog for all versions](https://plugins.svn.wordpress.org/bookiflex/trunk/CHANGELOG.txt).
    165158
    166159
  • bookiflex/trunk/src/Admin/AssetsManager.php

    r3487384 r3495560  
    4646        // Vue — single path, dev manifest overrides prod vue path
    4747        $this->enqueueScript( 'bookiflex-vue', $this->assetUrl( $this->asset( 'external', 'vue' ) ), ['wp-i18n'] );
     48        // Carbon fields style
     49        $this->enqueueStyle( 'carbon-fields-style', $this->assetUrl( 'assets/vendor/carbon_fields/style.css' ), ['carbon-fields-metaboxes'] );
     50        // Radio card field
     51        $this->enqueueScript(
     52            'bookiflex-radio-card',
     53            $this->assetUrl( 'assets/vendor/carbon_fields/radio-card.js' ),
     54            ['carbon-fields-vendor', 'carbon-fields-core', 'carbon-fields-metaboxes'],
     55            true
     56        );
    4857        $this->enqueueAdminApp();
    4958        wp_set_script_translations( 'bookiflex-admin-app', 'bookiflex', $this->plugin->getPluginDir() . 'languages' );
  • bookiflex/trunk/src/Admin/CPT/AccommodationType.php

    r3478607 r3495560  
    1515
    1616use BookiFlex\Core\Admin\CPT\AccommodationType as BaseCPT;
     17use BookiFlex\Core\Contract\PluginInterface;
    1718use BookiFlex\Core\Model\AccommodationType\BookingUnitTypeInterface;
    1819use BookiFlex\Core\Util\ProFeature;
     
    2122defined( 'ABSPATH' ) || exit;
    2223class AccommodationType extends BaseCPT {
     24    public function register( PluginInterface $plugin ) : void {
     25        parent::register( $plugin );
     26        $this->customizeMenuOrder( __( 'Display order', 'bookiflex' ), __( 'Controls the display order of this rental unit on the booking widget. Lower numbers appear first.', 'bookiflex' ) );
     27        $this->customizeExcerpt( __( 'Description', 'bookiflex' ), __( 'A brief description of this unit. This text may be shown to guests during the booking process.', 'bookiflex' ) );
     28    }
     29
    2330    public function registerMetaBoxes() : void {
    2431        $overviewFields = [
    25             Field::make( 'text', self::FIELD_SHORT_CODE, __( 'Short code', 'bookiflex' ) )->set_width( 34 )->set_attribute( 'maxLength', '10' )->set_attribute( 'readOnly', 'true' )->set_attribute( 'placeholder', __( 'e.g. STD', 'bookiflex' ) )->help_text( __( 'A short identifier for this rental unit (generated automatically). For example: APT for Apartment, CAB for Cabin. This code may be used internally or in reports.', 'bookiflex' ) ),
     32            Field::make( 'text', self::FIELD_ADDRESS, __( 'Address', 'bookiflex' ) )->set_required()->set_help_text( __( 'The full address of the property.', 'bookiflex' ) ),
    2633            Field::make( 'text', self::FIELD_LIVING_SPACE, __( 'Living space', 'bookiflex' ) )->set_width( 33 )->set_attribute( 'placeholder', __( 'e.g. 30 m², 250 ft², 45', 'bookiflex' ) )->help_text( __( 'Specify the total living area of the rental unit (in square meters, square feet, or any unit you prefer). This information is often displayed on the unit page.', 'bookiflex' ) ),
    2734            Field::make( 'text', self::FIELD_ROOMS_COUNT, __( 'Number of rooms', 'bookiflex' ) )->set_width( 33 )->set_attribute( 'type', 'number' )->set_attribute( 'min', '1' )->set_attribute( 'placeholder', __( 'e.g. 3', 'bookiflex' ) )->help_text( __( 'Total number of separate rooms in this rental unit (bedrooms, living room, kitchen, etc.).', 'bookiflex' ) ),
     
    3239        $guestsBedFields = [Field::make( 'separator', 'bflex_main_placement_separator', __( 'Main placements', 'bookiflex' ) ), Field::make( 'select', self::FIELD_MAIN_SLEEPING_CAPACITY, __( 'Main sleeping capacity', 'bookiflex' ) )->set_width( 50 )->set_options( array_combine( range( 1, 10 ), range( 1, 10 ) ) )->set_default_value( 2 )->help_text( __( 'Enter the total number of main sleeping places (e.g., 1 double bed = 2 places).', 'bookiflex' ) ), Field::make( 'set', self::FIELD_MAIN_SLEEPING_ALLOWED_OCCUPANCY, __( 'Allowed occupancy', 'bookiflex' ) )->set_width( 50 )->set_options( array_combine( range( 1, 10 ), range( 1, 10 ) ) )->help_text( __( 'Select how many guests can stay in this accommodation. Each selected option can have its own price. <strong>Only selected occupancy options will be available for booking and pricing.</strong>', 'bookiflex' ) )];
    3340        $guestsBedFields = array_merge( $guestsBedFields, $this->addNotice() );
    34         Container::make( 'post_meta', 'at_configuration', __( 'Configuration', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->add_tab( __( 'Overview', 'bookiflex' ), $overviewFields )->add_tab( __( 'Guests & beds', 'bookiflex' ), $guestsBedFields )->add_tab( __( 'Contact information', 'bookiflex' ), [
    35             Field::make( 'separator', 'bflex_unit_address_separator', __( 'Rental unit address', 'bookiflex' ) ),
    36             Field::make( 'text', self::FIELD_ADDRESS, __( 'Address', 'bookiflex' ) )->set_required()->set_help_text( __( 'The full address of the property.', 'bookiflex' ) ),
    37             Field::make( 'text', self::FIELD_LONGITUDE, __( 'Longitude', 'bookiflex' ) )->set_width( 50 )->set_help_text( __( 'Used to display the property on a map.', 'bookiflex' ) ),
    38             Field::make( 'text', self::FIELD_LATITUDE, __( 'Latitude', 'bookiflex' ) )->set_width( 50 )->set_help_text( __( 'Used to display the property on a map.', 'bookiflex' ) ),
    39             Field::make( 'separator', 'bflex_unit_email_separator', __( 'Email information', 'bookiflex' ) ),
     41        Container::make( 'post_meta', 'at_short_code', __( 'Short code', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->set_context( 'side' )->add_fields( [Field::make( 'text', self::FIELD_SHORT_CODE, '' )->set_attribute( 'maxLength', '10' )->set_attribute( 'readOnly', 'true' )->set_attribute( 'placeholder', __( 'e.g. STD', 'bookiflex' ) )->help_text( __( 'A short identifier for this rental unit (generated automatically). For example: APT for Apartment, CAB for Cabin. This code may be used internally or in reports.', 'bookiflex' ) )] );
     42        Container::make( 'post_meta', 'at_configuration', __( 'Configuration', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->set_layout( 'tabbed-vertical' )->add_tab( __( 'Overview', 'bookiflex' ), $overviewFields )->add_tab( __( 'Guests & beds', 'bookiflex' ), $guestsBedFields )->add_tab( __( 'Email information', 'bookiflex' ), [
    4043            Field::make( 'text', self::FIELD_PHONE, __( 'Phone Number', 'bookiflex' ) )->set_width( 50 )->set_attribute( 'type', 'tel' )->set_help_text( __( 'For guest support and communication (if not specified, the default one will be used).', 'bookiflex' ) ),
    4144            Field::make( 'text', self::FIELD_EMAIL, __( 'Email', 'bookiflex' ) )->set_width( 50 )->set_attribute( 'type', 'email' )->set_help_text( __( 'For guest support and communication (if not specified, the default one will be used).', 'bookiflex' ) ),
    42             Field::make( 'textarea', self::FIELD_CHECKIN_INSTRUCTIONS, __( 'Check-in instructions', 'bookiflex' ) )->set_rows( 4 )->help_text( __( 'Important information sent to guests after booking confirmation. Include details like door codes, parking instructions, key pickup location, or check-in procedures specific to this rental unit.', 'bookiflex' ) )
     45            Field::make( 'textarea', self::FIELD_CHECKIN_INSTRUCTIONS, __( 'Check-in instructions', 'bookiflex' ) )->set_rows( 4 )->help_text( __( 'Important information sent to guests after booking confirmation. Include details like door codes, parking instructions, key pickup location, or check-in procedures specific to this rental unit.', 'bookiflex' ) ),
     46            Field::make( 'separator', 'bflex_unit_address_separator', __( 'Map coordinates', 'bookiflex' ) ),
     47            Field::make( 'text', self::FIELD_LONGITUDE, __( 'Longitude', 'bookiflex' ) )->set_width( 50 )->set_help_text( __( 'Used to display the property on a map.', 'bookiflex' ) ),
     48            Field::make( 'text', self::FIELD_LATITUDE, __( 'Latitude', 'bookiflex' ) )->set_width( 50 )->set_help_text( __( 'Used to display the property on a map.', 'bookiflex' ) )
    4349        ] );
    4450        Container::make( 'post_meta', __( 'Gallery', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->add_fields( [Field::make( 'media_gallery', self::FIELD_ACCOMMODATION_GALLERY, __( 'Gallery', 'bookiflex' ) )] );
  • bookiflex/trunk/src/Admin/CPT/CancellationPolicy.php

    r3478607 r3495560  
    1717use BookiFlex\Core\Contract\CancellationPolicy\CancellationPolicyTypeInterface;
    1818use BookiFlex\Core\Util\ProFeature;
     19use BookiFlex\Core\Admin\Field\RadioCard;
     20use BookiFlex\Core\Admin\Field\SectionNotice;
    1921use Carbon_Fields\Container;
    2022use Carbon_Fields\Field;
     
    2224class CancellationPolicy extends BaseCPT {
    2325    public function registerMetaBoxes() : void {
    24         // Build policy options
     26        // Build policy options with descriptions for radio cards
    2527        $policyOptions = [
    26             self::RULE_FULLY_FLEXIBLE => __( 'Fully flexible', 'bookiflex' ),
    27             self::RULE_STRICT         => __( 'Strict', 'bookiflex' ),
    28             self::RULE_FIRST_NIGHT    => __( 'First night penalty', 'bookiflex' ),
    29             self::RULE_NON_REFUNDABLE => __( 'Non-refundable', 'bookiflex' ),
     28            self::RULE_FULLY_FLEXIBLE => [
     29                'title'       => __( 'Fully flexible', 'bookiflex' ),
     30                'description' => __( 'Free cancellation at any time, no penalty will be charged.', 'bookiflex' ),
     31            ],
     32            self::RULE_STRICT         => [
     33                'title'       => __( 'Strict', 'bookiflex' ),
     34                'description' => __( 'Configurable cancellation with custom time window and penalty percentage.', 'bookiflex' ),
     35            ],
     36            self::RULE_FIRST_NIGHT    => [
     37                'title'       => __( 'First night penalty', 'bookiflex' ),
     38                'description' => __( 'Cancellation at any time will incur a penalty equal to the first night\'s stay.', 'bookiflex' ),
     39            ],
     40            self::RULE_NON_REFUNDABLE => [
     41                'title'       => __( 'Non-refundable', 'bookiflex' ),
     42                'description' => __( 'Cancellation at any time will incur a 100% charge of the booking amount.', 'bookiflex' ),
     43            ],
    3044        ];
    3145        $fields = [
    32             Field::make( 'html', 'cancellation_policy_html' )->set_html( sprintf( '<p>%s</p>', __( 'A cancellation policy defines how and when cancellation penalties apply. Later, this policy can be assigned to a rate plan.', 'bookiflex' ) ) ),
    33             Field::make( 'radio', self::FIELD_PREDEFINED_RULE, __( 'Rule', 'bookiflex' ) )->set_width( 50 )->set_help_text( __( 'Chose one of predefined policies.', 'bookiflex' ) )->set_options( $policyOptions ),
    34             Field::make( 'html', self::FIELD_PREDEFINED_RULE . '_html' )->set_width( 50 )->set_html( sprintf( '<p><strong>%s</strong> - %s</p>', __( 'Fully flexible', 'bookiflex' ), __( 'Free cancellation at any time, no penalty will be charged.', 'bookiflex' ) ) . sprintf( '<p><strong>%s</strong> - %s</p>', __( 'Strict', 'bookiflex' ), __( 'Configurable cancellation with custom time window and penalty percentage.', 'bookiflex' ) ) . sprintf( '<p><strong>%s</strong> - %s</p>', __( 'First night penalty', 'bookiflex' ), __( 'Cancellation at any time will incur a penalty equal to the first night\'s stay.', 'bookiflex' ) ) . sprintf( '<p><strong>%s</strong> - %s</p>', __( 'Non-refundable', 'bookiflex' ), __( 'Cancellation at any time will incur a 100% charge of the booking amount.', 'bookiflex' ) ) ),
     46            SectionNotice::make( 'cancellation_policy_html', __( 'A cancellation policy defines how and when cancellation penalties apply. Later, this policy can be assigned to a rate plan.', 'bookiflex' ) ),
     47            ...RadioCard::make(
     48                self::FIELD_PREDEFINED_RULE,
     49                __( 'Rule', 'bookiflex' ),
     50                $policyOptions,
     51                self::RULE_FULLY_FLEXIBLE
     52            ),
     53            Field::make( 'separator', 'bflex__cp__strict_separator', __( 'Configuration', 'bookiflex' ) )->set_conditional_logic( [[
     54                'field' => self::FIELD_PREDEFINED_RULE,
     55                'value' => self::RULE_STRICT,
     56            ]] ),
    3557            Field::make( 'select', self::FIELD_STRICT_TIME_WINDOW, __( 'Cancellation window', 'bookiflex' ) )->set_conditional_logic( [[
    3658                'field' => self::FIELD_PREDEFINED_RULE,
  • bookiflex/trunk/src/Admin/CPT/PaymentType.php

    r3478607 r3495560  
    1515
    1616use BookiFlex\Core\Admin\CPT\PaymentType as BaseCPT;
     17use BookiFlex\Core\Admin\Field\RadioCard;
     18use BookiFlex\Core\Admin\Field\SectionNotice;
     19use BookiFlex\Core\Contract\PluginInterface;
    1720use BookiFlex\Core\Model\PaymentType\PaymentTypeInterface;
    1821use BookiFlex\Core\Util\ProFeature;
     
    2124defined( 'ABSPATH' ) || exit;
    2225class PaymentType extends BaseCPT {
     26    public function register( PluginInterface $plugin ) : void {
     27        parent::register( $plugin );
     28        $this->customizeExcerpt( __( 'Description', 'bookiflex' ), __( 'A brief description of this payment type. This text may be shown to guests during the booking process.', 'bookiflex' ) );
     29    }
     30
    2331    public function registerMetaBoxes() : void {
    2432        $paymentGateways = $this->getPaymentGateways();
    2533        // Build payment settings fields
    2634        $paymentSettingsFields = [
    27             Field::make( 'radio', self::FIELD_CALCULATOR_TYPE, __( 'How will the payment be collected?', 'bookiflex' ) )->set_width( 50 )->set_help_text( __( 'Choose when the booking gets confirmed. Options requiring payment will hold the booking until the guest pays within the allowed time — if not paid, the booking is canceled and availability is restored. Options without payment requirement confirm the booking immediately.', 'bookiflex' ) )->set_options( [
    28                 PaymentTypeInterface::PAYMENT_ON_ARRIVAL_TYPE   => __( 'Guest pays on arrival', 'bookiflex' ),
    29                 PaymentTypeInterface::BANK_PAYMENT_TYPE         => __( 'Bank transfer (confirmed without payment)', 'bookiflex' ),
    30                 PaymentTypeInterface::BANK_PAYMENT_PAYABLE_TYPE => __( 'Bank transfer (payment required to confirm)', 'bookiflex' ),
    31                 PaymentTypeInterface::INTERNET_PAYMENT_TYPE     => __( 'Guest pays online (via payment gateway)', 'bookiflex' ),
    32             ] ),
     35            SectionNotice::make( 'payment_type_notice', __( 'Choose when the booking gets confirmed. Options requiring payment will hold the booking until the guest pays within the allowed time — if not paid, the booking is canceled and availability is restored. Options without payment requirement confirm the booking immediately.', 'bookiflex' ) ),
     36            ...RadioCard::make(
     37                self::FIELD_CALCULATOR_TYPE,
     38                __( 'How will the payment be collected?', 'bookiflex' ),
     39                [
     40                    PaymentTypeInterface::PAYMENT_ON_ARRIVAL_TYPE   => [
     41                        'title'       => __( 'Guest pays on arrival', 'bookiflex' ),
     42                        'description' => __( 'Booking is confirmed immediately. No online payment required.', 'bookiflex' ),
     43                    ],
     44                    PaymentTypeInterface::BANK_PAYMENT_TYPE         => [
     45                        'title'       => __( 'Bank transfer (no payment required)', 'bookiflex' ),
     46                        'description' => __( 'Booking is confirmed immediately. Bank details are shared with the guest.', 'bookiflex' ),
     47                    ],
     48                    PaymentTypeInterface::BANK_PAYMENT_PAYABLE_TYPE => [
     49                        'title'       => __( 'Bank transfer (payment required)', 'bookiflex' ),
     50                        'description' => __( 'Booking is held until the guest completes the bank transfer within the allowed time.', 'bookiflex' ),
     51                    ],
     52                    PaymentTypeInterface::INTERNET_PAYMENT_TYPE     => [
     53                        'title'       => __( 'Online payment', 'bookiflex' ),
     54                        'description' => __( 'Booking is held until the guest pays online via the selected payment gateway.', 'bookiflex' ),
     55                    ],
     56                ],
     57                PaymentTypeInterface::PAYMENT_ON_ARRIVAL_TYPE
     58            ),
    3359            // Payment Gateway Settings
    3460            Field::make( 'separator', 'bflex_payment_gateway_separator', __( 'Payment Gateway Settings', 'bookiflex' ) )->set_conditional_logic( [[
     
    6692        ] );
    6793        $paymentSettingsFields = array_merge( $paymentSettingsFields, $this->addBasePaymentSettingsFields() );
    68         Container::make( 'post_meta', __( 'Configuration', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->add_tab( __( 'Payment Method Settings', 'bookiflex' ), $paymentSettingsFields );
     94        Container::make( 'post_meta', __( 'Configuration', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->add_fields( $paymentSettingsFields );
    6995    }
    7096
  • bookiflex/trunk/src/Admin/CPT/RatePlan.php

    r3478607 r3495560  
    1818use BookiFlex\Core\Admin\CPT\PaymentType;
    1919use BookiFlex\Core\Admin\CPT\RatePlan as BaseCPT;
     20use BookiFlex\Core\Admin\Field\SectionNotice;
    2021use BookiFlex\Core\Contract\PluginInterface;
    2122use BookiFlex\Core\Model\AccommodationType\AccommodationPlacementTypeInterface;
     
    2324use BookiFlex\Core\Model\RatePlan\BoardTypeInterface;
    2425use BookiFlex\Core\Util\ProFeature;
     26use BookiFlex\Core\Admin\Field\RadioCard;
    2527use BookiFlex\Extensions\Onboarding\Contract\OnboardingPresetsProviderInterface;
    2628use Carbon_Fields\Container;
     
    3537    public function register( PluginInterface $plugin ) : void {
    3638        parent::register( $plugin );
     39        $this->customizeMenuOrder( __( 'Display order', 'bookiflex' ), __( 'Controls the display order of this rate plan. Lower numbers appear first.', 'bookiflex' ) );
     40        if ( $this->supportInheritance() ) {
     41            $this->customizeParentSelector( __( 'Parent rate plan', 'bookiflex' ), __( 'Select a parent rate plan to inherit prices and restrictions from.', 'bookiflex' ) );
     42        }
     43        $this->customizeExcerpt( __( 'Description', 'bookiflex' ), __( 'A brief description of this rate plan. This text may be shown to guests during the booking process.', 'bookiflex' ) );
    3744        $this->plugin->addAction(
    3845            'carbon_fields_post_meta_container_saved',
     
    4956
    5057    public function registerMetaBoxes() : void {
    51         $generalFields = [
    52             Field::make( 'text', self::FIELD_SHORT_CODE, __( 'Short code', 'bookiflex' ) )->set_width( 50 )->set_attribute( 'maxLength', '10' )->set_attribute( 'readOnly', 'true' )->help_text( __( 'A short identifier for this rate plan (generated automatically). For example: BRP for Base rate plan, SAM for Summer vacation rate plan. This code may be used internally or in reports.', 'bookiflex' ) ),
    53             Field::make( 'hidden', self::FIELD_BOARD_TYPE, '' )->set_default_value( BoardTypeInterface::ROOM_ONLY ),
    54             Field::make( 'separator', 'bflex_rental_units_separator', __( 'Rental units', 'bookiflex' ) ),
    55             Field::make( 'html', 'rental-units-html' )->set_html( sprintf( '<p>%s</p>', __( 'Select the rental units this rate plan applies to. You\'ll be able to define prices and restrictions for each one separately.', 'bookiflex' ) ) ),
    56             Field::make( 'association', self::FIELD_ACCOMMODATION_TYPES, '' )->set_required()->set_types( [[
    57                 'type'      => 'post',
    58                 'post_type' => AccommodationType::POST_TYPE,
    59             ]] )
    60         ];
    61         Container::make( 'post_meta', 'rate-plan-fields', __( 'Configuration', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->add_tab( __( 'General', 'bookiflex' ), $generalFields )->add_tab( __( 'Booking terms', 'bookiflex' ), $this->buildBookingTermsFields() );
     58        $generalFields = [Field::make( 'hidden', self::FIELD_BOARD_TYPE, '' )->set_default_value( BoardTypeInterface::ROOM_ONLY ), SectionNotice::make( 'bflex__rp__accommodation_types-html', sprintf( '<strong>%s</strong> %s', __( 'Select the rental units this rate plan applies to.', 'bookiflex' ), __( 'You\'ll be able to define prices and restrictions for each one separately.', 'bookiflex' ) ) ), Field::make( 'association', self::FIELD_ACCOMMODATION_TYPES, '' )->set_required()->set_types( [[
     59            'type'      => 'post',
     60            'post_type' => AccommodationType::POST_TYPE,
     61        ]] )];
     62        Container::make( 'post_meta', 'rate-plan-short-code', __( 'Short code', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->set_context( 'side' )->add_fields( [Field::make( 'text', self::FIELD_SHORT_CODE, '' )->set_attribute( 'maxLength', '10' )->set_attribute( 'readOnly', 'true' )->help_text( __( 'A short identifier for this rate plan (generated automatically). For example: BRP for Base rate plan, SAM for Summer vacation rate plan. This code may be used internally or in reports.', 'bookiflex' ) )] );
     63        Container::make( 'post_meta', 'rate-plan-fields', __( 'Configuration', 'bookiflex' ) )->set_layout( 'tabbed-vertical' )->where( 'post_type', '=', self::POST_TYPE )->add_tab( __( 'General', 'bookiflex' ), $generalFields )->add_tab( __( 'Booking terms', 'bookiflex' ), $this->buildBookingTermsFields() );
    6264        $this->showNotice();
    6365    }
     
    7476    private function buildBookingTermsFields() : array {
    7577        $fields = [];
    76         $fields[] = Field::make( 'html', 'booking-terms-html' )->set_html( sprintf( '<p>%s</p>', __( 'Choose how guests pay and what cancellation rules apply to this rate plan.', 'bookiflex' ) ) );
    77         $fields[] = Field::make( 'radio', self::FIELD_BOOKING_TERMS, '' )->set_options( function () {
    78             return $this->getAvailableBookingTermsOptions();
    79         } )->set_default_value( $this->getDefaultBookingTermsValue() );
    80         // Add conditional description blocks for each preset
     78        $fields[] = SectionNotice::make( 'booking-terms-html', __( 'Choose how guests pay and what cancellation rules apply to this rate plan.', 'bookiflex' ) );
     79        // Build RadioCard options from presets
     80        $options = [];
    8181        $presets = $this->presetsProvider->getBookingPresets();
    8282        foreach ( $presets as $preset ) {
    83             $html = sprintf( '<div style="padding: 12px 16px; background: #f0f6fc; border-left: 4px solid #0073aa; margin: 8px 0;">
    84                     <p style="margin: 0 0 6px;">%s</p>
    85                     <p style="margin: 0; color: #555; font-style: italic;">%s</p>
    86                 </div>', esc_html( $preset['shortDescription'] ), esc_html( $preset['recommendation'] ) );
    87             $fields[] = Field::make( 'html', 'preset_' . $preset['id'] . '_desc' )->set_html( $html )->set_conditional_logic( [[
    88                 'field' => self::FIELD_BOOKING_TERMS,
    89                 'value' => $preset['id'],
    90             ]] );
    91         }
    92         // Custom description
    93         $fields[] = Field::make( 'html', 'custom_booking_terms_desc' )->set_html( sprintf( '<div style="padding: 12px 16px; background: #fff8e5; border-left: 4px solid #dba617; margin: 8px 0;">
    94                     <p style="margin: 0;">%s</p>
    95                 </div>', esc_html__( 'Manually select the payment method(s) and cancellation policy for this rate plan.', 'bookiflex' ) ) )->set_conditional_logic( [[
     83            $paymentTypeId = $this->findCptByPresetKey( PaymentType::POST_TYPE, $preset['paymentTypeKey'] );
     84            $cancellationPolicyId = $this->findCptByPresetKey( CancellationPolicy::POST_TYPE, $preset['cancellationPolicyKey'] );
     85            if ( $paymentTypeId && $cancellationPolicyId ) {
     86                $title = $preset['title'];
     87                if ( !empty( $preset['popular'] ) ) {
     88                    $title .= ' ' . __( '(recommended)', 'bookiflex' );
     89                }
     90                $description = $preset['shortDescription'];
     91                if ( !empty( $preset['recommendation'] ) ) {
     92                    $description .= ' ' . $preset['recommendation'];
     93                }
     94                $options[$preset['id']] = [
     95                    'title'       => $title,
     96                    'description' => $description,
     97                ];
     98            }
     99        }
     100        $options['custom'] = [
     101            'title'       => __( 'Custom', 'bookiflex' ),
     102            'description' => __( 'Manually select the payment method(s) and cancellation policy for this rate plan.', 'bookiflex' ),
     103        ];
     104        $default = $this->getDefaultBookingTermsValue();
     105        array_push( $fields, ...RadioCard::make(
     106            self::FIELD_BOOKING_TERMS,
     107            __( 'Booking terms', 'bookiflex' ),
     108            $options,
     109            $default,
     110            true
     111        ) );
     112        // Association fields — visible only for Custom
     113        $fields[] = Field::make( 'separator', 'bflex__cp__strict_separator', __( 'Configuration', 'bookiflex' ) )->set_conditional_logic( [[
    96114            'field' => self::FIELD_BOOKING_TERMS,
    97115            'value' => 'custom',
    98116        ]] );
    99         // Association fields — visible only for Custom
    100117        $fields[] = Field::make( 'association', self::FIELD_PAYMENT_TYPES, __( 'Payment types', 'bookiflex' ) )->set_types( [[
    101118            'type'      => 'post',
     
    116133
    117134    /**
    118      * Get available booking terms options for radio field.
    119      *
    120      * Only includes presets for which both PaymentType and CancellationPolicy CPTs
    121      * with matching `_bflex_preset_key` exist in the database.
    122      *
    123      * @return array<string, string>
    124      */
    125     private function getAvailableBookingTermsOptions() : array {
     135     * Get default value for booking terms radio.
     136     *
     137     * Returns the first available preset id, or 'custom' if none available.
     138     *
     139     * @return string
     140     */
     141    private function getDefaultBookingTermsValue() : string {
    126142        $presets = $this->presetsProvider->getBookingPresets();
    127         $options = [];
    128143        foreach ( $presets as $preset ) {
    129144            $paymentTypeId = $this->findCptByPresetKey( PaymentType::POST_TYPE, $preset['paymentTypeKey'] );
    130145            $cancellationPolicyId = $this->findCptByPresetKey( CancellationPolicy::POST_TYPE, $preset['cancellationPolicyKey'] );
    131146            if ( $paymentTypeId && $cancellationPolicyId ) {
    132                 $label = $preset['title'];
    133                 if ( !empty( $preset['popular'] ) ) {
    134                     $label .= ' ' . __( '(recommended)', 'bookiflex' );
    135                 }
    136                 $options[$preset['id']] = $label;
    137             }
    138         }
    139         $options['custom'] = __( 'Custom', 'bookiflex' );
    140         return $options;
    141     }
    142 
    143     /**
    144      * Get default value for booking terms radio.
    145      *
    146      * Returns the first available preset id, or 'custom' if none available.
    147      *
    148      * @return string
    149      */
    150     private function getDefaultBookingTermsValue() : string {
    151         $options = $this->getAvailableBookingTermsOptions();
    152         $keys = array_keys( $options );
    153         // Return first non-custom option, or 'custom'
    154         foreach ( $keys as $key ) {
    155             if ( $key !== 'custom' ) {
    156                 return $key;
     147                return $preset['id'];
    157148            }
    158149        }
  • bookiflex/trunk/src/Core/Admin/CPT/AbstractCPT.php

    r3478607 r3495560  
    4343            $query->set('order', 'ASC');
    4444        }
     45    }
     46
     47    protected function customizeMenuOrder(string $title, string $description): void
     48    {
     49        $this->plugin->addAction('add_meta_boxes', function () use ($title, $description) {
     50            add_meta_box(
     51                'bflex_' . static::POST_TYPE . '_menu_order',
     52                $title,
     53                function ($post) use ($description) {
     54                    echo '<input name="menu_order" type="number" min="0" id="menu_order" value="' . esc_attr($post->menu_order) . '" style="width:100%" />';
     55                    echo '<p class="description">' . esc_html($description) . '</p>';
     56                },
     57                static::POST_TYPE,
     58                'side'
     59            );
     60        });
     61    }
     62
     63    protected function customizeParentSelector(string $title, string $description): void
     64    {
     65        $this->plugin->addAction('add_meta_boxes', function () use ($title, $description) {
     66            add_meta_box(
     67                'bflex_' . static::POST_TYPE . '_parent',
     68                $title,
     69                function ($post) use ($description) {
     70                    $dropdown = wp_dropdown_pages([
     71                        'post_type'        => $post->post_type,
     72                        'exclude_tree'     => $post->ID,
     73                        'selected'         => $post->post_parent,
     74                        'name'             => 'parent_id',
     75                        'show_option_none' => __('(no parent)', 'bookiflex'),
     76                        'sort_column'      => 'menu_order, post_title',
     77                        'echo'             => 0,
     78                    ]);
     79                    if ($dropdown) {
     80                        echo $dropdown;
     81                    }
     82                    echo '<p class="description">' . esc_html($description) . '</p>';
     83                },
     84                static::POST_TYPE,
     85                'side'
     86            );
     87        });
     88    }
     89
     90    protected function customizeExcerpt(string $title, string $description): void
     91    {
     92        $this->plugin->addAction('add_meta_boxes', function () use ($title, $description) {
     93            add_meta_box(
     94                'bflex_' . static::POST_TYPE . '_excerpt',
     95                $title,
     96                function ($post) use ($title, $description) {
     97                    ?>
     98                    <label class="screen-reader-text" for="excerpt"><?php echo esc_html($title); ?></label>
     99                    <textarea rows="1" cols="40" name="excerpt" id="excerpt"><?php echo esc_html($post->post_excerpt); ?></textarea>
     100                    <p><?php echo esc_html($description); ?></p>
     101                    <?php
     102                },
     103                static::POST_TYPE,
     104                'normal'
     105            );
     106        });
    45107    }
    46108
  • bookiflex/trunk/src/Core/Admin/CPT/AccommodationType.php

    r3478607 r3495560  
    5656        // Register event hooks from trait
    5757        $this->registerEventHooks();
     58
     59        // Thumbnail column in list table
     60        $this->plugin->addFilter('manage_' . self::POST_TYPE . '_posts_columns', [$this, 'addThumbnailColumn']);
     61        $this->plugin->addAction('manage_' . self::POST_TYPE . '_posts_custom_column', [$this, 'renderThumbnailColumn'], 10, 2);
     62    }
     63
     64    /**
     65     * Insert "Image" column before "title"
     66     */
     67    public function addThumbnailColumn(array $columns): array
     68    {
     69        $result = [];
     70
     71        foreach ($columns as $key => $label) {
     72            if ($key === 'title') {
     73                $result['bflex_thumbnail'] = __('Image', 'bookiflex');
     74            }
     75            $result[$key] = $label;
     76        }
     77
     78        return $result;
     79    }
     80
     81    /**
     82     * Render thumbnail or placeholder in list column
     83     */
     84    public function renderThumbnailColumn(string $column, int $postId): void
     85    {
     86        if ($column !== 'bflex_thumbnail') {
     87            return;
     88        }
     89
     90        if (has_post_thumbnail($postId)) {
     91            echo get_the_post_thumbnail($postId, [80, 80]);
     92        } else {
     93            echo '<span class="bflex-thumbnail-placeholder dashicons dashicons-format-image"></span>';
     94        }
    5895    }
    5996
     
    99136            'hierarchical'       => false,
    100137            'menu_position'      => 0,
    101             'supports'           => ['title', 'excerpt', 'thumbnail', 'page-attributes'],
     138            'supports'           => ['title', 'thumbnail'],
    102139            'capability_type'    => self::POST_TYPE,
    103140            'capabilities'       => [
  • bookiflex/trunk/src/Core/Admin/CPT/CancellationPolicy.php

    r3478607 r3495560  
    1616namespace BookiFlex\Core\Admin\CPT;
    1717
     18use BookiFlex\Core\Contract\PluginInterface;
    1819use BookiFlex\Core\Contract\RoleCapabilitiesInterface;
    1920
     
    4344    public const FIELD_STRICT_TIME_WINDOW = 'bflex__cp__strict_time_window';
    4445    public const FIELD_STRICT_PENALTY_PERCENT = 'bflex__cp__strict_penalty_percent';
     46
     47    public function register(PluginInterface $plugin): void
     48    {
     49        parent::register($plugin);
     50
     51        $this->customizeExcerpt(
     52            __('Description', 'bookiflex'),
     53            __('An optional note for your own reference. This text is not shown to guests.', 'bookiflex')
     54        );
     55    }
    4556
    4657    public function registerPostType(): void
     
    8091            'hierarchical'       => false,
    8192            'menu_position'      => 2,
    82             'supports'           => ['title', 'excerpt'],
     93            'supports'           => ['title'],
    8394            'capability_type'    => self::POST_TYPE,
    8495            'capabilities'       => [
  • bookiflex/trunk/src/Core/Admin/CPT/PaymentType.php

    r3478607 r3495560  
    7878            'hierarchical'       => false,
    7979            'menu_position'      => 3,
    80             'supports'           => ['title', 'excerpt'],
     80            'supports'           => ['title'],
    8181            'capability_type'    => self::POST_TYPE,
    8282            'capabilities'       => [
  • bookiflex/trunk/src/Core/Admin/CPT/RatePlan.php

    r3478607 r3495560  
    8585            'hierarchical'       => $this->supportInheritance(),
    8686            'menu_position'      => 1,
    87             'supports'           => ['title', 'excerpt', 'page-attributes'],
     87            'supports'           => ['title'],
    8888            'capability_type'    => self::POST_TYPE,
    8989            'capabilities'       => [
  • bookiflex/trunk/vendor/composer/autoload_classmap.php

    r3478614 r3495560  
    2424    'BookiFlex\\Core\\Admin\\CTL\\CustomerTableList' => $baseDir . '/src/Core/Admin/CTL/CustomerTableList.php',
    2525    'BookiFlex\\Core\\Admin\\CTL\\ReservationTableList' => $baseDir . '/src/Core/Admin/CTL/ReservationTableList.php',
     26    'BookiFlex\\Core\\Admin\\Field\\RadioCard' => $baseDir . '/src/Core/Admin/Field/RadioCard.php',
     27    'BookiFlex\\Core\\Admin\\Field\\SectionNotice' => $baseDir . '/src/Core/Admin/Field/SectionNotice.php',
    2628    'BookiFlex\\Core\\Admin\\Lifecycle\\AbstractExtensionActivator' => $baseDir . '/src/Core/Admin/Lifecycle/AbstractExtensionActivator.php',
    2729    'BookiFlex\\Core\\Admin\\Lifecycle\\AbstractUninstaller' => $baseDir . '/src/Core/Admin/Lifecycle/AbstractUninstaller.php',
  • bookiflex/trunk/vendor/composer/autoload_static.php

    r3483810 r3495560  
    456456        'BookiFlex\\Core\\Admin\\CTL\\CustomerTableList' => __DIR__ . '/../..' . '/src/Core/Admin/CTL/CustomerTableList.php',
    457457        'BookiFlex\\Core\\Admin\\CTL\\ReservationTableList' => __DIR__ . '/../..' . '/src/Core/Admin/CTL/ReservationTableList.php',
     458        'BookiFlex\\Core\\Admin\\Field\\RadioCard' => __DIR__ . '/../..' . '/src/Core/Admin/Field/RadioCard.php',
     459        'BookiFlex\\Core\\Admin\\Field\\SectionNotice' => __DIR__ . '/../..' . '/src/Core/Admin/Field/SectionNotice.php',
    458460        'BookiFlex\\Core\\Admin\\Lifecycle\\AbstractExtensionActivator' => __DIR__ . '/../..' . '/src/Core/Admin/Lifecycle/AbstractExtensionActivator.php',
    459461        'BookiFlex\\Core\\Admin\\Lifecycle\\AbstractUninstaller' => __DIR__ . '/../..' . '/src/Core/Admin/Lifecycle/AbstractUninstaller.php',
  • bookiflex/trunk/vendor/composer/installed.php

    r3493377 r3495560  
    22    'root' => array(
    33        'name' => 'bookiflex/plugin',
    4         'pretty_version' => 'v1.1.1',
    5         'version' => '1.1.1.0',
    6         'reference' => '198f1b412b1060aca0e245573237bc45105d1d82',
     4        'pretty_version' => 'v1.2.0',
     5        'version' => '1.2.0.0',
     6        'reference' => 'b01ef3c27da8b2bf4bde963764fcc52c71eeb6db',
    77        'type' => 'wordpress-plugin',
    88        'install_path' => __DIR__ . '/../../',
     
    2121        ),
    2222        'bookiflex/plugin' => array(
    23             'pretty_version' => 'v1.1.1',
    24             'version' => '1.1.1.0',
    25             'reference' => '198f1b412b1060aca0e245573237bc45105d1d82',
     23            'pretty_version' => 'v1.2.0',
     24            'version' => '1.2.0.0',
     25            'reference' => 'b01ef3c27da8b2bf4bde963764fcc52c71eeb6db',
    2626            'type' => 'wordpress-plugin',
    2727            'install_path' => __DIR__ . '/../../',
Note: See TracChangeset for help on using the changeset viewer.