display_name, $userdata['display_name'] ) || $old_user->display_name !== $userdata['display_name'] ) { self::flush_service_cache(); } } /** * Whether the current page is a Timetics page: a meeting page, or a post * holding any Timetics shortcode. * * @return bool */ public function is_timetics_page(): bool { if ( is_singular( 'timetics-appointment' ) ) { return true; } // On a blog list or archive get_post() is the first post in the loop, // not the page the visitor is on. if ( ! is_singular() ) { return false; } $post = get_post(); if ( ! $post instanceof \WP_Post ) { return false; } return 1 === preg_match( '/\[timetics-[a-z-]+/', (string) $post->post_content ); } /** * Refresh the cached meeting list when a meeting is deleted. * * @param int $post_id Post id. * @return void */ public function flush_on_meeting_delete( $post_id ): void { if ( 'timetics-appointment' === get_post_type( (int) $post_id ) ) { self::flush_service_cache(); } } /** * Make every cached Timetics meeting search stale. * * @return void */ public static function flush_service_cache(): void { update_option( Timetics_Service_Search_Tool::CACHE_GENERATION, (int) get_option( Timetics_Service_Search_Tool::CACHE_GENERATION, 0 ) + 1, false ); } /** * Get services * * @return array */ public function get_services(): array { return []; } /** * Describe the Timetics panel for the Public Agent settings page. * * @param array $panels Panels registered so far. * @return array */ public function add_panel( array $panels ): array { $panels['timetics'] = [ 'topic' => [ 'label' => __( 'Book a meeting', 'aisentic' ), 'description' => __( 'See free times and book in a few taps', 'aisentic' ), 'icon' => 'calendar', 'matches_page' => [ $this, 'is_timetics_page' ], ], 'label' => __( 'Timetics', 'aisentic' ), 'docs' => 'https://arraytics.com/docs/wptimetics/how-to-set-up-the-ai-booking-assistant-in-wptimetics/', 'icon' => 'calendar', 'available' => fn(): bool => ( new Timetics_Public_Gateway() )->is_active(), 'fields' => [ 'recommendedLimit' => [ 'type' => 'number', 'label' => __( 'Services Shown', 'aisentic' ), 'help' => __( 'Maximum number of services the assistant shows per search.', 'aisentic' ), 'min' => 1, 'max' => 12, 'default' => 4, ], 'contactMessage' => [ 'type' => 'textarea', 'label' => __( 'Contact Message', 'aisentic' ), 'help' => __( 'Shown when a visitor needs to reach you, for example after too many booking-link requests in one day.', 'aisentic' ), 'default' => self::default_contact(), ], 'outOfScopeMessage' => [ 'type' => 'textarea', 'label' => __( 'Out of Scope Message', 'aisentic' ), 'help' => __( 'Shown when a visitor asks something outside the assistant\'s scope, such as a payment or a refund.', 'aisentic' ), 'default' => self::default_out_of_scope(), ], 'starterPrompts' => [ 'type' => 'prompts', 'label' => __( 'Starter Prompts', 'aisentic' ), 'help' => __( 'Quick-reply chips shown when the chat opens.', 'aisentic' ), 'max' => 8, 'maxChars' => 100, 'default' => [ __( 'What services do you offer?', 'aisentic' ), __( 'Any free times this week?', 'aisentic' ), __( 'How much is a consultation?', 'aisentic' ), __( 'I need to change my booking', 'aisentic' ), ], ], ], ]; return $panels; } /** * Add the Timetics tools to the public chat toolset * * @param array $tools Existing tools. * @param Settings $settings Settings instance. * @return array */ public function add_tools( array $tools, Settings $settings ): array { if ( ! $this->is_enabled( $settings ) ) { return $tools; } $tools[] = new Timetics_Service_Search_Tool( $settings ); $tools[] = new Timetics_Available_Slots_Tool( $settings ); $tools[] = new Timetics_Booking_Link_Tool( $settings ); $tools[] = new Timetics_Manage_Links_Tool( $settings ); return $tools; } /** * Append the Timetics booking prompt to the public chat instructions * * @param string $prompt Existing prompt. * @param Settings $settings Settings instance. * @return string */ public function add_instructions( string $prompt, Settings $settings ): string { if ( ! $this->is_enabled( $settings ) ) { return $prompt; } $agent_settings = new Public_Agent_Settings( $settings ); return $prompt . "\n\n" . Timetics_Prompt_Bank::get_sales_prompt( (string) $agent_settings->integration( 'timetics', 'outOfScopeMessage', self::default_out_of_scope() ), (string) $agent_settings->integration( 'timetics', 'contactMessage', self::default_contact() ), new \DateTimeImmutable( 'now', new \DateTimeZone( Visitor_Timezone::get() ) ) ); } /** * Let the shared manage-links endpoint reach Timetics. * * @param array $senders Senders by plugin slug. * @return array */ public function add_sender( array $senders ): array { // No sender while the Timetics chat is off, so the public endpoint cannot // send Timetics emails on a site that only uses another integration. if ( ! $this->is_enabled( new Settings() ) ) { return $senders; } $senders['timetics'] = static function ( string $email, array $hint ): void { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Timetics' own action; Timetics emails the links. do_action( 'timetics_send_manage_links', $email, $hint ); }; return $senders; } /** * Whether the Timetics panel is on and Timetics is active. * * @param Settings $settings Settings instance. * @return bool */ private function is_enabled( Settings $settings ): bool { // is_enabled() already checks the panel's `available` callback. return ( new Public_Agent_Settings( $settings ) )->is_enabled( 'timetics' ); } }