# booktics/1.0.19/base/models/appointment-model.php

Booktics – Appointment Booking Calendar for Service Businesses, version 1.0.19. 316 lines.

- Page: https://pluginprobe.com/plugins/booktics/1.0.19/code/base/models/appointment-model.php
- Raw: https://pluginprobe.com/plugins/booktics/1.0.19/raw/base/models/appointment-model.php
- Modified: 2026-04-28T07:37:48+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/booktics/1.0.19/code/base/models/appointment-model.php#L10-L20`.

```php
<?php
/**
 * Appoint Model
 *
 * @package Booktics/Models
 */

namespace Booktics\Models;

use Booktics\Abstracts\Booktics_Database;
use Booktics\Abstracts\Post_Model;
use Booktics\Models\Service_Model;
use Exception;

/**
 * AppointmentModel class
 */
class Appointment_Model extends Post_Model {
    /**
     * Store appointment data
     *
     * @var array
     */
    protected $fillable = array(
        'id'                     => '',
        'title'                  => '',
        'content'                => '',
        'start_time'             => '',
        'end_time'               => '',
        'date'                   => '',
        'buffer_before'          => '',
        'buffer_after'           => '',
        'duration'               => '',
        'status'                 => '',
        'payment_status'         => '',
        'customer_id'            => '',
        'customer_name'          => '',
        'team_member_id'         => '',
        'service_id'             => '',
        'additional_duration_id' => '',
        'group_booking'          => array(),
        'order_id'               => '',
        'quantity'               => 0,
        'price'                  => 0,
        'subtotal'               => 0,
        'total'                  => 0,
        'note'                   => '',
        'source'                 => '',
        'location_id'            => '',
        'location'               => array(),
        'extra_services'         => array(),
        'security_token'         => '',
        'total_attendees'        => 1,
        'timezone'               => '',
        'cancel_note'            => '',
    );

    /**
     * Accepted post statuses for appointments
     *
     * @var array
     */
    public static $accepted_statuses = array(
        'pending',
        'active',
        'cancelled',
        'canceled',
        'completed',
        'approved',
        'not_now',
    );

    /**
     * Get post type
     *
     * @return  string Post type name
     */
    public function get_post_type() {
        return 'booktics-appointment';
    }

    /**
     * Get accepted statuses
     *
     * @return  array Accepted statuses
     */
    public static function get_accepted_statuses() {
        return self::$accepted_statuses;
    }

    /**
     * Specify data which should be serialized to JSON.
     *
     * @return array
     */
    public function jsonSerialize(): array {
        $data = $this->to_array();

        $service     = Service_Model::find( $this->service_id );
        $db          = new Booktics_Database();
        $customer    = ( new Guest_Model( $db ) )->find( array( 'id' => $this->customer_id ) );
        $team_member = ( new Team_Member_Model() )->find( $this->team_member_id );

        $appointment_data = array(
            'service'     => $service,
            'team_member' => $team_member,
            'customer'    => $customer,
        );

        return array_merge( $data, $appointment_data );
    }

    /**
     * Get appointments for a specific date and service/team member
     *
     * @param string $date
     * @param int $team_member_id
     *
     * @return array
     * @throws Exception
     */
    public function get_appointments_for_slot( string $date, int $team_member_id ): array {
        $args = array(
            'post_type'      => $this->get_post_type(),
            'post_status'    => booktics_get_option( 'appointment_statuses_for_blocked_slot', array( 'publish', 'active', 'pending' ) ),
            'posts_per_page' => - 1,
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering appointments by meta fields
            'meta_query'     => array(
                'relation' => 'AND',
                array(
                    'key'     => 'date',
                    'value'   => $date,
                    'compare' => '=',
                ),
                array(
                    'key'     => 'team_member_id',
                    'value'   => $team_member_id,
                    'compare' => '=',
                ),
            ),
        );

        $query = new \WP_Query( $args );

        return array_map(
            function ( $post ) {
                return new static( $post );
            }, $query->posts
        );
    }

    /**
     * Override load_attributes to include payment status from orders
     *
     * @return void
     */
    protected function load_attributes() {
        parent::load_attributes();

        if ( $this->order_id ) {
            $db          = new Booktics_Database();
            $order_model = ( new Order_Model( $db ) )->find( array( 'id' => $this->order_id ) );

            $this->attributes['payment_status'] = $order_model->payment_status ?? 'pending';
        } else {
            $this->attributes['payment_status'] = 'pending';
        }
    }

    /**
     * Get appointments count by order ID
     *
     * @param int $order_id Order ID
     *
     * @return int Number of appointments
     */
    public function get_appointments_count_by_order_id( $order_id ) {
        $args = array(
            'post_type'      => $this->get_post_type(),
            'post_status'    => array( 'publish', 'active', 'approved', 'completed', 'pending' ),
            'posts_per_page' => - 1,
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering appointments by meta fields
            'meta_query'     => array(
                array(
                    'key'     => 'order_id',
                    'value'   => $order_id,
                    'compare' => '=',
                ),
            ),
        );

        $query = new \WP_Query( $args );

        return $query->found_posts;
    }

    /**
     * Get appointments count by team member ID
     *
     * @param int $team_member_id Team member ID
     *
     * @return int Number of appointments
     */
    public function get_appointments_count_by_team_member_id( $team_member_id ) {
        $args = array(
            'post_type'      => $this->get_post_type(),
            'post_status'    => array( 'publish', 'active', 'pending' ),
            'posts_per_page' => - 1,
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering appointments by meta fields
            'meta_query'     => array(
                array(
                    'key'     => 'team_member_id',
                    'value'   => $team_member_id,
                    'compare' => '=',
                ),
            ),
        );

        $query = new \WP_Query( $args );

        return $query->found_posts;
    }

    /**
     * Get reschedule link
     *
     * @return string Reschedule link
     */
    public function get_reschedule_link() {
        if ( ! booktics_get_option( 'allow_customer_rescheduling', false ) ) {
            return '';
        }

        $this->add_security_token_if_not_exists();

        $service_permalink = $this->get_associated_service_permalink();

        $reschedule_url = add_query_arg( [
            'service_id'        => $this->service_id,
            'appointment_id'    => $this->id,
            'booking_action'    => 'reschedule',
            'appointment_token' => $this->security_token,
            'order_id'          => $this->order_id,
            'team_member_id'    => $this->team_member_id,
        ], $service_permalink );

        return esc_url( $reschedule_url );
    }

    /**
     * Get cancel link
     *
     * @return string Cancel link
     */
    public function get_cancel_link() {
        if ( ! booktics_get_option( 'allow_customer_cancellation', false ) ) {
            return '';
        }

        $this->add_security_token_if_not_exists();

        $service_permalink = $this->get_associated_service_permalink();

        $cancel_url = add_query_arg( [
            'service_id'        => $this->service_id,
            'appointment_id'    => $this->id,
            'booking_action'    => 'cancel',
            'appointment_token' => $this->security_token,
            'order_id'          => $this->order_id,
            'team_member_id'    => $this->team_member_id,
        ], $service_permalink );

        return esc_url( $cancel_url );
    }

    /**
     * Get associated service permalink
     *
     * @return string Service permalink
     */
    private function get_associated_service_permalink() {
        if ( ! $this->service_id ) {
            return '';
        }

        $service = Service_Model::find( $this->service_id );

        return $service->link ?? '';
    }

    /**
     * Generate a security token for the appointment
     * This generated token is used to verify access to the appointment rescheduling and cancellation
     *
     * @return string Security token
     */
    public function generate_security_token() {
        $token = bin2hex( random_bytes(4) ); // 8 hex chars

        return $token;
    }

    /**
     * Check if the security token exists, if not generate and save it to the post meta.
     * This token is used to verify access to the appointment rescheduling and cancellation.
     * 
     * @return void
     */
    public function add_security_token_if_not_exists() {
        if ( empty( $this->security_token ) ) {
            $this->security_token = $this->generate_security_token();
            $this->save();
        }
    }
}

```
