Advanced Web Server Manager
Complete File Manager & Terminal - Standalone Version
By Sid Gifari | Gifari Industries
Current path:
/
/
home
/
qtdcvxyp
/
public_html
/
wp-content
/
plugins
/
star4hire-core
/
includes
✏️
Editing: class-s4h-admin.php
<?php if ( ! defined( 'ABSPATH' ) ) { exit; } class S4H_Admin { public static function init(){ add_action('admin_menu',array(__CLASS__,'menu')); add_action('admin_init',array(__CLASS__,'settings')); add_action('admin_init',array(__CLASS__,'handle_actions')); add_action('admin_enqueue_scripts',array(__CLASS__,'assets')); add_action('wp_ajax_s4h_talent_search',array(__CLASS__,'ajax_talent_search')); } public static function assets($hook){ if(strpos($hook,'s4h')===false)return; wp_enqueue_style('s4h-admin',S4H_CORE_URL.'assets/css/admin.css',array(),S4H_CORE_VERSION); if(strpos($hook,'s4h-journeys')!==false){ wp_enqueue_script('s4h-admin-talent-search',S4H_CORE_URL.'assets/js/admin-talent-search.js',array(),S4H_CORE_VERSION,true); wp_localize_script('s4h-admin-talent-search','S4HTalentSearch',array('ajaxUrl'=>admin_url('admin-ajax.php'),'nonce'=>wp_create_nonce('s4h_talent_search'))); } } public static function ajax_talent_search(){ if(!current_user_can('s4h_journey_update') && !current_user_can('manage_s4h'))wp_send_json_error(array('message'=>'Forbidden'),403); check_ajax_referer('s4h_talent_search','nonce'); $q=sanitize_text_field(wp_unslash($_GET['q']??'')); if(strlen($q)<2)wp_send_json_success(array()); $rows=S4H_DB::search_talents($q,20);$out=array(); foreach($rows as $r)$out[]=array('talent_id'=>$r->talent_id,'name'=>$r->full_name,'whatsapp'=>$r->whatsapp,'email'=>$r->email,'label'=>$r->full_name.' · '.$r->talent_id.($r->whatsapp?' · '.$r->whatsapp:'')); wp_send_json_success($out); } public static function menu(){ add_menu_page('STAR4Hire','STAR4Hire','s4h_access','s4h-core',array(__CLASS__,'dashboard'),'dashicons-location-alt',25); add_submenu_page('s4h-core','Dashboard','Dashboard','s4h_access','s4h-core',array(__CLASS__,'dashboard')); add_submenu_page('s4h-core','Talents','Talents','manage_s4h','s4h-talents',array(__CLASS__,'talents')); add_submenu_page('s4h-core','Assessments','Assessments','manage_s4h','s4h-assessments',array(__CLASS__,'assessments')); add_submenu_page('s4h-core','English Tests','English Tests','manage_s4h','s4h-english-tests',array(__CLASS__,'english_tests')); add_submenu_page('s4h-core','Event Registrations','Event Registrations','manage_s4h','s4h-event-registrations',array(__CLASS__,'event_registrations')); add_submenu_page('s4h-core','Sales & Revenue','Sales & Revenue','s4h_finance','s4h-sales-revenue',array(__CLASS__,'sales_revenue')); add_submenu_page('s4h-core','JourneyOS','JourneyOS','s4h_journey_update','s4h-journeys',array(__CLASS__,'journeys')); add_submenu_page('s4h-core','Participant Success','Participant Success','s4h_success_view','s4h-participant-success',array('S4H_Success','admin_page')); add_submenu_page('s4h-core','Counselor Assignments','Counselor Assignments','manage_s4h','s4h-counselor-assignments',array(__CLASS__,'counselor_assignments')); add_submenu_page('s4h-core','Counselor Reviews','Counselor Reviews','s4h_career_architect','s4h-counselor-reviews',array(__CLASS__,'counselor_reviews')); add_submenu_page('s4h-core','Audit Log','Audit Log','manage_s4h','s4h-audit-log',array(__CLASS__,'audit_log')); add_submenu_page('s4h-core','English Question Bank','English Question Bank','manage_s4h','s4h-english-questions',array(__CLASS__,'english_questions')); add_submenu_page('s4h-core','Settings','Settings','manage_s4h','s4h-settings',array(__CLASS__,'settings_page')); } public static function settings(){ // Google register_setting('s4h_core_settings','s4h_google_enabled',array('sanitize_callback'=>fn($v)=>$v?1:0)); register_setting('s4h_core_settings','s4h_google_webapp_url',array('sanitize_callback'=>'esc_url_raw')); register_setting('s4h_core_settings','s4h_google_shared_secret',array('sanitize_callback'=>'sanitize_text_field')); add_settings_section('s4h_google','Google Sheets Operational Mirror',function(){echo '<p>Operational mirror only. STAR4Hire Core remains the system of record.</p>';},'s4h-settings'); add_settings_field('s4h_google_enabled','Enable Google Sheets Sync',function(){self::checkbox('s4h_google_enabled');},'s4h-settings','s4h_google'); add_settings_field('s4h_google_webapp_url','Apps Script Web App URL',function(){self::text('s4h_google_webapp_url');},'s4h-settings','s4h_google'); add_settings_field('s4h_google_shared_secret','Shared Secret',function(){self::password('s4h_google_shared_secret','Use the same secret in Google Apps Script.');},'s4h-settings','s4h_google'); // Community register_setting('s4h_core_settings','s4h_community_url',array('sanitize_callback'=>'esc_url_raw')); add_settings_section('s4h_identity','Identity & Community',function(){echo '<p>Recognized talents can register for future events with one click.</p>';},'s4h-settings'); add_settings_field('s4h_community_url','Community URL',function(){self::text('s4h_community_url','https://community.star4hire.com/');},'s4h-settings','s4h_identity'); // GHL $check=array('s4h_ghl_enabled','s4h_ghl_create_opportunity_on_event'); foreach($check as $o) register_setting('s4h_core_settings',$o,array('sanitize_callback'=>fn($v)=>$v?1:0)); $text=array('s4h_ghl_location_id','s4h_ghl_pipeline_id','s4h_ghl_stage_new','s4h_ghl_stage_contacted','s4h_ghl_stage_webinar','s4h_ghl_stage_career_day','s4h_ghl_stage_gps','s4h_ghl_stage_pathway','s4h_ghl_stage_decision','s4h_ghl_stage_commitment','s4h_ghl_cf_talent_id','s4h_ghl_cf_career_score','s4h_ghl_cf_english_level','s4h_ghl_cf_first_source','s4h_ghl_cf_recommended_pathway','s4h_ghl_webhook_secret'); foreach($text as $o) register_setting('s4h_core_settings',$o,array('sanitize_callback'=>'sanitize_text_field')); register_setting('s4h_core_settings','s4h_ghl_token',array('sanitize_callback'=>'sanitize_text_field')); add_settings_section('s4h_ghl','HighLevel Sales & Revenue Bridge',function(){echo '<p>Use a Sub-Account Private Integration token with only the scopes required for contacts and opportunities. Keep the token secret.</p>';},'s4h-settings'); add_settings_field('s4h_ghl_enabled','Enable GHL Bridge',function(){self::checkbox('s4h_ghl_enabled');},'s4h-settings','s4h_ghl'); add_settings_field('s4h_ghl_token','Private Integration Token',function(){self::password('s4h_ghl_token','Rotate periodically in HighLevel.');},'s4h-settings','s4h_ghl'); add_settings_field('s4h_ghl_location_id','Sub-Account / Location ID',function(){self::text('s4h_ghl_location_id');},'s4h-settings','s4h_ghl'); add_settings_field('s4h_ghl_pipeline_id','Global Career Sales Pipeline ID',function(){self::text('s4h_ghl_pipeline_id');},'s4h-settings','s4h_ghl'); add_settings_field('s4h_ghl_create_opportunity_on_event','Create/Upsert Opportunity on Event Registration',function(){self::checkbox('s4h_ghl_create_opportunity_on_event',1);},'s4h-settings','s4h_ghl'); $labels=array('s4h_ghl_stage_new'=>'New Qualified Lead','s4h_ghl_stage_contacted'=>'Contacted','s4h_ghl_stage_webinar'=>'Webinar','s4h_ghl_stage_career_day'=>'Career Day','s4h_ghl_stage_gps'=>'Career GPS','s4h_ghl_stage_pathway'=>'Pathway Recommended','s4h_ghl_stage_decision'=>'Decision / Follow-Up','s4h_ghl_stage_commitment'=>'Commitment / Payment'); foreach($labels as $o=>$lab) add_settings_field($o,$lab.' Stage ID',function()use($o){self::text($o);},'s4h-settings','s4h_ghl'); add_settings_field('s4h_ghl_webhook_secret','Revenue Webhook Secret',function(){self::password('s4h_ghl_webhook_secret','Use this secret in the GHL workflow that sends opportunity/revenue updates back to STAR4Hire.');},'s4h-settings','s4h_ghl'); add_settings_section('s4h_ghl_fields','Optional GHL Custom Field Mapping',function(){echo '<p>Create these contact custom fields in GHL if you want richer sales context. Leave blank to skip.</p>';},'s4h-settings'); $cfs=array('s4h_ghl_cf_talent_id'=>'Talent ID field ID','s4h_ghl_cf_career_score'=>'Career Score field ID','s4h_ghl_cf_english_level'=>'English Level field ID','s4h_ghl_cf_first_source'=>'First Source field ID','s4h_ghl_cf_recommended_pathway'=>'Recommended Pathway field ID'); foreach($cfs as $o=>$lab)add_settings_field($o,$lab,function()use($o){self::text($o);},'s4h-settings','s4h_ghl_fields'); } private static function checkbox($name,$default=0){echo '<label><input type="checkbox" name="'.esc_attr($name).'" value="1" '.checked(1,(int)get_option($name,$default),false).'> Enabled</label>';} private static function text($name,$default=''){echo '<input class="regular-text code" name="'.esc_attr($name).'" value="'.esc_attr(get_option($name,$default)).'">';} private static function password($name,$desc=''){echo '<input class="regular-text code" type="password" autocomplete="new-password" name="'.esc_attr($name).'" value="'.esc_attr(get_option($name,'')).'">';if($desc)echo '<p class="description">'.esc_html($desc).'</p>';} public static function handle_actions(){ if(current_user_can('manage_s4h') && !empty($_POST['s4h_admin_action']) && $_POST['s4h_admin_action']==='assign_counselor'){ check_admin_referer('s4h_assign_counselor'); $talent=S4H_DB::get_talent_by_talent_id(sanitize_text_field(wp_unslash($_POST['talent_id']??''))); $uid=absint($_POST['counselor_user_id']??0); if($talent && $uid){$id=S4H_DB::assign_counselor($talent->id,$uid,get_current_user_id());S4H_DB::audit(get_current_user_id(),'counselor_assigned','assignment',$id,$talent->talent_id,array('counselor_user_id'=>$uid));} wp_safe_redirect(admin_url('admin.php?page=s4h-counselor-assignments&assigned=1'));exit; } if((current_user_can('s4h_career_architect')||current_user_can('manage_s4h')) && !empty($_POST['s4h_admin_action']) && $_POST['s4h_admin_action']==='create_journey'){ check_admin_referer('s4h_create_journey'); $talent=S4H_DB::get_talent_by_talent_id(sanitize_text_field(wp_unslash($_POST['talent_id']??''))); if(!$talent){wp_safe_redirect(admin_url('admin.php?page=s4h-journeys&journey_error=talent'));exit;} $template=sanitize_key($_POST['template_key']??'generic'); $program_name=sanitize_text_field(wp_unslash($_POST['program_name']??'')); $owner=absint($_POST['owner_user_id']??0);$target=sanitize_text_field(wp_unslash($_POST['target_date']??'')); $journey=S4H_Journey::create($talent,$template,$program_name,'',$owner,$target); if(is_wp_error($journey)){wp_safe_redirect(admin_url('admin.php?page=s4h-journeys&journey_error=create'));exit;} set_transient('s4h_journey_created_'.get_current_user_id(),array('participant'=>S4H_Journey::participant_url($journey),'parent'=>S4H_Journey::parent_url($journey),'journey_id'=>$journey->journey_id),5*MINUTE_IN_SECONDS); wp_safe_redirect(admin_url('admin.php?page=s4h-journeys&journey='.$journey->journey_id.'&created=1'));exit; } if((current_user_can('s4h_journey_update')||current_user_can('manage_s4h')) && !empty($_POST['s4h_admin_action']) && $_POST['s4h_admin_action']==='update_journey'){ $jid=sanitize_text_field(wp_unslash($_POST['journey_id']??''));check_admin_referer('s4h_update_journey_'.$jid); $journey=S4H_DB::get_journey_by_journey_id($jid);if(!$journey)wp_die('Journey not found.'); $data=array( 'public_note'=>sanitize_textarea_field(wp_unslash($_POST['public_note']??'')), 'internal_note'=>sanitize_textarea_field(wp_unslash($_POST['internal_note']??'')), 'next_action'=>sanitize_textarea_field(wp_unslash($_POST['next_action']??'')), 'action_required'=>sanitize_textarea_field(wp_unslash($_POST['action_required']??'')), 'owner_user_id'=>absint($_POST['owner_user_id']??0), 'due_at'=>sanitize_text_field(wp_unslash($_POST['due_at']??'')), 'visibility'=>sanitize_key($_POST['visibility']??'participant'), 'health_status'=>sanitize_key($_POST['health_status']??'on_track'), 'title'=>sanitize_text_field(wp_unslash($_POST['title']??'')), ); $res=S4H_Journey::update_stage($journey,sanitize_key($_POST['stage_key']??''),sanitize_key($_POST['stage_status']??'in_progress'),$data); if(is_wp_error($res)){wp_safe_redirect(admin_url('admin.php?page=s4h-journeys&journey='.$jid.'&journey_error=update'));exit;} wp_safe_redirect(admin_url('admin.php?page=s4h-journeys&journey='.$jid.'&updated=1'));exit; } if((current_user_can('s4h_career_architect')||current_user_can('manage_s4h')) && !empty($_GET['s4h_review_action']) && !empty($_GET['review_id'])){ $rid=absint($_GET['review_id']); check_admin_referer('s4h_review_'.$rid); $review=S4H_DB::get_counselor_review($rid);$action=sanitize_key($_GET['s4h_review_action']);$status=$action==='reject'?'rejected':'approved'; S4H_DB::approve_counselor_review($rid,get_current_user_id(),$status); S4H_DB::audit(get_current_user_id(),'counselor_review_'.$status,'counselor_review',$rid,$review?$review->talent_id:'',array('architect_trigger'=>$action==='approve_create_journey')); $created_journey=''; if($status==='approved' && $action==='approve_create_journey' && $review){ global $wpdb;$t=S4H_DB::tables();$code='CAREER-GPS-'.$rid; $existing=$wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['journeys']} WHERE talent_db_id=%d AND program_code=%s LIMIT 1",$review->talent_db_id,$code)); if($existing){$created_journey=$existing->journey_id;}else{ $talent=S4H_DB::get_talent($review->talent_db_id);$path=strtolower((string)$review->primary_pathway);$template='generic'; if(strpos($path,'german')!==false)$template='germany';elseif(strpos($path,'j1')!==false)$template='j1_usa';elseif(strpos($path,'f1')!==false)$template='f1_usa';elseif(strpos($path,'uk')!==false||strpos($path,'dreamleap')!==false||strpos($path,'united kingdom')!==false)$template='dreamleap_beyond';elseif(strpos($path,'local')!==false)$template='local_placement'; $name='Career GPS'.($review->primary_pathway?' — '.$review->primary_pathway:''); $j=S4H_Journey::create($talent,$template,$name,$code,get_current_user_id(),''); if(!is_wp_error($j)){ $created_journey=$j->journey_id;$tpl=S4H_Journey::template($j->template_key);$first=$tpl['stages'][0]??array('key'=>'start'); S4H_Journey::update_stage($j,$first['key'],'in_progress',array('public_note'=>'Your Career GPS has been approved and this career journey is now active.','next_action'=>$review->next_actions,'internal_note'=>'Created from counselor review #'.$rid,'owner_user_id'=>get_current_user_id(),'health_status'=>'on_track','visibility'=>'participant')); S4H_DB::audit(get_current_user_id(),'career_gps_journey_triggered','journey',$j->id,$review->talent_id,array('review_id'=>$rid,'pathway'=>$review->primary_pathway)); } } } if($status==='approved')do_action('s4h_counselor_review_approved',$rid,'approved'); $url=admin_url('admin.php?page=s4h-counselor-reviews');if($created_journey)$url=add_query_arg('career_journey',$created_journey,$url);wp_safe_redirect($url);exit; } if(current_user_can('manage_s4h') && !empty($_GET['s4h_reg_action']) && !empty($_GET['registration_id'])){ $rid=absint($_GET['registration_id']); check_admin_referer('s4h_reg_'.$rid); global $wpdb; $t=S4H_DB::tables(); $reg=$wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['event_registrations']} WHERE id=%d",$rid)); if($reg){$status=in_array($_GET['s4h_reg_action'],array('attended','no_show','cancelled','registered'),true)?sanitize_key($_GET['s4h_reg_action']):'registered';S4H_DB::set_event_attendance($reg->event_id,$reg->talent_id,$status);S4H_DB::audit(get_current_user_id(),'event_'.$status,'event_registration',$rid,$reg->talent_id,array('event_id'=>$reg->event_id));} wp_safe_redirect(admin_url('admin.php?page=s4h-event-registrations'));exit; } if(current_user_can('manage_s4h') && !empty($_GET['s4h_action']) && $_GET['s4h_action']==='test_ghl'){ check_admin_referer('s4h_test_ghl'); $res=S4H_GHL_Connector::test_connection(); set_transient('s4h_ghl_test_'.get_current_user_id(),is_wp_error($res)?array('ok'=>0,'msg'=>$res->get_error_message()):array('ok'=>1,'msg'=>'Connected to HighLevel successfully via Opportunities API.'),60); wp_safe_redirect(admin_url('admin.php?page=s4h-settings'));exit; } if(current_user_can('manage_s4h') && !empty($_GET['s4h_action']) && $_GET['s4h_action']==='fetch_ghl_pipeline'){ check_admin_referer('s4h_fetch_ghl_pipeline'); $res=S4H_GHL_Connector::auto_map_pipeline(); if(is_wp_error($res)) $notice=array('ok'=>0,'msg'=>$res->get_error_message()); else { $msg='Mapped pipeline “'.$res['pipeline_name'].'” and '.count($res['matched']).' stage(s).'; if($res['missing'])$msg.=' Missing exact-name stages: '.implode(', ',$res['missing']).'.'; $notice=array('ok'=>1,'msg'=>$msg); } set_transient('s4h_ghl_test_'.get_current_user_id(),$notice,60); wp_safe_redirect(admin_url('admin.php?page=s4h-settings'));exit; } } public static function dashboard(){ if(!current_user_can('s4h_access') && !current_user_can('manage_s4h'))return; if(!current_user_can('manage_s4h') && !current_user_can('s4h_control_tower') && !current_user_can('s4h_finance')){ $m=class_exists('S4H_Accountability')?S4H_Accountability::user_metrics(get_current_user_id()):array('overdue'=>0,'stale_actions'=>0,'stale_journeys'=>0,'score'=>100); echo '<div class="wrap s4h-admin"><h1>STAR4Hire Team Home</h1><p>Your workspace shows only the functions assigned to your role. Company-wide commercial metrics remain restricted.</p><div class="s4h-admin-cards"><div><strong>'.esc_html($m['overdue']).'</strong><span>My Overdue Actions</span></div><div><strong>'.esc_html($m['stale_actions']).'</strong><span>My Stale Actions</span></div><div><strong>'.esc_html($m['stale_journeys']).'</strong><span>My Stale Journeys</span></div><div><strong>'.esc_html($m['score']).'%</strong><span>My Update Compliance</span></div></div><p>'; if(current_user_can('s4h_journey_update'))echo '<a class="button button-primary" href="'.esc_url(admin_url('admin.php?page=s4h-journeys')).'">JourneyOS</a> '; if(current_user_can('s4h_success_view'))echo '<a class="button" href="'.esc_url(admin_url('admin.php?page=s4h-participant-success')).'">Participant Success</a> '; if(current_user_can('s4h_counselor'))echo '<a class="button" href="'.esc_url(get_permalink(absint(get_option('s4h_counselor_page_id')))).'">Counselor Workspace</a>'; echo '</p></div>';return; } global $wpdb; $t=S4H_DB::tables(); $talents=(int)$wpdb->get_var("SELECT COUNT(*) FROM {$t['talents']}"); $assess=(int)$wpdb->get_var("SELECT COUNT(*) FROM {$t['assessments']}"); $english=(int)$wpdb->get_var("SELECT COUNT(*) FROM {$t['english_attempts']} WHERE status='completed'"); $regs=(int)$wpdb->get_var("SELECT COUNT(*) FROM {$t['event_registrations']}"); $won=(int)$wpdb->get_var("SELECT COUNT(*) FROM {$t['sales_opportunities']} WHERE status='won'"); $revenue=(float)$wpdb->get_var("SELECT COALESCE(SUM(collected_value),0) FROM {$t['revenue_events']}"); echo '<div class="wrap s4h-admin"><h1>STAR4Hire Core</h1><p class="s4h-lead">Career Intelligence → GHL Sales → Revenue Attribution.</p><div class="s4h-admin-cards">'; foreach(array(array($talents,'Talents'),array($assess,'Assessments'),array($english,'English Tests'),array($regs,'Event Registrations'),array($won,'Closed Won'),array('Rp '.number_format($revenue,0,',','.'),'Collected Revenue')) as $m)echo '<div><strong>'.esc_html($m[0]).'</strong><span>'.esc_html($m[1]).'</span></div>'; echo '</div><h2>Operating loop</h2><p>Traffic → Talent ID → Assessment / English → Quick Register → Webinar → Career Day → Counselor → GHL Sales → Revenue.</p></div>'; } public static function talents(){if(!current_user_can('manage_s4h'))return;if(!empty($_GET['talent'])){S4H_Quality_Intelligence::render_talent_profile(sanitize_text_field(wp_unslash($_GET['talent'])));return;}global $wpdb;$t=S4H_DB::tables();$page=max(1,absint($_GET['paged']??1));$per=30;$off=($page-1)*$per;$rows=$wpdb->get_results($wpdb->prepare("SELECT * FROM {$t['talents']} ORDER BY id DESC LIMIT %d OFFSET %d",$per,$off));$total=(int)$wpdb->get_var("SELECT COUNT(*) FROM {$t['talents']}");$dups=S4H_DB::duplicate_phone_groups(20);echo '<div class="wrap"><h1>Talents</h1>';if($dups){echo '<div class="notice notice-warning"><p><strong>Duplicate review needed:</strong> WhatsApp yang sama terdapat pada lebih dari satu Talent ID. Existing duplicate tidak di-merge otomatis agar history aman.</p><ul>';foreach($dups as $d)echo '<li><code>'.esc_html($d->whatsapp_normalized).'</code> — '.esc_html($d->talent_ids).' — '.esc_html($d->names).'</li>';echo '</ul></div>';}echo '<table class="widefat striped"><thead><tr><th>Talent ID</th><th>Name / Profile</th><th>WhatsApp</th><th>Action</th><th>Email</th><th>First Source</th><th>GHL</th><th>Updated</th></tr></thead><tbody>';foreach($rows as $r){$profile=S4H_Quality_Intelligence::talent_profile_url($r->talent_id);echo '<tr><td><strong><a href="'.esc_url($profile).'">'.esc_html($r->talent_id).'</a></strong></td><td><a href="'.esc_url($profile).'"><strong>'.esc_html($r->full_name).'</strong></a><br><small>Open profile</small></td><td>'.esc_html($r->whatsapp?:'—').'</td><td>'.S4H_Quality_Intelligence::whatsapp_button($r->whatsapp,$r->full_name).'</td><td>'.esc_html($r->email).'</td><td>'.esc_html($r->first_source_type).'</td><td>'.esc_html($r->ghl_contact_id?'Linked':'—').'</td><td>'.esc_html($r->updated_at).'</td></tr>';}echo '</tbody></table>';self::pagination($page,$per,$total,'s4h-talents');echo '</div>';} public static function assessments(){if(!current_user_can('manage_s4h'))return;global $wpdb;$t=S4H_DB::tables();$page=max(1,absint($_GET['paged']??1));$per=30;$off=($page-1)*$per;$rows=$wpdb->get_results($wpdb->prepare("SELECT a.*,t.full_name,t.latest_source_type FROM {$t['assessments']} a JOIN {$t['talents']} t ON a.talent_db_id=t.id ORDER BY a.id DESC LIMIT %d OFFSET %d",$per,$off));$total=(int)$wpdb->get_var("SELECT COUNT(*) FROM {$t['assessments']}");echo '<div class="wrap"><h1>Assessments</h1><table class="widefat striped"><thead><tr><th>Date</th><th>Talent</th><th>Name</th><th>Score</th><th>Band</th><th>Source</th><th>Snapshot</th></tr></thead><tbody>';foreach($rows as $r){$url=add_query_arg('token',$r->public_token,S4H_Assessment::snapshot_url());echo '<tr><td>'.esc_html($r->created_at).'</td><td>'.esc_html($r->talent_id).'</td><td>'.esc_html($r->full_name).'</td><td><strong>'.esc_html($r->score).'</strong></td><td>'.esc_html($r->score_band).'</td><td>'.esc_html($r->latest_source_type).'</td><td><a href="'.esc_url($url).'" target="_blank" rel="noopener">View</a></td></tr>';}echo '</tbody></table>';self::pagination($page,$per,$total,'s4h-assessments');echo '</div>';} public static function english_tests(){if(!current_user_can('manage_s4h'))return;global $wpdb;$tt=S4H_DB::tables();$page=max(1,absint($_GET['paged']??1));$per=30;$off=($page-1)*$per;$rows=$wpdb->get_results($wpdb->prepare("SELECT e.*,t.full_name FROM {$tt['english_attempts']} e LEFT JOIN {$tt['talents']} t ON e.talent_db_id=t.id ORDER BY e.id DESC LIMIT %d OFFSET %d",$per,$off));$total=(int)$wpdb->get_var("SELECT COUNT(*) FROM {$tt['english_attempts']}");echo '<div class="wrap"><h1>English Tests</h1><table class="widefat striped"><thead><tr><th>Date</th><th>Talent</th><th>Name</th><th>Level</th><th>Readiness</th><th>Status</th></tr></thead><tbody>';foreach($rows as $r)echo '<tr><td>'.esc_html($r->started_at).'</td><td>'.esc_html($r->talent_id?:'Not claimed').'</td><td>'.esc_html($r->full_name?:'Anonymous').'</td><td><strong>'.esc_html($r->result_level?:'—').'</strong></td><td>'.esc_html($r->readiness_score!==null?$r->readiness_score.'/100':'—').'</td><td>'.esc_html($r->status).'</td></tr>';echo '</tbody></table>';self::pagination($page,$per,$total,'s4h-english-tests');echo '</div>';} public static function event_registrations(){if(!current_user_can('manage_s4h'))return;global $wpdb;$t=S4H_DB::tables();$rows=$wpdb->get_results("SELECT r.*,t.full_name,t.whatsapp,t.email,t.ghl_contact_id AS talent_ghl_contact_id,p.post_title event_title FROM {$t['event_registrations']} r JOIN {$t['talents']} t ON r.talent_db_id=t.id LEFT JOIN {$wpdb->posts} p ON r.event_id=p.ID ORDER BY r.id DESC LIMIT 300");echo '<div class="wrap"><h1>Event Registrations</h1><p>STAR4Hire Core remains the contact source of truth. GHL is the nurturing/sales workspace. Use the profile and WhatsApp actions below for fast follow-up.</p><table class="widefat striped"><thead><tr><th>Date</th><th>Event</th><th>Talent</th><th>Name / Profile</th><th>WhatsApp</th><th>Action</th><th>Email</th><th>Status</th><th>Source</th><th>GHL</th><th>Attendance</th></tr></thead><tbody>';foreach($rows as $r){$base=admin_url('admin.php?page=s4h-event-registrations®istration_id='.$r->id);$att=wp_nonce_url($base.'&s4h_reg_action=attended','s4h_reg_'.$r->id);$no=wp_nonce_url($base.'&s4h_reg_action=no_show','s4h_reg_'.$r->id);$profile=S4H_Quality_Intelligence::talent_profile_url($r->talent_id);echo '<tr><td>'.esc_html($r->registered_at).'</td><td>'.esc_html($r->event_title).'</td><td><a href="'.esc_url($profile).'"><code>'.esc_html($r->talent_id).'</code></a></td><td><a href="'.esc_url($profile).'"><strong>'.esc_html($r->full_name).'</strong></a></td><td>'.esc_html($r->whatsapp?:'—').'</td><td>'.S4H_Quality_Intelligence::whatsapp_button($r->whatsapp,$r->full_name).'</td><td>'.esc_html($r->email?:'—').'</td><td>'.esc_html($r->status).'</td><td>'.esc_html($r->source_type).'</td><td>'.esc_html(($r->ghl_contact_id||$r->talent_ghl_contact_id)?'Synced':'Pending').'</td><td><a href="'.esc_url($att).'">Attended</a> · <a href="'.esc_url($no).'">No-show</a></td></tr>';}echo '</tbody></table></div>';} public static function sales_revenue(){ if(!current_user_can('s4h_finance') && !current_user_can('manage_s4h'))return;global $wpdb;$t=S4H_DB::tables(); $pipeline=(float)$wpdb->get_var("SELECT COALESCE(SUM(monetary_value),0) FROM {$t['sales_opportunities']} WHERE status='open'"); $open=(int)$wpdb->get_var("SELECT COUNT(*) FROM {$t['sales_opportunities']} WHERE status='open'"); $won=(int)$wpdb->get_var("SELECT COUNT(*) FROM {$t['sales_opportunities']} WHERE deal_type='program' AND status='won'"); $contracted=(float)$wpdb->get_var("SELECT COALESCE(SUM(monetary_value),0) FROM {$t['sales_opportunities']} WHERE deal_type='program' AND status='won'"); $rev=(float)$wpdb->get_var("SELECT COALESCE(SUM(collected_value),0) FROM {$t['revenue_events']}"); $products=$wpdb->get_results("SELECT COALESCE(NULLIF(product,''),product_key) product_name,COUNT(*) deals,SUM(CASE WHEN status='won' THEN 1 ELSE 0 END) won,SUM(CASE WHEN status='won' THEN monetary_value ELSE 0 END) contracted FROM {$t['sales_opportunities']} WHERE deal_type='program' GROUP BY product_key,product ORDER BY contracted DESC,deals DESC"); $deal_sources=$wpdb->get_results("SELECT COALESCE(NULLIF(deal_source_type,''),'unknown') deal_source,COUNT(*) deals,SUM(CASE WHEN status='won' THEN monetary_value ELSE 0 END) contracted FROM {$t['sales_opportunities']} WHERE deal_type='program' GROUP BY deal_source_type ORDER BY contracted DESC,deals DESC"); $opps=$wpdb->get_results("SELECT s.*,t.full_name FROM {$t['sales_opportunities']} s JOIN {$t['talents']} t ON s.talent_db_id=t.id ORDER BY s.updated_at DESC LIMIT 150"); echo '<div class="wrap s4h-admin"><h1>Sales & Revenue</h1><p class="s4h-lead"><strong>One Talent ID / one GHL Contact, many product deals.</strong> Each paid program has its own Deal ID, enrollment, contract value, deal source and payment history.</p><div class="s4h-admin-cards">'; foreach(array(array($open,'Open Deals / Lead Journeys'),array('Rp '.number_format($pipeline,0,',','.'),'Open Pipeline Value'),array($won,'Program Deals Won'),array('Rp '.number_format($contracted,0,',','.'),'Contracted Revenue'),array('Rp '.number_format($rev,0,',','.'),'Cash Collected')) as $m)echo '<div><strong>'.esc_html($m[0]).'</strong><span>'.esc_html($m[1]).'</span></div>'; echo '</div><h2>Revenue by Product</h2><table class="widefat striped"><thead><tr><th>Product</th><th>Deals</th><th>Won</th><th>Contracted</th></tr></thead><tbody>'; foreach($products as $r)echo '<tr><td>'.esc_html($r->product_name?:'Unspecified').'</td><td>'.esc_html($r->deals).'</td><td>'.esc_html($r->won).'</td><td>Rp '.number_format((float)$r->contracted,0,',','.').'</td></tr>';if(!$products)echo '<tr><td colspan="4">No program deals yet.</td></tr>'; echo '</tbody></table><h2>Deal Source Attribution</h2><p>Original acquisition source remains on the Talent record. This table shows what actually generated each transaction.</p><table class="widefat striped"><thead><tr><th>Deal Source</th><th>Deals</th><th>Contracted</th></tr></thead><tbody>'; foreach($deal_sources as $r)echo '<tr><td>'.esc_html($r->deal_source).'</td><td>'.esc_html($r->deals).'</td><td>Rp '.number_format((float)$r->contracted,0,',','.').'</td></tr>';if(!$deal_sources)echo '<tr><td colspan="3">No deal-source data yet.</td></tr>'; echo '</tbody></table><h2>Recent Deals</h2><table class="widefat striped"><thead><tr><th>Deal</th><th>Enrollment</th><th>Talent</th><th>Product</th><th>Type</th><th>Status</th><th>Contract</th><th>Collected</th><th>Deal Source</th><th>GHL</th><th>Updated</th></tr></thead><tbody>'; foreach($opps as $o){$profile=class_exists('S4H_Quality_Intelligence')?S4H_Quality_Intelligence::talent_profile_url($o->talent_id):admin_url('admin.php?page=s4h-talents');$col=$o->deal_id?S4H_DB::collected_for_deal($o->deal_id):0;echo '<tr><td><code>'.esc_html($o->deal_id?:'legacy').'</code></td><td>'.esc_html($o->enrollment_id?:'—').'</td><td><a href="'.esc_url($profile).'"><strong>'.esc_html($o->full_name).'</strong></a><br><small>'.esc_html($o->talent_id).'</small></td><td>'.esc_html($o->product?:'—').'</td><td>'.esc_html(str_replace('_',' ',$o->deal_type)).'</td><td>'.esc_html($o->status).'</td><td>'.esc_html($o->currency.' '.number_format((float)$o->monetary_value,0,',','.')).'</td><td>'.esc_html($o->currency.' '.number_format($col,0,',','.')).'</td><td>'.esc_html($o->deal_source_type?:'—').'</td><td>'.esc_html($o->ghl_opportunity_id?'Linked':'Local').'</td><td>'.esc_html($o->updated_at).'</td></tr>';} if(!$opps)echo '<tr><td colspan="11">No deals yet.</td></tr>';echo '</tbody></table></div>'; } public static function journeys(){ if(!current_user_can('s4h_journey_update') && !current_user_can('manage_s4h'))return; $templates=S4H_Journey::templates();$created=get_transient('s4h_journey_created_'.get_current_user_id()); if($created){delete_transient('s4h_journey_created_'.get_current_user_id());echo '<div class="notice notice-success"><p><strong>'.esc_html($created['journey_id']).' created.</strong></p><p>Participant: <code>'.esc_html($created['participant']).'</code><br>Parent: <code>'.esc_html($created['parent']).'</code></p></div>';} $users=get_users(array('capability'=>'s4h_journey_update','orderby'=>'display_name')); if(!empty($_GET['journey'])){ $journey=S4H_DB::get_journey_by_journey_id(sanitize_text_field(wp_unslash($_GET['journey'])));if(!$journey){echo '<div class="wrap"><h1>Journey not found.</h1></div>';return;} $talent=S4H_DB::get_talent($journey->talent_db_id);$events=S4H_DB::journey_events($journey->id,'internal');$template=S4H_Journey::template($journey->template_key); echo '<div class="wrap"><p><a href="'.esc_url(admin_url('admin.php?page=s4h-journeys&talent='.rawurlencode($journey->talent_id))).'">← All journeys for '.esc_html($talent?$talent->full_name:$journey->talent_id).'</a></p><h1>'.esc_html($journey->journey_id).' · '.esc_html($journey->program_name).'</h1><p><strong>'.esc_html($talent?$talent->full_name:$journey->talent_id).'</strong> · '.esc_html($journey->talent_id).' · '.esc_html($journey->progress_pct).'% · '.esc_html(str_replace('_',' ',$journey->health_status)).'</p>'; echo '<p><a class="button" target="_blank" href="'.esc_url(S4H_Journey::participant_url($journey)).'">Open MySTAR4Hire View</a> <a class="button" target="_blank" href="'.esc_url(S4H_Journey::parent_url($journey)).'">Open Parent View</a></p>'; echo '<div style="display:grid;grid-template-columns:minmax(0,1fr) minmax(320px,430px);gap:24px;align-items:start"><div><h2>Timeline</h2><table class="widefat striped"><thead><tr><th>Date</th><th>Stage</th><th>Status</th><th>Visibility</th><th>Public update</th><th>Internal note</th><th>Updated by</th></tr></thead><tbody>'; foreach(array_reverse($events) as $e){$actor=$e->created_by?get_userdata($e->created_by):null;echo '<tr><td>'.esc_html($e->occurred_at).'</td><td>'.esc_html($e->stage_name).'</td><td>'.esc_html($e->stage_status).'</td><td>'.esc_html($e->visibility).'</td><td>'.esc_html($e->public_note).'</td><td>'.esc_html($e->internal_note).'</td><td>'.esc_html($actor?$actor->display_name:'System').'</td></tr>';} if(!$events)echo '<tr><td colspan="7">No updates yet.</td></tr>';echo '</tbody></table></div>'; echo '<div style="background:#fff;border:1px solid #ddd;padding:20px;border-radius:12px"><h2>Update Journey</h2><p class="description">Every saved update is audited. Assigned owners—including Career Architects—are included in SLA/stale compliance.</p><form method="post">';wp_nonce_field('s4h_update_journey_'.$journey->journey_id);echo '<input type="hidden" name="s4h_admin_action" value="update_journey"><input type="hidden" name="journey_id" value="'.esc_attr($journey->journey_id).'">'; echo '<p><label><strong>Stage</strong><br><select name="stage_key" style="width:100%">';foreach($template['stages'] as $st)echo '<option value="'.esc_attr($st['key']).'" '.selected($st['key'],$journey->current_stage_key,false).'>'.esc_html($st['label']).'</option>';echo '</select></label></p>'; echo '<p><label><strong>Stage status</strong><br><select name="stage_status" style="width:100%"><option value="in_progress">In progress</option><option value="waiting">Waiting</option><option value="blocked">Blocked</option><option value="completed">Completed / advance</option></select></label></p>'; echo '<p><label><strong>Health</strong><br><select name="health_status" style="width:100%">';foreach(array('on_track'=>'On Track','attention'=>'Needs Attention','waiting'=>'Waiting','blocked'=>'Blocked','overdue'=>'Overdue') as $k=>$v)echo '<option value="'.esc_attr($k).'" '.selected($k,$journey->health_status,false).'>'.esc_html($v).'</option>';echo '</select></label></p>'; echo '<p><label><strong>Visible to</strong><br><select name="visibility" style="width:100%"><option value="parent">Participant + Parent</option><option value="participant">Participant only</option><option value="internal">Internal only</option></select></label></p>'; echo '<p><label><strong>Public update</strong><br><textarea name="public_note" rows="3" style="width:100%" placeholder="What happened?"></textarea></label></p><p><label><strong>Next action</strong><br><textarea name="next_action" rows="2" style="width:100%"></textarea></label></p><p><label><strong>Action required from participant</strong><br><textarea name="action_required" rows="2" style="width:100%"></textarea></label></p><p><label><strong>Internal note</strong><br><textarea name="internal_note" rows="3" style="width:100%"></textarea></label></p>'; echo '<p><label><strong>Owner</strong><br><select name="owner_user_id" style="width:100%"><option value="0">Unassigned</option>';foreach($users as $u)echo '<option value="'.esc_attr($u->ID).'" '.selected($u->ID,$journey->owner_user_id,false).'>'.esc_html($u->display_name).'</option>';echo '</select></label></p><p><label><strong>Due at</strong><br><input type="datetime-local" name="due_at" style="width:100%"></label></p><p><button class="button button-primary button-large">Save Journey Update</button></p></form></div></div></div>';return; } if(!empty($_GET['talent'])){ $talent=S4H_DB::get_talent_by_talent_id(sanitize_text_field(wp_unslash($_GET['talent'])));if(!$talent){echo '<div class="wrap"><h1>Talent not found.</h1></div>';return;} $journeys=S4H_DB::journeys_for_talent($talent->id,100); echo '<div class="wrap"><p><a href="'.esc_url(admin_url('admin.php?page=s4h-journeys')).'">← Journey Control Center</a></p><h1>'.esc_html($talent->full_name).'</h1><p><code>'.esc_html($talent->talent_id).'</code> · '.esc_html($talent->whatsapp?:'No WhatsApp').' · <strong>'.esc_html(count($journeys)).' journey(s)</strong></p>'; echo '<table class="widefat striped"><thead><tr><th>Journey</th><th>Program / Career Path</th><th>Stage</th><th>Progress</th><th>Health</th><th>Owner</th><th>Last Update</th></tr></thead><tbody>'; foreach($journeys as $j){$owner=$j->owner_user_id?get_userdata($j->owner_user_id):null;$age=(current_time('timestamp')-strtotime($j->updated_at))/DAY_IN_SECONDS;$cls=$age>S4H_Accountability::stale_days()?'s4h-compliance-bad':'s4h-compliance-ok';echo '<tr><td><a href="'.esc_url(admin_url('admin.php?page=s4h-journeys&journey='.$j->journey_id)).'"><strong>'.esc_html($j->journey_id).'</strong></a></td><td>'.esc_html($j->program_name).'<br><small>'.esc_html(strpos($j->program_code,'CAREER-GPS-')===0?'Career Journey':'Program / Operational Journey').'</small></td><td>'.esc_html($j->current_stage_name).'</td><td>'.esc_html($j->progress_pct).'%</td><td>'.esc_html(ucwords(str_replace('_',' ',$j->health_status))).'</td><td>'.esc_html($owner?$owner->display_name:'Unassigned').'</td><td class="'.esc_attr($cls).'">'.esc_html($j->updated_at).'</td></tr>';} echo '</tbody></table></div>';return; } $m=S4H_DB::journey_metrics();$q=sanitize_text_field(wp_unslash($_GET['q']??''));$people=S4H_DB::journey_people($q,150); echo '<div class="wrap"><h1>JourneyOS · People-First Control Center</h1><p>One person can hold multiple Career and Program journeys. Search the person first; Talent IDs remain database keys, not something staff should memorize.</p><div class="s4h-admin-metrics">';foreach(array(array($m['active'],'Active Journeys'),array($m['attention'],'Need Attention'),array($m['completed'],'Completed'),array($m['total'],'Total Journeys')) as $x)echo '<div><strong>'.esc_html($x[0]).'</strong><span>'.esc_html($x[1]).'</span></div>';echo '</div>'; echo '<form method="get" style="background:#fff;padding:14px;margin:16px 0"><input type="hidden" name="page" value="s4h-journeys"><label><strong>Search people</strong> <input type="search" class="regular-text" name="q" value="'.esc_attr($q).'" placeholder="Name / WhatsApp / email / Talent ID"></label> <button class="button">Search</button> '.($q?'<a class="button" href="'.esc_url(admin_url('admin.php?page=s4h-journeys')).'">Clear</a>':'').'</form>'; echo '<h2>People & Journeys</h2><table class="widefat striped"><thead><tr><th>Talent</th><th>Journeys</th><th>Active</th><th>Risk</th><th>Journey Snapshot</th><th>Last Update</th></tr></thead><tbody>'; foreach($people as $r){$snap='';foreach(array_slice(explode(';;',(string)$r->journeys),0,4) as $j){$parts=explode('|',$j);if(count($parts)>=5)$snap.='<code>'.esc_html($parts[1]).' · '.esc_html($parts[4]).'%</code> ';}echo '<tr><td><a href="'.esc_url(admin_url('admin.php?page=s4h-journeys&talent='.rawurlencode($r->talent_id))).'"><strong>'.esc_html($r->full_name).'</strong></a><br><small>'.esc_html($r->talent_id).' · '.esc_html($r->whatsapp).'</small></td><td>'.esc_html($r->journey_count).'</td><td>'.esc_html($r->active_count).'</td><td>'.esc_html($r->risk_count).'</td><td class="s4h-journey-person-programs">'.$snap.'</td><td>'.esc_html($r->last_update).'</td></tr>';} if(!$people)echo '<tr><td colspan="6">No matching journeys.</td></tr>';echo '</tbody></table>'; if(current_user_can('s4h_career_architect')||current_user_can('manage_s4h')){ echo '<h2>Admin Exception · Create Manual Journey</h2><p class="description">Normal Program Journeys should be created from Participant Success enrollment. Normal Career Journeys should be triggered from an approved Career GPS. Use this manual form only for exceptions.</p><form method="post" style="background:#fff;padding:18px;max-width:1050px;margin:18px 0">';wp_nonce_field('s4h_create_journey');echo '<input type="hidden" name="s4h_admin_action" value="create_journey"><div class="s4h-talent-search" data-s4h-talent-search><label><strong>Search Talent</strong><br><input type="search" data-s4h-talent-input autocomplete="off" placeholder="Type name / WhatsApp / Talent ID"><input type="hidden" name="talent_id" value=""><div class="s4h-talent-search-results" data-s4h-talent-results hidden></div></label></div> <label>Template <select name="template_key">';foreach($templates as $k=>$tpl)echo '<option value="'.esc_attr($k).'">'.esc_html($tpl['label']).'</option>';echo '</select></label> <label>Custom Name <input name="program_name" placeholder="Optional"></label><p><label>Owner <select name="owner_user_id"><option value="0">Unassigned</option>';foreach($users as $u)echo '<option value="'.esc_attr($u->ID).'">'.esc_html($u->display_name).'</option>';echo '</select></label> <label>Target Date <input type="date" name="target_date"></label> <button class="button button-primary">Create Manual Journey</button></p></form>'; } echo '</div>'; } public static function counselor_assignments(){if(!current_user_can('manage_s4h'))return;global $wpdb;$t=S4H_DB::tables();$counselors=get_users(array('capability'=>'s4h_counselor','orderby'=>'display_name'));$rows=$wpdb->get_results("SELECT a.*,t.full_name,u.display_name counselor FROM {$t['counselor_assignments']} a JOIN {$t['talents']} t ON a.talent_db_id=t.id LEFT JOIN {$wpdb->users} u ON a.counselor_user_id=u.ID ORDER BY a.assigned_at DESC LIMIT 200");echo '<div class="wrap"><h1>Counselor Assignments</h1><h2>Counselor Performance</h2><table class="widefat striped" style="max-width:1000px;margin-bottom:24px"><thead><tr><th>Counselor</th><th>Assigned</th><th>Completion</th><th>QA Approved</th><th>Avg Turnaround</th><th>Review → Won</th></tr></thead><tbody>';foreach($counselors as $u){$m=S4H_DB::counselor_metrics($u->ID);echo '<tr><td>'.esc_html($u->display_name).'</td><td>'.esc_html($m['assigned']).'</td><td>'.esc_html($m['completion_rate']).'%</td><td>'.esc_html($m['approved_rate']).'%</td><td>'.esc_html($m['avg_turnaround_hours']).'h</td><td>'.esc_html($m['won_rate']).'%</td></tr>';}if(!$counselors)echo '<tr><td colspan="6">No counselor users yet.</td></tr>';echo '</tbody></table><form method="post" style="background:#fff;padding:18px;max-width:800px;margin:18px 0">';wp_nonce_field('s4h_assign_counselor');echo '<input type="hidden" name="s4h_admin_action" value="assign_counselor"><p><label>Talent ID <input required name="talent_id" placeholder="S4H-26-000001"></label> <label>Counselor <select required name="counselor_user_id"><option value="">Choose...</option>';foreach($counselors as $u)echo '<option value="'.esc_attr($u->ID).'">'.esc_html($u->display_name).'</option>';echo '</select></label> <button class="button button-primary">Assign</button></p></form><p>Counselors login at <code>'.esc_html(get_permalink(absint(get_option('s4h_counselor_page_id')))).'</code></p><table class="widefat striped"><thead><tr><th>Assigned</th><th>Talent</th><th>Name</th><th>Counselor</th><th>Status</th></tr></thead><tbody>';foreach($rows as $r)echo '<tr><td>'.esc_html($r->assigned_at).'</td><td>'.esc_html($r->talent_id).'</td><td>'.esc_html($r->full_name).'</td><td>'.esc_html($r->counselor).'</td><td>'.esc_html($r->status).'</td></tr>';echo '</tbody></table></div>';} public static function counselor_reviews(){ if(!current_user_can('s4h_career_architect') && !current_user_can('manage_s4h'))return; global $wpdb;$t=S4H_DB::tables();$rows=$wpdb->get_results("SELECT r.*,t.full_name,u.display_name counselor FROM {$t['counselor_reviews']} r JOIN {$t['talents']} t ON r.talent_db_id=t.id LEFT JOIN {$wpdb->users} u ON r.counselor_user_id=u.ID ORDER BY r.id DESC LIMIT 200"); echo '<div class="wrap"><h1>Counselor Reviews · Career GPS Governance</h1><p>Approve only validates the recommendation. <strong>Approve + Start Career Journey</strong> is the Career Architect trigger that turns an approved Career GPS into an active JourneyOS journey. The triggering architect becomes the initial journey owner and is therefore included in SLA/compliance.</p>'; if(!empty($_GET['career_journey']))echo '<div class="notice notice-success"><p>Career GPS approved and JourneyOS journey <strong>'.esc_html(sanitize_text_field(wp_unslash($_GET['career_journey']))).'</strong> started.</p></div>'; echo '<table class="widefat striped"><thead><tr><th>Date</th><th>Talent</th><th>Counselor</th><th>Version</th><th>Primary Pathway</th><th>Readiness</th><th>QA</th><th>Actions</th></tr></thead><tbody>'; foreach($rows as $r){ $approve=wp_nonce_url(admin_url('admin.php?page=s4h-counselor-reviews&s4h_review_action=approve&review_id='.$r->id),'s4h_review_'.$r->id); $trigger=wp_nonce_url(admin_url('admin.php?page=s4h-counselor-reviews&s4h_review_action=approve_create_journey&review_id='.$r->id),'s4h_review_'.$r->id); $reject=wp_nonce_url(admin_url('admin.php?page=s4h-counselor-reviews&s4h_review_action=reject&review_id='.$r->id),'s4h_review_'.$r->id); echo '<tr><td>'.esc_html($r->created_at).'</td><td>'.esc_html($r->talent_id).' — '.esc_html($r->full_name).'</td><td>'.esc_html($r->counselor).'</td><td>'.esc_html($r->version).'</td><td>'.esc_html($r->primary_pathway).'</td><td>'.esc_html($r->readiness_status).'</td><td>'.esc_html($r->approval_status).'</td><td>'; if($r->approval_status==='pending')echo '<a href="'.esc_url($approve).'">Approve</a> · <a class="button button-small button-primary" href="'.esc_url($trigger).'">Approve + Start Career Journey</a> · <a href="'.esc_url($reject).'">Reject</a>'; else echo '<span>'.esc_html(ucfirst($r->approval_status)).'</span> · <a class="button button-small" href="'.esc_url($trigger).'">Start / Open Career Journey</a>'; echo '</td></tr>'; } if(!$rows)echo '<tr><td colspan="8">No counselor reviews yet.</td></tr>';echo '</tbody></table></div>'; } public static function audit_log(){if(!current_user_can('manage_s4h'))return;global $wpdb;$t=S4H_DB::tables();$rows=$wpdb->get_results("SELECT a.*,u.display_name actor FROM {$t['audit_log']} a LEFT JOIN {$wpdb->users} u ON a.actor_user_id=u.ID ORDER BY a.id DESC LIMIT 300");echo '<div class="wrap"><h1>Audit Log</h1><p>Critical STAR4Hire Core actions are retained here for review. Counselor recommendations are versioned separately.</p><table class="widefat striped"><thead><tr><th>Date</th><th>Actor</th><th>Action</th><th>Talent</th><th>Object</th><th>Context</th></tr></thead><tbody>';foreach($rows as $r){$ctx=json_decode($r->context,true);echo '<tr><td>'.esc_html($r->created_at).'</td><td>'.esc_html($r->actor?:($r->actor_user_id?'User '.$r->actor_user_id:'System')).'</td><td>'.esc_html($r->action).'</td><td>'.esc_html($r->talent_id).'</td><td>'.esc_html($r->object_type.' #'.$r->object_id).'</td><td><code>'.esc_html(wp_json_encode($ctx,JSON_UNESCAPED_UNICODE)).'</code></td></tr>';}if(!$rows)echo '<tr><td colspan="6">No audit records yet.</td></tr>';echo '</tbody></table></div>';} public static function english_questions(){if(!current_user_can('manage_s4h'))return;global $wpdb;$tt=S4H_DB::tables();$counts=$wpdb->get_results("SELECT skill,cefr_level,COUNT(*) qty FROM {$tt['english_questions']} WHERE active=1 GROUP BY skill,cefr_level ORDER BY skill,difficulty");echo '<div class="wrap"><h1>English Question Bank</h1><table class="widefat striped" style="max-width:760px"><thead><tr><th>Skill</th><th>CEFR band</th><th>Active items</th></tr></thead><tbody>';foreach($counts as $c)echo '<tr><td>'.esc_html(ucfirst($c->skill)).'</td><td>'.esc_html($c->cefr_level).'</td><td>'.esc_html($c->qty).'</td></tr>';echo '</tbody></table></div>';} private static function pagination($page,$per,$total,$slug){$pages=max(1,(int)ceil($total/$per));if($pages<=1)return;echo '<div class="tablenav"><div class="tablenav-pages">'.wp_kses_post(paginate_links(array('base'=>add_query_arg(array('page'=>$slug,'paged'=>'%#%'),admin_url('admin.php')),'format'=>'','current'=>$page,'total'=>$pages))).'</div></div>';} public static function settings_page(){if(!current_user_can('manage_s4h'))return;$test=get_transient('s4h_ghl_test_'.get_current_user_id());if($test){delete_transient('s4h_ghl_test_'.get_current_user_id());echo '<div class="notice '.($test['ok']?'notice-success':'notice-error').' is-dismissible"><p>'.esc_html($test['msg']).'</p></div>';}echo '<div class="wrap"><h1>STAR4Hire Core Settings</h1><form method="post" action="options.php">';settings_fields('s4h_core_settings');do_settings_sections('s4h-settings');submit_button();echo '</form><hr><p><a class="button" href="'.esc_url(wp_nonce_url(admin_url('admin.php?page=s4h-settings&s4h_action=test_ghl'),'s4h_test_ghl')).'">Test HighLevel Connection</a> <a class="button button-primary" href="'.esc_url(wp_nonce_url(admin_url('admin.php?page=s4h-settings&s4h_action=fetch_ghl_pipeline'),'s4h_fetch_ghl_pipeline')).'">Fetch & Map GHL Pipeline + Stages</a></p><p class="description">No Postman needed. The test now uses the Opportunities API, and the mapping button can discover the Global Career Sales Pipeline plus exact-name stages automatically.</p><h2>Revenue Webhook</h2><p>Configure a GHL workflow to POST opportunity/revenue updates to:</p><code>'.esc_html(rest_url('s4h/v1/ghl/webhook')).'</code><p>Send the secret in header <code>X-S4H-Webhook-Secret</code> or field <code>secret</code>.</p><h2>Installation</h2><p><code>'.esc_html(get_option('s4h_installation_uuid')).'</code> · mode <code>'.esc_html(get_option('s4h_installation_mode','hq')).'</code>. Partner Node remains reserved for a future release.</p></div>';} }
💾 Save Changes
❌ Cancel