/ wordpress / restai / includes / class-restai-widget.php
class-restai-widget.php
  1  <?php
  2  /**
  3   * Embed the RESTai chat widget on the front-end via a single <script> tag.
  4   *
  5   * Lazily provisions a widget on the support bot project the first time the
  6   * tag is rendered, so the embedded script gets a real `wk_…` widget key and
  7   * authenticates instead of falling into preview mode.
  8   *
  9   * @package RESTai
 10   */
 11  
 12  namespace RESTai;
 13  
 14  if ( ! defined( 'ABSPATH' ) ) {
 15  	exit;
 16  }
 17  
 18  class Widget {
 19  
 20  	const STORE_KEY = 'restai_widget_credentials';
 21  
 22  	public function __construct() {
 23  		add_action( 'wp_footer', array( $this, 'maybe_embed' ) );
 24  		add_shortcode( 'restai_chat', array( $this, 'shortcode' ) );
 25  	}
 26  
 27  	public function maybe_embed() {
 28  		$settings = get_option( 'restai_settings', array() );
 29  		if ( empty( $settings['enable_widget'] ) ) {
 30  			return;
 31  		}
 32  		echo $this->script_tag(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
 33  	}
 34  
 35  	public function shortcode( $atts = array() ) {
 36  		return $this->script_tag( $atts );
 37  	}
 38  
 39  	private function script_tag( $overrides = array() ) {
 40  		$settings   = get_option( 'restai_settings', array() );
 41  		$widget_cfg = get_option( 'restai_widget_settings', array() );
 42  		$plugin     = Plugin::instance();
 43  		$project_id = $plugin->provisioner->project_for( 'support_bot' );
 44  		if ( empty( $settings['url'] ) || $project_id <= 0 ) {
 45  			return '';
 46  		}
 47  
 48  		$widget_key = $this->ensure_widget_key( $project_id, $widget_cfg );
 49  		if ( empty( $widget_key ) ) {
 50  			// Fall back to preview mode rather than a broken script tag.
 51  			return '';
 52  		}
 53  
 54  		$url     = untrailingslashit( $settings['url'] );
 55  		$title   = isset( $widget_cfg['title'] )   ? $widget_cfg['title']   : __( 'Chat with us', 'restai' );
 56  		$color   = isset( $widget_cfg['color'] )   ? $widget_cfg['color']   : '#6366f1';
 57  		$welcome = isset( $widget_cfg['welcome'] ) ? $widget_cfg['welcome'] : __( 'Hi! How can I help?', 'restai' );
 58  
 59  		$title   = isset( $overrides['title'] )   ? $overrides['title']   : $title;
 60  		$color   = isset( $overrides['color'] )   ? $overrides['color']   : $color;
 61  		$welcome = isset( $overrides['welcome'] ) ? $overrides['welcome'] : $welcome;
 62  
 63  		return sprintf(
 64  			'<script src="%1$s/widget/chat.js" data-widget-key="%2$s" data-stream="true" data-title="%3$s" data-primary-color="%4$s" data-welcome-message="%5$s"></script>',
 65  			esc_url( $url ),
 66  			esc_attr( $widget_key ),
 67  			esc_attr( $title ),
 68  			esc_attr( $color ),
 69  			esc_attr( $welcome )
 70  		);
 71  	}
 72  
 73  	/**
 74  	 * Returns a `wk_…` widget key for the given support-bot project. If we've
 75  	 * already provisioned one (and it's still bound to the same project) we
 76  	 * reuse it; otherwise we create a new one on RESTai and stash the key.
 77  	 *
 78  	 * @param int   $project_id
 79  	 * @param array $widget_cfg user-tunable widget settings (title/color/...)
 80  	 * @return string|null
 81  	 */
 82  	private function ensure_widget_key( $project_id, $widget_cfg ) {
 83  		$stored = get_option( self::STORE_KEY, array() );
 84  		if ( ! empty( $stored['widget_key'] ) && (int) ( $stored['project_id'] ?? 0 ) === $project_id ) {
 85  			return $stored['widget_key'];
 86  		}
 87  
 88  		$client = Plugin::instance()->client;
 89  		$home_host = wp_parse_url( home_url(), PHP_URL_HOST );
 90  
 91  		$payload = array(
 92  			'name'            => 'WordPress chat widget',
 93  			'allowed_domains' => $home_host ? array( $home_host ) : array(),
 94  			'config'          => array(
 95  				'title'          => isset( $widget_cfg['title'] )   ? (string) $widget_cfg['title']   : 'Chat with us',
 96  				'subtitle'       => '',
 97  				'primaryColor'   => isset( $widget_cfg['color'] )   ? (string) $widget_cfg['color']   : '#6366f1',
 98  				'welcomeMessage' => isset( $widget_cfg['welcome'] ) ? (string) $widget_cfg['welcome'] : 'Hi! How can I help?',
 99  				'stream'         => true,
100  			),
101  		);
102  
103  		$resp = $client->post( 'projects/' . $project_id . '/widgets', $payload );
104  		if ( is_wp_error( $resp ) || empty( $resp['widget_key'] ) ) {
105  			if ( is_wp_error( $resp ) ) {
106  				error_log( '[RESTai] widget create failed: ' . $resp->get_error_message() );
107  			}
108  			return null;
109  		}
110  
111  		$record = array(
112  			'widget_key' => $resp['widget_key'],
113  			'widget_id'  => isset( $resp['id'] ) ? (int) $resp['id'] : 0,
114  			'project_id' => $project_id,
115  			'created_at' => time(),
116  		);
117  		update_option( self::STORE_KEY, $record );
118  		return $record['widget_key'];
119  	}
120  }