<?php
declare(strict_types=1);

function db():PDO{static$pdo;global$config;if($pdo)return$pdo;$d=$config['db'];return$pdo=new PDO("mysql:host={$d['host']};port={$d['port']};dbname={$d['name']};charset=utf8mb4",$d['user'],$d['pass'],[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC,PDO::ATTR_EMULATE_PREPARES=>false]);}
function q(string$sql,array$p=[]):PDOStatement{$s=db()->prepare($sql);$s->execute($p);return$s;}
function one(string$sql,array$p=[]):?array{$r=q($sql,$p)->fetch();return$r?:null;}
function all(string$sql,array$p=[]):array{return q($sql,$p)->fetchAll();}
function e(?string$v):string{return htmlspecialchars($v??'',ENT_QUOTES|ENT_SUBSTITUTE,'UTF-8');}
function url(string$p=''):string{global$config;return rtrim($config['app_url'],'/').'/'.ltrim($p,'/');}
function setting(string$k,string$default=''):string{static$c=[];if(array_key_exists($k,$c))return$c[$k];$r=one('SELECT value FROM settings WHERE `key`=?',[$k]);return$c[$k]=$r['value']??$default;}
function csrf():string{return$_SESSION['csrf']??=$_SESSION['csrf']=bin2hex(random_bytes(24));}
function csrf_check():void{if(!hash_equals($_SESSION['csrf']??'',$_POST['csrf']??'')){http_response_code(419);exit('Session expired.');}}
function admin_user():?array{return$_SESSION['admin']??null;}
function require_admin():void{if(!admin_user()){header('Location: '.url(($GLOBALS['config']['admin_path']??'adminji').'/login'));exit;}}
function audit(string$action,array$details=[]):void{if(admin_user())q('INSERT INTO audit_log(admin_id,action,details,created_at) VALUES(?,?,?,NOW())',[admin_user()['id'],$action,json_encode($details,JSON_UNESCAPED_SLASHES)]);}
function meta_title(array$c=[]):string{return$c['meta_title']??$c['title']??setting('site_name','Exams.fit');}
function clean_html(string$h):string{$h=preg_replace('~<(script|style|iframe|object|embed|form|input|button)[^>]*>.*?</\1>~is','',$h);$h=strip_tags($h,'<p><h2><h3><h4><ul><ol><li><strong><em><blockquote><table><thead><tbody><tr><th><td><a><br><code><pre><figure><figcaption><img>');$h=preg_replace('/\s(?:on\w+|style|srcdoc)\s*=\s*(?:"[^"]*"|\'[^\']*\'|[^\s>]+)/i','',$h);$h=preg_replace_callback('/\s(href|src)\s*=\s*(["\'])(.*?)\2/i',static function($m){$u=html_entity_decode($m[3],ENT_QUOTES);$ok=$m[1]==='href'?preg_match('~^(?:https://|/|#)~i',$u):preg_match('~^/(?:uploads/media/)[a-zA-Z0-9_./-]+$~',$u);return$ok?' '.$m[1].'="'.e($u).'"':'';},$h);return$h;}
function ef_menu(string$location):string{$rows=all('SELECT label,url FROM menus WHERE location=? AND enabled=1 ORDER BY position',[$location]);$h='';foreach($rows as$r){$href=str_starts_with($r['url'],'http')?$r['url']:url($r['url']);$h.='<a href="'.e($href).'">'.e($r['label']).'</a>';}return$h;}

function ef_layout(string$body,array$meta=[]):void{
 $site=setting('site_name','Exams.fit');$raw=$meta['title']??$site;$title=!empty($meta['final_title'])||stripos($raw,$site)!==false?$raw:$raw.' | '.$site;$description=$meta['description']??setting('site_description','Free USA study resources and practice tests.');$canonical=$meta['canonical']??url();$noindex=!empty($meta['noindex']);$theme=setting('theme_color','#155eef');
 header('Content-Type:text/html; charset=utf-8');header('X-Content-Type-Options:nosniff');header('X-Frame-Options:SAMEORIGIN');header('Referrer-Policy:strict-origin-when-cross-origin');header('Permissions-Policy:camera=(), microphone=(), geolocation=()');
 echo'<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>'.e($title).'</title><meta name="description" content="'.e($description).'"><link rel="canonical" href="'.e($canonical).'"><meta name="robots" content="'.($noindex?'noindex,follow':'index,follow,max-image-preview:large').'"><meta name="theme-color" content="'.e($theme).'">';
 if(setting('google_verification'))echo'<meta name="google-site-verification" content="'.e(setting('google_verification')).'">';if(setting('bing_verification'))echo'<meta name="msvalidate.01" content="'.e(setting('bing_verification')).'">';
 echo'<link rel="alternate" type="application/rss+xml" title="Exams.fit Study Blog" href="'.url('blog/feed.xml').'">';if(!empty($meta['image']))echo'<meta property="og:image" content="'.e($meta['image']).'"><meta name="twitter:card" content="summary_large_image">';else echo'<meta name="twitter:card" content="summary">';
 echo'<meta property="og:type" content="'.e($meta['og_type']??'website').'"><meta property="og:title" content="'.e($title).'"><meta property="og:description" content="'.e($description).'"><meta property="og:url" content="'.e($canonical).'"><link rel="manifest" href="'.url('manifest.webmanifest').'"><link rel="alternate" type="application/rss+xml" href="'.url('feed.xml').'"><link rel="stylesheet" href="'.url('assets/app.css?v=311').'"><script>window.EF_BASE='.json_encode(rtrim(url(),'/'),JSON_HEX_TAG|JSON_HEX_AMP).';window.EF_CSRF='.json_encode(csrf(),JSON_HEX_TAG|JSON_HEX_AMP).';</script><script defer src="'.url('assets/app.js?v=311').'\"></script>'.setting('head_code').'</head><body><a class="skip" href="#main">Skip to content</a><header><a class="brand" href="'.url().'">Exams<span>.fit</span></a><form action="'.url('search').'" role="search"><input name="q" aria-label="Search Exams.fit" placeholder="Search lessons, exams, notes…"><button>Search</button></form><nav>'.ef_menu('header').'</nav><div class="tools"><button data-font="down" aria-label="Smaller text">A−</button><button data-font="up" aria-label="Larger text">A+</button><button data-theme aria-label="Change color mode">◐</button></div></header>'.ef_ad('header').'<main id="main">'.$body.'</main><footer><div><strong>Exams.fit</strong><p>Independent USA educational resource. Verify current requirements with official organizations.</p></div><nav>'.ef_menu('footer').'<a href="'.url('about').'">About</a><a href="'.url('editorial-policy').'">Editorial Policy</a><a href="'.url('privacy').'">Privacy</a><a href="'.url('contact').'">Contact</a><a href="'.url('sitemap_index.xml').'">Sitemap</a></nav></footer>'.ef_ad('footer').setting('footer_code').'</body></html>';
 echo '<script defer src="'.url('assets/app.js?v=311').'"></script>';
}
function ef_cards(array$items,string$layout='cards'):string{$h='<div class="cards layout-'.e($layout).'">';foreach($items as$c){$h.='<article class="card"><span class="type">'.e(str_replace('_',' ',$c['type'])).'</span><h3><a href="'.url($c['slug']).'">'.e($c['title']).'</a></h3><p>'.e($c['excerpt']).'</p></article>'; }return$h.'</div>';}
function ef_section_types(string$title):array{$map=['School Study'=>['school_grade','school_subject','school_lesson'],'College Study'=>['college_field','college_subject','college_course'],'Study Notes'=>['study_notes','study_chapter'],'Latest Study Blog'=>['blog_post'],'Popular Exams'=>['exam_profile','exam_syllabus','exam_pattern'],'Latest Exam Updates'=>['exam_update'],'College Admissions'=>['admission_guide','admission_update'],'Upcoming Deadlines'=>['exam_update','admission_update'],'Practice Tests'=>['practice_test','question_bank'],'Full Mock Exams'=>['mock_exam'],'Daily Quiz'=>['daily_quiz'],'Study PDFs'=>['pdf_material'],'Video Lessons'=>['youtube_video','video_playlist'],'Flashcards'=>['flashcard_set'],'Worksheets'=>['worksheet'],'Study Tools'=>['tool','formula_sheet'],'Latest Education News'=>['education_news'],'Education Jobs'=>['education_job']];return$map[$title]??[];}
function ef_home():void{$sections=all('SELECT * FROM sections WHERE enabled=1 ORDER BY position LIMIT 15');$body='<section class="hero"><p class="eyebrow">LEARN • PRACTICE • SUCCEED</p><h1>USA education resources built for focused learning</h1><p>Study school and college subjects, understand exam requirements, practice original questions and follow attributed education updates.</p><a class="button" href="'.url('search').'">Explore learning resources</a></section>';
 foreach($sections as$s){$limit=max(1,min(12,(int)$s['item_limit']));$types=json_decode($s['content_types']??'[]',true)?:ef_section_types($s['title']);$where="status='published' AND noindex=0 AND external_id NOT LIKE 'EF-SYSTEM-%'";$p=[];if((int)$s['category_id']){$where.=' AND category_id=?';$p[]=(int)$s['category_id'];}if($types){$where.=' AND type IN('.implode(',',array_fill(0,count($types),'?')).')';$p=[...$p,...$types];}$order=str_contains(strtolower($s['title']),'popular')?'views DESC,updated_at DESC':(str_contains(strtolower($s['title']),'updated')?'updated_at DESC':'featured DESC,published_at DESC');$items=all("SELECT type,slug,title,excerpt FROM content WHERE $where ORDER BY $order LIMIT $limit",$p);$body.='<section><div class="section-title"><h2>'.e($s['title']).'</h2></div>'.($items?ef_cards($items,$s['layout']):'<p class="empty">Approved resources will appear here after import.</p>').'</section>';}
 $schema=['@context'=>'https://schema.org','@graph'=>[['@type'=>'Organization','name'=>setting('site_name','Exams.fit'),'url'=>url()],['@type'=>'WebSite','name'=>setting('site_name','Exams.fit'),'url'=>url(),'potentialAction'=>['@type'=>'SearchAction','target'=>url('search?q={search_term_string}'),'query-input'=>'required name=search_term_string']]]];$body.='<script type="application/ld+json">'.json_encode($schema,JSON_UNESCAPED_SLASHES|JSON_HEX_TAG).'</script>';ef_layout($body,['title'=>'Free USA Study Resources','description'=>setting('site_description')]);}
function ef_collection(string$kind):void{$map=['school'=>['School study',['school_grade','school_subject','school_lesson']],'college'=>['College study',['college_field','college_subject','college_course']],'exams'=>['USA exams',['exam_profile','exam_syllabus','exam_pattern','registration_guide','eligibility_guide','fee_guide']],'practice-tests'=>['Practice tests',['practice_test','mock_exam','question_bank','daily_quiz']]];if(!isset($map[$kind])){ef_error(404);return;}[$title,$types]=$map[$kind];$page=max(1,(int)($_GET['page']??1));$per=24;$ph=implode(',',array_fill(0,count($types),'?'));$total=(int)(one("SELECT COUNT(*) n FROM content WHERE status='published' AND type IN($ph)",$types)['n']??0);$items=all("SELECT type,slug,title,excerpt FROM content WHERE status='published' AND type IN($ph) ORDER BY featured DESC,published_at DESC LIMIT $per OFFSET ".(($page-1)*$per),$types);$body='<section><h1>'.e($title).'</h1>'.ef_cards($items).ef_pagination($page,(int)ceil($total/$per),$kind).'</section>';ef_layout($body,['title'=>$title,'description'=>'Browse approved '.strtolower($title).' resources for learners in the United States.','canonical'=>url($kind.($page>1?'?page='.$page:''))]);}
function ef_pagination(int$page,int$pages,string$path):string{if($pages<=1)return'';$join=str_contains($path,'?')?'&':'?';$h='<nav class="pagination" aria-label="Pagination">';if($page>1)$h.='<a href="'.e(url($path.$join.'page='.($page-1))).'">Previous</a>';for($i=max(1,$page-2);$i<=min($pages,$page+2);$i++)$h.='<a '.($i===$page?'aria-current="page"':'').' href="'.e(url($path.$join.'page='.$i)).'">'.$i.'</a>';if($page<$pages)$h.='<a href="'.e(url($path.$join.'page='.($page+1))).'">Next</a>';return$h.'</nav>';}

function ef_category(string$slug):void{$cat=one('SELECT * FROM categories WHERE slug=?',[$slug]);if(!$cat){ef_error(404);return;}if($cat['type']==='blog_category'){http_response_code(301);header('Location: '.url('blog/category/'.$cat['slug']));return;}if($cat['type']==='tool_category'){http_response_code(301);header('Location: '.url('tools/category/'.$cat['slug']));return;}$page=max(1,(int)($_GET['page']??1));$per=24;$total=(int)(one("SELECT COUNT(*) n FROM content WHERE category_id=? AND status='published' AND noindex=0",[$cat['id']])['n']??0);$items=all("SELECT type,slug,title,excerpt FROM content WHERE category_id=? AND status='published' AND noindex=0 ORDER BY featured DESC,published_at DESC LIMIT $per OFFSET ".(($page-1)*$per),[$cat['id']]);$body='<section><h1>'.e($cat['name']).'</h1><p class="lead">'.e($cat['description']).'</p>'.ef_cards($items).ef_pagination($page,(int)ceil($total/$per),'category/'.$cat['slug']).'</section>';ef_layout($body,['title'=>$cat['name'],'description'=>$cat['description']?:'Browse approved Exams.fit resources in '.$cat['name'].'.','canonical'=>url('category/'.$cat['slug'].($page>1?'?page='.$page:''))]);}

function ef_content(string$slug):void{$c=one("SELECT c.*,cat.name category FROM content c LEFT JOIN categories cat ON cat.id=c.category_id WHERE c.slug=? AND c.status='published'",[$slug]);if(!$c){http_response_code(404);ef_error(404);return;}q('UPDATE content SET views=views+1 WHERE id=?',[$c['id']]);$d=json_decode($c['data']??'{}',true)?:[];$body='<nav class="crumb"><a href="'.url().'">Home</a> / '.e($c['category']??str_replace('_',' ',$c['type'])).'</nav><article class="content"><span class="type">'.e(str_replace('_',' ',$c['type'])).'</span><h1>'.e($c['title']).'</h1><p class="lead">'.e($c['excerpt']).'</p><div class="byline">By '.e($c['author']?:'Exams.fit Editorial Team').' · Reviewed by '.e($c['reviewer']?:'Exams.fit Education Reviewer').' · Updated '.e(substr($c['reviewed_at']?:$c['updated_at'],0,10)).'</div>'.ef_ad('content_top',$c).'<div class="prose">'.$c['body'].'</div>';
 if($c['type']==='blog_post')$body.=ef_blog_extras($d);
 if($c['type']==='tool')$body.=ef_tool_widget($d);
 if($c['source_url'])$body.='<p class="source"><strong>Primary source:</strong> <a rel="nofollow noopener" href="'.e($c['source_url']).'">'.e(parse_url($c['source_url'],PHP_URL_HOST)?:$c['source_url']).'</a></p>';
 if(in_array($c['type'],['practice_test','mock_exam','daily_quiz','question_bank'],true))$body.='<div id="practice" data-slug="'.e($c['slug']).'"><button class="button" data-start-test>Start practice</button></div><script defer src="'.url('assets/practice.js?v=3').'"></script>';
 if(in_array($c['type'],['youtube_video','video_playlist'],true))$body.=ef_video_player($c,$d);
 if($c['type']==='pdf_material'&&!empty($d['file'])&&(!isset($d['embed'])||$d['embed']))$body.='<div class="pdfbox"><iframe loading="lazy" src="'.url('viewer.html?file='.rawurlencode($d['file']).'&print='.(int)!empty($d['print']).'&download='.(int)!empty($d['download'])).'" title="'.e($c['title']).'"></iframe></div>'.(!empty($d['related_notes'])?'<h2>Related notes</h2><ul>'.implode('',array_map(fn($x)=>'<li>'.e((string)$x).'</li>',$d['related_notes'])).'</ul>':'').(!empty($d['download'])?'<a class="button" data-event="pdf_download" href="'.url($d['file']).'" download>Download permitted PDF</a>':'');
 $related=all("SELECT DISTINCT x.type,x.slug,x.title,x.excerpt FROM content x LEFT JOIN content_relations r ON r.related_id=x.id AND r.content_id=? WHERE x.status='published' AND x.id<>? AND (r.content_id IS NOT NULL OR x.category_id=?) ORDER BY r.position,x.featured DESC,x.published_at DESC LIMIT 6",[$c['id'],$c['id'],$c['category_id']]);$body.=ef_ad('content_bottom',$c).($related?'<h2>Related resources</h2>'.ef_cards($related):'').'<section class="correction"><h2>Report an issue</h2><form data-report data-slug="'.e($c['slug']).'"><label>Issue type<select name="kind"><option value="correction">Content correction</option><option value="question">Question issue</option><option value="copyright">Copyright concern</option><option value="accessibility">Accessibility issue</option></select></label><label>Details<textarea name="message" minlength="10" maxlength="2000" required></textarea></label><button class="button">Send report</button><p role="status"></p></form></section></article>';
 foreach(ef_schemas($c,$d) as$schema)$body.='<script type="application/ld+json">'.json_encode($schema,JSON_UNESCAPED_SLASHES|JSON_HEX_TAG).'</script>';ef_layout($body,['title'=>meta_title($c),'final_title'=>true,'description'=>$c['meta_description']?:$c['excerpt'],'canonical'=>$c['canonical_url']?:url($c['slug']),'noindex'=>(bool)$c['noindex'],'og_type'=>'article','image'=>!empty($d['featured_image'])?url($d['featured_image']):null]);}
function ef_video_player(array$c,array$d):string{$yt=ef_youtube_id($d['youtube_id']??$d['youtube_url']??'');$src=$c['type']==='video_playlist'&&!empty($d['playlist_id'])?'https://www.youtube-nocookie.com/embed/videoseries?list='.rawurlencode($d['playlist_id']):'https://www.youtube-nocookie.com/embed/'.rawurlencode($yt);if(!$yt&&empty($d['playlist_id']))return'';return'<section class="video-study"><div class="video" data-video-src="'.e($src).'"><button class="video-consent button">Load privacy-enhanced YouTube player</button></div><button type="button" class="theater" data-theater>Theater mode</button>'.(!empty($d['duration'])?'<p>Duration: '.e($d['duration']).'</p>':'').ef_video_sections($d).'</section><script defer src="'.url('assets/video.js?v=3').'"></script>';}
function ef_video_sections(array$d):string{$h='';if($d['chapters']??[]){$h.='<h2>Chapters</h2><ol>';foreach($d['chapters'] as$x)$h.='<li><strong>'.e($x['time']??'').'</strong> '.e($x['title']??'').'</li>';$h.='</ol>';}if(!empty($d['transcript']))$h.='<details><summary>Transcript</summary><div class="prose">'.nl2br(e($d['transcript'])).'</div></details>';if(!empty($d['study_notes']))$h.='<h2>Study notes</h2><div class="prose">'.clean_html($d['study_notes']).'</div>';return$h;}
function ef_schemas(array$c,array$d):array{
 $base=['@context'=>'https://schema.org'];$crumbItems=[['@type'=>'ListItem','position'=>1,'name'=>'Home','item'=>url()]];if($c['type']==='blog_post')$crumbItems[]=['@type'=>'ListItem','position'=>2,'name'=>'Study Blog','item'=>url('blog')];$crumbItems[]=['@type'=>'ListItem','position'=>count($crumbItems)+1,'name'=>$c['title'],'item'=>url($c['slug'])];$crumb=$base+['@type'=>'BreadcrumbList','itemListElement'=>$crumbItems];
 if($c['type']==='course'||$c['type']==='college_course')$main=$base+['@type'=>'Course','name'=>$c['title'],'description'=>$c['excerpt'],'provider'=>['@type'=>'Organization','name'=>setting('site_name','Exams.fit'),'url'=>url()]];
 elseif(in_array($c['type'],['practice_test','mock_exam','daily_quiz','question_bank'],true))$main=$base+['@type'=>'Quiz','name'=>$c['title'],'description'=>$c['excerpt'],'hasPart'=>array_map(fn($q)=>['@type'=>'Question','name'=>$q['prompt']],array_slice($d['questions']??[],0,20))];
 elseif($c['type']==='youtube_video'&&!empty($d['thumbnail'])&&!empty($d['duration']))$main=$base+['@type'=>'VideoObject','name'=>$c['title'],'description'=>$c['excerpt'],'thumbnailUrl'=>[$d['thumbnail']],'uploadDate'=>date(DATE_ATOM,strtotime($c['published_at'])),'duration'=>$d['duration'],'embedUrl'=>'https://www.youtube-nocookie.com/embed/'.ef_youtube_id($d['youtube_id']??$d['youtube_url']??'')];
 elseif($c['type']==='blog_post'){$main=$base+['@type'=>'BlogPosting','headline'=>$c['title'],'description'=>$c['excerpt'],'author'=>['@type'=>'Person','name'=>$c['author']?:'Exams.fit Editorial Team'],'reviewedBy'=>['@type'=>'Person','name'=>$c['reviewer']?:'Exams.fit Education Reviewer'],'publisher'=>['@type'=>'Organization','name'=>setting('site_name','Exams.fit'),'url'=>url()],'datePublished'=>date(DATE_ATOM,strtotime($c['published_at']?:$c['created_at'])),'dateModified'=>date(DATE_ATOM,strtotime($c['updated_at'])),'mainEntityOfPage'=>['@type'=>'WebPage','@id'=>url($c['slug'])],'articleSection'=>$d['primary_topic']??$c['category']??'Education'];if(!empty($d['featured_image']))$main['image']=[url($d['featured_image'])];}
 elseif($c['type']==='tool')$main=$base+['@type'=>'SoftwareApplication','name'=>$c['title'],'description'=>$c['excerpt'],'applicationCategory'=>'EducationalApplication','operatingSystem'=>'Any','offers'=>['@type'=>'Offer','price'=>'0','priceCurrency'=>'USD']];
 else$main=$base+['@type'=>'Article','headline'=>$c['title'],'description'=>$c['excerpt'],'author'=>['@type'=>'Organization','name'=>$c['author']?:setting('site_name','Exams.fit')],'datePublished'=>$c['published_at'],'dateModified'=>$c['updated_at'],'mainEntityOfPage'=>url($c['slug'])];
 $out=[$main,$crumb];if($c['type']==='blog_post'&&!empty($d['faqs'])&&is_array($d['faqs'])){$entities=[];foreach(array_slice($d['faqs'],0,12) as$faq)if(!empty($faq['question'])&&!empty($faq['answer']))$entities[]=['@type'=>'Question','name'=>$faq['question'],'acceptedAnswer'=>['@type'=>'Answer','text'=>$faq['answer']]];if($entities)$out[]=$base+['@type'=>'FAQPage','mainEntity'=>$entities];}return$out;
}
function ef_search():void{$term=trim($_GET['q']??'');$page=max(1,(int)($_GET['page']??1));$per=20;$items=[];$total=0;if(mb_strlen($term)>=2){$like='%'.$term.'%';$total=(int)(one("SELECT COUNT(*) n FROM content WHERE status='published' AND (title LIKE ? OR excerpt LIKE ?)",[$like,$like])['n']??0);$items=all("SELECT type,slug,title,excerpt FROM content WHERE status='published' AND (title LIKE ? OR excerpt LIKE ?) ORDER BY featured DESC,published_at DESC LIMIT $per OFFSET ".(($page-1)*$per),[$like,$like]);ef_track_server($items?'search':'zero_search',$term);}$body='<section><h1>Search Exams.fit</h1><form class="big-search"><input name="q" value="'.e($term).'" placeholder="What do you want to study?"><button>Search</button></form>'.($term?'<p>'.number_format($total).' results</p>'.ef_cards($items).ef_pagination($page,(int)ceil($total/$per),'search?q='.rawurlencode($term)):'').'</section>';ef_layout($body,['title'=>'Search','noindex'=>true]);}
function ef_error(int$code):void{http_response_code($code);ef_layout('<section><h1>'.$code.'</h1><p>The requested page could not be found.</p><a class="button" href="'.url().'">Return home</a></section>',['title'=>'Page not found','noindex'=>true]);}
function ef_ad(string$slot,array$c=[]):string{if(setting('ads_enabled','0')!=='1'||!empty($c['ad_free']))return'';$device=preg_match('/Mobile|Android|iPhone|iPad/i',$_SERVER['HTTP_USER_AGENT']??'')?'mobile':'desktop';$sql="SELECT code FROM ads WHERE enabled=1 AND slot=? AND (starts_at IS NULL OR starts_at<=NOW()) AND (ends_at IS NULL OR ends_at>=NOW()) AND device IN('all',?) AND (category_id IS NULL OR category_id=?) AND (content_type IS NULL OR content_type=?) AND (content_id IS NULL OR content_id=?) ORDER BY content_id DESC,category_id DESC LIMIT 1";$r=one($sql,[$slot,$device,$c['category_id']??null,$c['type']??null,$c['id']??null]);return$r?'<aside class="ad" aria-label="Advertisement">'.$r['code'].'</aside>':'';}
function ef_robots():void{header('Content-Type:text/plain');echo"User-agent: *\nDisallow: /".($GLOBALS['config']['admin_path']??'adminji')."/\nDisallow: /install/\nDisallow: /upgrade/\nDisallow: /api/\nSitemap: ".url('sitemap_index.xml')."\n".setting('robots_extra');}
function ef_sitemap_group(string$key):array{$map=['blog'=>['blog_post'],'exams'=>['exam_profile','exam_syllabus','exam_pattern','registration_guide','eligibility_guide','fee_guide'],'practice'=>['practice_test','mock_exam','question_bank','daily_quiz'],'school'=>['school_grade','school_subject','school_lesson'],'college'=>['college_field','college_subject','college_course'],'notes'=>['study_chapter','study_notes','study_guide'],'courses'=>['course','college_course'],'articles'=>['article'],'news'=>['education_news','exam_update','admission_update'],'videos'=>['youtube_video','video_playlist'],'pdfs'=>['pdf_material'],'tools'=>['tool']];return$map[$key]??[];}
function ef_sitemap(?string$type):void{header('Content-Type:application/xml; charset=utf-8');echo'<?xml version="1.0" encoding="UTF-8"?>';if(!$type){$groups=['static','exams','practice','school','college','notes','courses','articles','news','videos','pdfs','tools','categories','images'];echo'<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';foreach($groups as$g){$count=$g==='categories'?(int)(one('SELECT COUNT(*) n FROM categories')['n']??0):($g==='static'?1:(int)(one("SELECT COUNT(*) n FROM content WHERE status='published' AND noindex=0")['n']??0));$pages=max(1,(int)ceil($count/50000));for($p=1;$p<=$pages;$p++)echo'<sitemap><loc>'.e(url('sitemap-'.$g.($p>1?'-'.$p:'').'.xml')).'</loc></sitemap>';}echo'</sitemapindex>';return;}$page=1;if(preg_match('/^(.+)-(\d+)$/',$type,$m)){$type=$m[1];$page=max(1,(int)$m[2]);}$offset=($page-1)*50000;if($type==='static'){echo'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><url><loc>'.e(url()).'</loc></url></urlset>';return;}if($type==='categories'){$rows=all("SELECT slug,updated_at FROM categories ORDER BY id LIMIT 50000 OFFSET $offset");echo'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';foreach($rows as$r)echo'<url><loc>'.e(url('category/'.$r['slug'])).'</loc><lastmod>'.e(substr($r['updated_at'],0,10)).'</lastmod></url>';echo'</urlset>';return;}if($type==='images'){echo'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">';foreach(all("SELECT c.slug,m.path,m.title FROM media m JOIN content c ON c.body LIKE CONCAT('%',m.path,'%') WHERE c.status='published' AND c.noindex=0 AND m.mime LIKE 'image/%' LIMIT 50000 OFFSET $offset") as$r)echo'<url><loc>'.e(url($r['slug'])).'</loc><image:image><image:loc>'.e(url($r['path'])).'</image:loc><image:title>'.e($r['title']).'</image:title></image:image></url>';echo'</urlset>';return;}$types=ef_sitemap_group($type);if(!$types){http_response_code(404);echo'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"/>';return;}$ph=implode(',',array_fill(0,count($types),'?'));$rows=all("SELECT slug,updated_at FROM content WHERE status='published' AND noindex=0 AND type IN($ph) ORDER BY id LIMIT 50000 OFFSET $offset",$types);echo'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';foreach($rows as$r)echo'<url><loc>'.e(url($r['slug'])).'</loc><lastmod>'.e(substr($r['updated_at'],0,10)).'</lastmod></url>';echo'</urlset>';}
function ef_rss():void{$rows=all("SELECT slug,title,excerpt,published_at FROM content WHERE status='published' ORDER BY published_at DESC LIMIT 30");header('Content-Type:application/rss+xml; charset=utf-8');echo'<?xml version="1.0"?><rss version="2.0"><channel><title>Exams.fit</title><link>'.e(url()).'</link><description>USA education resources</description>';foreach($rows as$r)echo'<item><title>'.e($r['title']).'</title><link>'.e(url($r['slug'])).'</link><guid>'.e(url($r['slug'])).'</guid><description>'.e($r['excerpt']).'</description><pubDate>'.date(DATE_RSS,strtotime($r['published_at'])).'</pubDate></item>';echo'</channel></rss>';}
function ef_track_server(string$event,string$label):void{if(setting('analytics_enabled','1')==='1')q('INSERT INTO analytics(day,event,path,session_hash,value,label,device,browser,referrer) VALUES(CURDATE(),?,?,?,1,?,"server","server",NULL)',[$event,substr(parse_url($_SERVER['REQUEST_URI']??'/',PHP_URL_PATH)?:'/',0,255),substr(hash('sha256',session_id()),0,32),mb_substr($label,0,255)]);}
function ef_analytics_endpoint_root():void{if($_SERVER['REQUEST_METHOD']!=='POST'){http_response_code(405);return;}if(setting('analytics_enabled','1')!=='1'){http_response_code(204);return;}$_SESSION['analytics_window']=$_SESSION['analytics_window']??['at'=>time(),'count'=>0];if(time()-$_SESSION['analytics_window']['at']>3600)$_SESSION['analytics_window']=['at'=>time(),'count'=>0];if(++$_SESSION['analytics_window']['count']>120){http_response_code(429);return;}$d=json_decode(file_get_contents('php://input',false,null,0,4096)?:'',true)?:[];$event=preg_replace('/[^a-z_]/','',substr($d['event']??'pageview',0,30));$path=substr($d['path']??'/',0,255);if(!str_starts_with($path,'/')){http_response_code(204);return;}$session=substr(hash('sha256',(string)($d['session']??session_id())),0,32);q('INSERT INTO analytics(day,event,path,session_hash,value,label,device,browser,referrer) VALUES(CURDATE(),?,?,?,?,?,?,?,?)',[$event,$path,$session,min(86400,max(0,(int)($d['value']??1))),substr($d['label']??'',0,255),substr($d['device']??'',0,30),substr($d['browser']??'',0,40),substr($d['referrer']??'',0,255)]);http_response_code(204);}
function ef_practice_api(string$slug,string$action):void{$c=one("SELECT id,data FROM content WHERE slug=? AND status='published' AND type IN('practice_test','mock_exam','daily_quiz','question_bank')",[$slug]);if(!$c){http_response_code(404);return;}$d=json_decode($c['data'],true)?:[];$qs=$d['questions']??[];header('Content-Type:application/json');if($action!=='submit'){$safe=[];foreach($qs as$q){unset($q['answer'],$q['explanation']);$safe[]=$q;}echo json_encode(['questions'=>$safe,'time_limit'=>(int)($d['time_limit']??0),'randomize_questions'=>(bool)($d['randomize_questions']??false),'randomize_options'=>(bool)($d['randomize_options']??false),'csrf'=>csrf()]);return;}$in=json_decode(file_get_contents('php://input'),true)?:[];if(!hash_equals(csrf(),$in['csrf']??'')){http_response_code(419);echo'{"error":"expired"}';return;}$score=0;$out=[];foreach($qs as$q){$given=$in['answers'][$q['id']]??null;$expected=$q['answer'];$type=$q['type']??'single_choice';if($type==='multiple_choice'){$expected=is_array($expected)?$expected:[$expected];$given=is_array($given)?$given:[$given];sort($expected);sort($given);$correct=$given===$expected;}elseif($type==='matching'){$expected=is_array($expected)?$expected:[];$given=is_array($given)?$given:[];ksort($expected);ksort($given);$correct=$given===$expected;}elseif($type==='fill_blank'&&is_array($expected)){$normal=mb_strtolower(trim((string)$given));$correct=in_array($normal,array_map(fn($x)=>mb_strtolower(trim((string)$x)),$expected),true);}else$correct=mb_strtolower(trim((string)$given))===mb_strtolower(trim((string)$expected));if($correct)$score++;$out[]=['id'=>$q['id'],'correct'=>$correct,'answer'=>$expected,'explanation'=>$q['explanation']??'','topic'=>$q['topic']??'General'];}echo json_encode(['score'=>$score,'total'=>count($qs),'results'=>$out]);}
function ef_report_endpoint():void{if($_SERVER['REQUEST_METHOD']!=='POST'){http_response_code(405);return;}$d=json_decode(file_get_contents('php://input',false,null,0,4096),true)?:[];if(!hash_equals(csrf(),$d['csrf']??'')){http_response_code(419);return;}$slug=preg_replace('~[^a-z0-9/_-]~','',$d['slug']??'');$c=one('SELECT id FROM content WHERE slug=?',[$slug]);$message=trim(strip_tags($d['message']??''));if(!$c||mb_strlen($message)<10||mb_strlen($message)>2000){http_response_code(422);return;}$_SESSION['report_at']=$_SESSION['report_at']??0;if(time()-$_SESSION['report_at']<60){http_response_code(429);return;}$kind=in_array($d['kind']??'correction',['correction','question','copyright','accessibility'],true)?$d['kind']:'correction';q('INSERT INTO reports(content_id,kind,message,status,created_at) VALUES(?,?,?,"open",NOW())',[$c['id'],$kind,$message]);$_SESSION['report_at']=time();http_response_code(204);}
