'application/json'];if($connection)$headers['Authorization']='Bearer '.$connection['connection_id'].'.'.$connection['secret']; $response=wp_remote_post('https://yooty.app/api/wordpress/connection.php',['headers'=>$headers,'body'=>wp_json_encode(array_merge($data,['action'=>$action])),'timeout'=>90,'redirection'=>0,'sslverify'=>true,'limit_response_size'=>1000000]); if(is_wp_error($response))throw new RuntimeException('Yooty could not be reached. Please retry.'); $body=json_decode(wp_remote_retrieve_body($response),true);if(!is_array($body)||empty($body['success']))throw new RuntimeException(is_array($body)?esc_html((string)($body['error']??'Request failed.')):'Yooty returned an unreadable response.');return $body; } function yooty_wp_connection_state(array $connection): array { try{return yooty_wp_request('status',[],$connection);}catch(RuntimeException $e){ if(!in_array($e->getMessage(),['Connection not found.','Connection expired or disconnected.','Invalid connection credential.','Invalid connection.'],true))throw $e; return ['status'=>'disconnected']; } } add_action('admin_menu',static function(){add_options_page(__('Yooty widgets','yooty-widgets'),__('Yooty widgets','yooty-widgets'),'manage_options','yooty-growth-widgets','yooty_growth_settings');}); add_filter('plugin_action_links_'.plugin_basename(__DIR__.'/yooty-growth-widgets.php'),static function($links){array_unshift($links,''.esc_html__('Settings','yooty-widgets').'');return $links;}); add_action('admin_enqueue_scripts',static function($hook){if($hook!=='settings_page_yooty-growth-widgets')return; wp_enqueue_script('yooty-widget-icons',plugins_url('widget-icons.js',__FILE__),[],'0.6.0-icons1',true); wp_enqueue_script('yooty-widget-copy',plugins_url('widget-copy.js',__FILE__),[],'0.6.0-measurement1',true);wp_enqueue_script('yooty-widget-locale',plugins_url('widget-locale.js',__FILE__),['yooty-widget-copy'],'0.6.0',true);wp_enqueue_script('yooty-widget-fields',plugins_url('widget-fields.js',__FILE__),['yooty-widget-locale'],'0.6.0',true);wp_enqueue_style('yooty-widget-fields',plugins_url('widget-fields.css',__FILE__),[],'0.6.0');wp_enqueue_script('yooty-widget-admin',plugins_url('admin.js',__FILE__),['yooty-widget-fields','yooty-widget-icons'],'0.6.0-measurement1',true);wp_add_inline_script('yooty-widget-admin','window.YootyWordPressAdmin='.wp_json_encode(['ajax'=>admin_url('admin-ajax.php'),'nonce'=>wp_create_nonce('yooty_widgets_admin')]).';','before');wp_enqueue_style('yooty-widget-admin',plugins_url('admin.css',__FILE__),[],'0.6.0-icons1'); }); function yooty_growth_settings(): void { if(!current_user_can('manage_options'))return; echo '

'.esc_html__('Yooty widgets','yooty-widgets').'

'.esc_html__('Connect your business to edit its shared widget settings here. Changes are saved in Yooty and apply wherever that widget is installed.','yooty-widgets').'

'.esc_html__('Register business','yooty-widgets').'

'; } add_action('wp_ajax_yooty_widgets_admin',static function(){ if(!current_user_can('manage_options'))wp_send_json_error(['message'=>'Administrator access required.'],403); check_ajax_referer('yooty_widgets_admin','nonce'); try{ $kind=sanitize_key(wp_unslash($_POST['kind']??''));$data=json_decode(sanitize_textarea_field(wp_unslash($_POST['payload']??'{}')),true,32,JSON_THROW_ON_ERROR);if(!is_array($data))throw new RuntimeException('Invalid request.');$connection=yooty_wp_connection(); if($kind==='start'){ if($connection){$state=yooty_wp_connection_state($connection);if(($state['status']??'')==='approved')throw new RuntimeException('Disconnect the current business before connecting another.');} $secret=bin2hex(random_bytes(32));$result=yooty_wp_request('start',['site_url'=>site_url('/'),'secret'=>$secret]);$url=wp_parse_url($result['verification_url']??'');if(($url['scheme']??'')!=='https'||($url['host']??'')!=='yooty.app'||($url['path']??'')!=='/wordpress/connect.php')throw new RuntimeException('Invalid connection URL.'); update_option('yooty_private_connection',yooty_wp_seal(['connection_id'=>$result['connection_id'],'secret'=>$secret,'verification_url'=>$result['verification_url']]),false);$out=['status'=>'pending','verification_url'=>$result['verification_url']]; }elseif($kind==='placements'){ $out=yooty_wp_placement_settings($data); }elseif($kind==='status'){ if(!$connection)$out=['status'=>'disconnected'];else{$out=yooty_wp_connection_state($connection);if(($out['status']??'')==='pending')$out['verification_url']=$connection['verification_url']; if(($out['status']??'')==='approved'){if(is_string($out['agent_instructions']??null))update_option('yooty_agent_instructions',$out['agent_instructions'],false);unset($out['agent_instructions']);$presentation=yooty_wp_request('presentation',[],$connection);update_option('yooty_booking_presentation',$presentation['presentation'],false);$tokens=[];foreach(($out['widgets']??[]) as $key=>$widget)if(!empty($widget['token']))$tokens[$key]=$widget['token'];update_option('yooty_widget_tokens',yooty_growth_sanitize($tokens),false);foreach($out['widgets'] as &$widget)unset($widget['token']);unset($widget);} } }elseif(in_array($kind,['load','save','disconnect','presentation'],true)){ if(!$connection)throw new RuntimeException('Connect your business first.');$out=yooty_wp_request($kind,$data,$connection); if($kind==='presentation')update_option('yooty_booking_presentation',$out['presentation'],false); if($kind==='disconnect'){delete_option('yooty_agent_instructions');delete_option('yooty_private_connection');delete_option('yooty_widget_tokens');delete_option('yooty_booking_presentation');} }else throw new RuntimeException('Invalid action.'); wp_send_json_success($out); }catch(Throwable $e){wp_send_json_error(['message'=>$e->getMessage()],400);} }); add_action('template_redirect',static function(){ $path=wp_parse_url(sanitize_url(wp_unslash($_SERVER['REQUEST_URI']??'')),PHP_URL_PATH);if($path!==wp_parse_url(home_url('/llms.txt'),PHP_URL_PATH))return; $text=(string)get_option('yooty_agent_instructions','');if($text===''||!yooty_wp_connection())return; status_header(200);header('Content-Type: text/plain; charset=utf-8');header('X-Content-Type-Options: nosniff'); // Plain-text discovery document: HTML escaping would corrupt Markdown and URLs. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- text/plain with nosniff; remove HTML tags instead. echo wp_strip_all_tags($text);exit; }); add_action('wp_head',static function(){if(get_option('yooty_agent_instructions','')!==''&&yooty_wp_connection())echo ''."\n";});