     1	<?php
     2	require __DIR__.'/../config.php';
     3	require __DIR__.'/../auth.php';
     4	require __DIR__.'/../subscription_lib.php';
     5	$AUTH_USER=login_required();
     6	$view=$_GET['view']??'dashboard';
     7	function ensure_utility_button_columns(): void {
     8	  static $done=false; if($done) return;
     9	  $pdo=db();
    10	  try{
    11	    $flag=(string)($pdo->query("SELECT setting_value FROM settings WHERE setting_key='utility_buttons_schema_v80' LIMIT 1")->fetchColumn() ?: '');
    12	    if($flag==='1'){ $done=true; return; }
    13	    $defs=['button2_text'=>'ALTER TABLE utility_templates ADD COLUMN button2_text VARCHAR(255) NULL','button2_url'=>'ALTER TABLE utility_templates ADD COLUMN button2_url TEXT NULL','button3_text'=>'ALTER TABLE utility_templates ADD COLUMN button3_text VARCHAR(255) NULL','button3_url'=>'ALTER TABLE utility_templates ADD COLUMN button3_url TEXT NULL'];
    14	    foreach($defs as $col=>$sql){$q=$pdo->prepare("SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='utility_templates' AND COLUMN_NAME=?");$q->execute([$col]);if(!(int)$q->fetchColumn())$pdo->exec($sql);}
    15	    $pdo->prepare('INSERT INTO settings(setting_key,setting_value) VALUES(?,?) ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value)')->execute(['utility_buttons_schema_v80','1']);
    16	    $done=true;
    17	  }catch(Throwable $e){}
    18	}
    19	try { db()->query('SELECT 1'); ensure_runtime_schema(); ensure_background_tables(); if($view==='subscriptions') ensure_subscription_tables(); if($view==='utility') ensure_utility_button_columns(); } catch(Throwable $e) { http_response_code(500); exit('Database/initialization error: '.e($e->getMessage())); }
    20	if(!is_admin() && $view==='settings') $view='dashboard';
    21	$allowed=['dashboard','settings','connect','subscribers','comments','automation','utility','campaigns','subscriptions','logs','graph_tester'];
    22	if(!in_array($view,$allowed,true)) $view='dashboard';
    23	$flashes=flashes();
    24	function nav(string $v,string $label,string $icon=''): void { $active=(($_GET['view']??'dashboard')===$v)?'active':''; echo '<a class="nav '.$active.'" href="../'.e($v).'">'.$icon.'<span>'.e($label).'</span></a>'; }
    25	function head(string $title): void { global $flashes, $AUTH_USER; ?><!doctype html><html lang="ar" dir="rtl"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title><?=e($title)?> - <?=APP_NAME?></title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.rtl.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"><style>body{background:#f6f8fc;font-family:Tahoma,Arial}body{background:#f4f7fb;font-family:Tahoma,Arial;color:#172033}.sidebar{width:250px;position:fixed;left:0;right:auto;top:0;bottom:0;background:linear-gradient(180deg,#111d31 0%,#17263f 100%);border:0;padding:20px 16px;z-index:1000;box-shadow:8px 0 28px rgba(15,23,42,.08)}.main{margin-left:250px;margin-right:0;padding:25px 28px;min-height:100vh}.brand{display:flex;align-items:center;justify-content:center;width:100%;height:58px;margin-bottom:26px;overflow:hidden}.brand-logo{display:block;width:190px;height:auto;max-height:52px;object-fit:contain}.nav{display:flex;gap:11px;align-items:center;padding:12px 13px;border-radius:10px;text-decoration:none;color:#dbe5f5;margin-bottom:6px;font-size:14px;transition:.18s}.nav i{font-size:18px;min-width:20px;text-align:center}.nav.active,.nav:hover{background:linear-gradient(90deg,#3d56f4,#1e8bff);color:#fff;box-shadow:0 8px 18px rgba(45,92,245,.22)}.cardx{background:#fff;border:0;border-radius:16px;box-shadow:0 6px 24px rgba(22,34,55,.07)}.stat{font-size:28px;font-weight:800}.small-muted{color:#7b8490;font-size:13px}.table>:not(caption)>*>*{padding:12px}.badge-soft{background:#eef4ff;color:#1877f2}.code{background:#f3f5f8;padding:10px;border-radius:8px;direction:ltr;text-align:left;word-break:break-all}.dashboard-title{font-size:30px;font-weight:800}.dash-subtitle{color:#758197}.metric-card{position:relative;overflow:hidden;min-height:124px}.metric-icon{width:48px;height:48px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:#eef4ff;color:#1877f2;font-size:22px}.metric-value{font-size:30px;font-weight:800;line-height:1}.metric-label{font-size:14px;color:#596579}.chart-box{min-height:300px}.mini-bars{height:185px;display:flex;align-items:end;gap:12px;padding:18px 8px 6px}.mini-bars span{flex:1;border-radius:8px 8px 2px 2px;background:linear-gradient(180deg,#3f7cff,#7ca9ff);min-height:18px}.mini-bars span:nth-child(2n){background:linear-gradient(180deg,#6b5cff,#a99cff)}.section-title{font-weight:800;font-size:18px}.topbar{background:#fff;border-radius:16px;padding:12px 16px;margin-bottom:22px;box-shadow:0 4px 18px rgba(22,34,55,.05)}.topbar .avatar{width:40px;height:40px;border-radius:50%;background:#e9eef8;color:#66738a;display:flex;align-items:center;justify-content:center;font-size:20px}@media(max-width:900px){.sidebar{width:70px;padding:18px 10px}.sidebar span{display:none}.main{margin-left:70px;padding:18px}.brand{width:46px;height:46px;margin:0 auto 24px;justify-content:flex-start}.brand-logo{width:178px;max-width:none;height:46px;object-fit:contain;object-position:left center}.dashboard-title{font-size:24px}}</style></head><body><aside class="sidebar"><a class="brand" href="../dashboard" aria-label="bestchat"><img class="brand-logo" src="../assets/bestchat-logo.png" alt="bestchat"></a><?php nav('dashboard','Dashboard','<i class="bi bi-speedometer2"></i>'); nav('connect','تسجيل دخول Facebook','<i class="bi bi-facebook"></i>'); nav('subscribers','المشتركين','<i class="bi bi-people"></i>'); nav('comments','التعليقات','<i class="bi bi-chat-dots"></i>'); nav('automation','الرد التلقائي','<i class="bi bi-magic"></i>'); nav('utility','Utility Messages','<i class="bi bi-lightning-charge"></i>'); nav('campaigns','الحملات','<i class="bi bi-send"></i>'); nav('subscriptions','الاشتراك','<i class="bi bi-credit-card"></i>'); if(is_admin()) nav('settings','إعدادات التطبيق','<i class="bi bi-gear"></i>'); nav('logs','السجل','<i class="bi bi-journal-text"></i>'); nav('graph_tester','Graph API Tester','<i class="bi bi-terminal"></i>'); echo '<div style="border-top:1px solid #e8ebf0;margin:14px 0 10px;padding-top:12px"></div>'; echo '<a class="nav" href="../account"><i class="bi bi-person-circle"></i><span>حسابي / تغيير كلمة المرور</span></a>'; if(is_admin()) echo '<a class="nav admin-link" href="../admin"><i class="bi bi-people-fill"></i><span>إدارة المستخدمين</span></a>'; echo '<a class="nav" href="../logout"><i class="bi bi-box-arrow-right"></i><span>تسجيل الخروج</span></a>'; ?></aside><main class="main"><div class="topbar d-flex justify-content-between align-items-center"><div class="d-flex align-items-center gap-3"><div class="avatar"><i class="bi bi-person"></i></div><div><div class="fw-bold"><?=e($AUTH_USER['email'])?></div><div class="small-muted">لوحة إدارة bestchat</div></div></div><div class="d-flex align-items-center gap-2"><a href="../account" class="btn btn-sm btn-light">حسابي</a><?php if(is_admin()): ?><a href="../admin" class="btn btn-sm btn-outline-primary">إدارة المستخدمين</a><?php endif; ?><a href="../logout" class="btn btn-sm btn-outline-secondary">خروج</a></div></div><div class="mb-4"><h3 class="mb-1"><?=e($title)?></h3><div class="small-muted">نظام إدارة صفحات Facebook — bestchat</div></div><?php foreach($flashes as $f): ?><div class="alert alert-<?=e($f[0])?>"><?=e($f[1])?></div><?php endforeach; ?><?php }
    26	function foot(): void { global $view; if($view==='dashboard'){ echo '<script>function refreshJobs(){var panel=document.getElementById("dashboardActivity");if(!panel)return;fetch("?view=dashboard&jobs=1",{cache:"no-store"}).then(r=>r.text()).then(t=>{if(t.trim())panel.innerHTML=t;}).catch(()=>{});} setInterval(refreshJobs,15000);</script>'; } echo '</main></body></html>'; }
    27	function render_pagination(int $total, int $perPage, int $current, string $pageParam='p', array $extra=[]): void {
    28	  $pages=max(1,(int)ceil($total/max(1,$perPage))); if($pages<=1)return;
    29	  $current=max(1,min($pages,$current));
    30	  $window=[]; $window[]=1; for($i=max(2,$current-2);$i<=min($pages-1,$current+2);$i++)$window[]=$i; if($pages>1)$window[]=$pages;
    31	  $window=array_values(array_unique($window)); sort($window);
    32	  echo '<nav class="mt-3" aria-label="Pagination"><ul class="pagination pagination-sm justify-content-center flex-wrap gap-1">';
    33	  $last=0; foreach($window as $i){ if($last && $i>$last+1)echo '<li class="page-item disabled"><span class="page-link">…</span></li>';
    34	    $params=$extra; $params[$pageParam]=$i; $qs=http_build_query($params);
    35	    echo '<li class="page-item '.($i===$current?'active':'').'"><a class="page-link" href="?'.e($qs).'">'.$i.'</a></li>'; $last=$i; }
    36	  echo '</ul><div class="text-center small-muted">صفحة '.$current.' من '.$pages.' — إجمالي '.$total.'</div></nav>';
    37	}
    38	
    39	function page_ids_from_selector($value, array $pages): array {
    40	  if($value==='all') return array_values(array_map(fn($x)=>(int)$x['id'],$pages));
    41	  $id=(int)$value; return $id>0 ? [$id] : [];
    42	}
    43	function page_label($value, array $pages): string {
    44	  if($value==='all') return 'كل الصفحات';
    45	  foreach($pages as $p) if((int)$p['id']===(int)$value) return $p['name'];
    46	  return '-';
    47	}
    48	
    49	
    50	if($_SERVER['REQUEST_METHOD']==='POST' && ($_POST['action']??'')==='sync_all_reviews'){
    51	  verify_csrf();
    52	  $count=0;$pagesOk=0;$errors=0;$empty=0;$details=[];
    53	  foreach(tenant_pages() as $p){
    54	    $pageCount=0;$after=null;$pageError='';
    55	    for($pass=0;$pass<20;$pass++){
    56	      $params=['limit'=>100,'fields'=>'id,name,language,status,category,components'];
    57	      if($after) $params['after']=$after;
    58	      $r=graph('GET',$p['page_id'].'/message_templates',$p['access_token'],$params);
    59	      if(!$r['ok']){
    60	        $errors++; $pageError=$r['error']??'Graph API error';
    61	        log_event((int)$p['id'],'template_review_sync','error',$pageError,['page_id'=>$p['page_id'],'http_status'=>$r['status']??null,'graph_response'=>$r['data']??null]);
    62	        break;
    63	      }
    64	      $items=$r['data']['data']??[];
    65	      foreach($items as $t){
    66	        $raw=$t?json_encode($t,JSON_UNESCAPED_UNICODE):'';
    67	        $name=(string)($t['name']??'');
    68	        if($name==='') continue;
    69	        $lang=(string)($t['language']??($t['language_code']??'en_US'));
    70	        $status=(string)($t['status']??'');
    71	        $metaId=$t['id']??($t['template_id']??null);
    72	        $body=$t['body']['text']??($t['body_text']??'');$header=$t['header']['text']??($t['header_text']??'');$btn='';$url='';
    73	        if(isset($t['components']) && is_array($t['components'])) foreach($t['components'] as $component){
    74	          $type=strtoupper((string)($component['type']??''));
    75	          if($type==='HEADER'&&$header==='')$header=(string)($component['text']??'');
    76	          if($type==='BODY'&&$body==='')$body=(string)($component['text']??'');
    77	          if($type==='BUTTONS'&&!empty($component['buttons'][0])){$b=$component['buttons'][0];$btn=(string)($b['text']??'');$url=(string)($b['url']??'');}
    78	        }
    79	        $btn=$btn?:($t['button']['text']??($t['button_text']??''));$url=$url?:($t['button']['url']??($t['button_url']??''));
    80	        $q=db()->prepare('INSERT INTO utility_templates(user_id,page_id,template_name,language_code,status,raw_json,body_text,header_text,button_text,button_url,meta_id,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,NOW(),NOW()) ON DUPLICATE KEY UPDATE user_id=VALUES(user_id),status=VALUES(status),raw_json=VALUES(raw_json),body_text=COALESCE(NULLIF(VALUES(body_text),""),body_text),header_text=COALESCE(NULLIF(VALUES(header_text),""),header_text),button_text=COALESCE(NULLIF(VALUES(button_text),""),button_text),button_url=COALESCE(NULLIF(VALUES(button_url),""),button_url),meta_id=COALESCE(VALUES(meta_id),meta_id),updated_at=NOW()');
    81	        $q->execute([auth_user_id(),$p['id'],$name,$lang,$status,$raw,$body,$header,$btn,$url,$metaId]);$count++;$pageCount++;
    82	      }
    83	      $after=$r['data']['paging']['cursors']['after']??null;
    84	      if(!$after || empty($r['data']['paging']['next'])) break;
    85	    }
    86	    if($pageError!=='') $details[]=e($p['name']).': '.e($pageError);
    87	    elseif($pageCount===0){$empty++;$details[]=e($p['name']).': لم تُرجع Meta أي قوالب من endpoint.';}
    88	    else {$pagesOk++;$details[]=e($p['name']).': تم استيراد/تحديث '.$pageCount.' قالب.';}
    89	  }
    90	  $msg='تمت مزامنة '.$count.' قالب من Meta على '.$pagesOk.' صفحة.';
    91	  if($empty) $msg.=' '.$empty.' صفحة أعادت 0 قوالب؛ هذا لا يعني أن القوالب حُذفت من Meta.';
    92	  if($errors) $msg.=' تعذر الوصول إلى '.$errors.' صفحة؛ راجع تفاصيل السجل.';
    93	  flash($errors?'warning':'success',$msg.' '.implode(' | ',$details));
    94	  redirect('../campaigns');
    95	}
    96	
    97	if($view==='graph_tester'){
    98	  $pages=all_pages();
    99	  $result=null;
   100	  $selectedPageId=(int)($_POST['saved_page_id']??$_GET['page_id']??0);
   101	  if($_SERVER['REQUEST_METHOD']==='POST' && isset($_POST['tester_submit'])){
   102	    verify_csrf();
   103	    $method=strtoupper(trim((string)($_POST['method']??'GET')));
   104	    $path=trim((string)($_POST['path']??''));
   105	    $token='';
   106	    $paramsText=trim((string)($_POST['params']??''));
   107	    {
   108	      $pg=page($selectedPageId);
   109	      if(!$pg) $result=['ok'=>false,'status'=>0,'error'=>'الصفحة المحفوظة غير موجودة.'];
   110	      else $token=(string)$pg['access_token'];
   111	    }
   112	    if(!$result && !in_array($method,['GET','POST','DELETE'],true)) $result=['ok'=>false,'status'=>0,'error'=>'Method غير مسموح. استخدم GET أو POST أو DELETE.'];
   113	    if(!$result && ($path==='' || preg_match('~^https?://|^//|[\r\n]~i',$path))) $result=['ok'=>false,'status'=>0,'error'=>'اكتب Graph API path فقط مثل 1329052833615805 أو 132.../feed.'];
   114	    $params=[];
   115	    if(!$result && $paramsText!==''){
   116	      $decoded=json_decode($paramsText,true);
   117	      if(is_array($decoded)) $params=$decoded;
   118	      else {
   119	        parse_str($paramsText,$params);
   120	        if(!is_array($params)) $result=['ok'=>false,'status'=>0,'error'=>'Parameters يجب أن تكون JSON أو key=value&key2=value2.'];
   121	      }
   122	    }
   123	    if(!$result && $token==='') $result=['ok'=>false,'status'=>0,'error'=>'الصفحة المحددة لا تحتوي على Page Access Token محفوظ. حدّث Page Token من Facebook Accounts أولًا.'];
   124	    if(!$result){
   125	      $path=ltrim($path,'/');
   126	      $url=graph_url($path);
   127	      if($method==='GET' && $params) $url.='?'.http_build_query($params);
   128	      $ch=curl_init($url);
   129	      $headers=['Accept: application/json'];
   130	      if($token) $headers[]='Authorization: Bearer '.$token;
   131	      if($method==='POST'){
   132	        curl_setopt($ch,CURLOPT_POST,true);
   133	        curl_setopt($ch,CURLOPT_POSTFIELDS,http_build_query($params));
   134	        $headers[]='Content-Type: application/x-www-form-urlencoded';
   135	      }elseif($method==='DELETE') curl_setopt($ch,CURLOPT_CUSTOMREQUEST,'DELETE');
   136	      curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_HTTPHEADER=>$headers,CURLOPT_TIMEOUT=>45,CURLOPT_FOLLOWLOCATION=>true]);
   137	      $raw=curl_exec($ch); $curlErr=curl_error($ch); $status=(int)curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
   138	      $decoded=json_decode((string)$raw,true);
   139	      $result=['ok'=>!$curlErr && $status>=200 && $status<400 && !(isset($decoded['error'])),'status'=>$status,'error'=>$curlErr ?: ($decoded['error']['message']??null),'raw'=>$raw,'json'=>$decoded,'request'=>['method'=>$method,'url'=>$url,'path'=>$path,'params'=>$params]];
   140	    }
   141	  }
   142	  head('Graph API Tester');
   143	  $presets=[
   144	    ['name'=>'بيانات الصفحة','method'=>'GET','path'=>'{PAGE_ID}','params'=>'{"fields":"id,name"}'],
   145	    ['name'=>'منشورات الصفحة','method'=>'GET','path'=>'{PAGE_ID}/feed','params'=>'{"fields":"id,message,created_time","limit":5}'],
   146	    ['name'=>'تعليقات الصفحة','method'=>'GET','path'=>'{PAGE_ID}/feed','params'=>'{"fields":"id,message,comments.limit(10){from,message,id}","limit":5}'],
   147	    ['name'=>'Webhooks subscriptions','method'=>'GET','path'=>'{PAGE_ID}/subscribed_apps','params'=>'{}'],
   148	    ['name'=>'Utility templates','method'=>'GET','path'=>'{PAGE_ID}/message_templates','params'=>'{"limit":25}'],
   149	  ];
   150	  ?>
   151	  <div class="cardx p-4 mb-4">
   152	    <div class="d-flex justify-content-between align-items-center"><div><h5 class="mb-1">🧪 Graph API Tester</h5><div class="small-muted">اختبر Graph API من داخل موقعك مثل أدوات الاختبار، بدون تخزين التوكن الذي تلصقه يدويًا.</div></div><span class="badge bg-primary"><?=e(graph_version())?></span></div>
   153	  </div>
   154	  <div class="cardx p-4 mb-4">
   155	    <h6>طلبات جاهزة</h6><div class="d-flex flex-wrap gap-2 mt-3"><?php foreach($presets as $pr): ?><button type="button" class="btn btn-sm btn-outline-primary preset" data-method="<?=e($pr['method'])?>" data-path="<?=e($pr['path'])?>" data-params='<?=e($pr['params'])?>'><?=e($pr['name'])?></button><?php endforeach; ?></div>
   156	  </div>
   157	  <div class="cardx p-4">
   158	    <form method="post" id="testerForm">
   159	      <input type="hidden" name="csrf" value="<?=csrf()?>"><input type="hidden" name="tester_submit" value="1">
   160	      <div class="row g-3">
   161	        <div class="col-md-2"><label class="form-label">Method</label><select class="form-select" name="method" id="testerMethod"><option>GET</option><option>POST</option><option>DELETE</option></select></div>
   162	        <div class="col-md-10"><label class="form-label">Graph API Path</label><input class="form-control" name="path" id="testerPath" placeholder="1329052833615805 أو PAGE_ID/message_templates" value="<?=e($_POST['path']??'')?>"><div class="small-muted mt-1">اكتب الـPath فقط، بدون https://graph.facebook.com/</div></div>
   163	        <div class="col-12"><label class="form-label">الصفحة</label><select class="form-select" name="saved_page_id" required><option value="0">-- اختر الصفحة --</option><?php foreach($pages as $pg): ?><option value="<?=$pg['id']?>" <?=$selectedPageId===(int)$pg['id']?'selected':''?>><?=e($pg['name'])?> — <?=e($pg['page_id'])?></option><?php endforeach; ?></select><div class="small-muted mt-1">سيستخدم Graph API Tester تلقائيًا Page Access Token المحفوظ لهذه الصفحة. لا حاجة لإدخال التوكن يدويًا.</div></div>
   164	        <div class="col-md-6"><label class="form-label">Parameters</label><textarea class="form-control" name="params" id="testerParams" rows="3" placeholder='{"fields":"id,name"}'><?=e($_POST['params']??'')?></textarea><div class="small-muted mt-1">JSON أو key=value&key2=value2</div></div>
   165	      </div>
   166	      <div class="mt-3 d-flex gap-2"><button class="btn btn-primary" type="submit">▶ تنفيذ الطلب</button><button class="btn btn-outline-secondary" type="button" id="clearTester">مسح</button></div>
   167	    </form>
   168	  </div>
   169	  <?php if($result!==null): ?>
   170	  <div class="cardx p-4 mt-4">
   171	    <div class="d-flex justify-content-between align-items-center mb-3"><h5 class="mb-0">النتيجة</h5><span class="badge bg-<?=$result['ok']?'success':'danger'?>">HTTP <?=e((string)($result['status']??0))?> — <?=$result['ok']?'OK':'ERROR'?></span></div>
   172	    <?php if(!empty($result['error'])): ?><div class="alert alert-danger"><b>Meta / cURL:</b> <?=e((string)$result['error'])?></div><?php endif; ?>
   173	    <?php if(!empty($result['request'])): ?><div class="small-muted mb-2">الطلب: <?=e($result['request']['method'].' '.$result['request']['url'])?></div><?php endif; ?>
   174	    <pre class="code" style="white-space:pre-wrap;max-height:520px;overflow:auto"><?=e(json_encode($result['json']??json_decode((string)($result['raw']??''),true),JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES) ?: (string)($result['raw']??''))?></pre>
   175	  </div>
   176	  <?php endif; ?>
   177	  <script>
   178	  (function(){
   179	    const path=document.getElementById('testerPath'), params=document.getElementById('testerParams'), method=document.getElementById('testerMethod');
   180	    document.querySelectorAll('.preset').forEach(b=>b.addEventListener('click',()=>{method.value=b.dataset.method; path.value=b.dataset.path.replace('{PAGE_ID}', document.querySelector('select[name="saved_page_id"]')?.selectedOptions[0]?.textContent.split('—')[1]?.trim()||'{PAGE_ID}'); params.value=b.dataset.params;}));
   181	    document.getElementById('clearTester')?.addEventListener('click',()=>{path.value='';params.value='';});
   182	  })();
   183	  </script>
   184	  <?php foot(); exit;
   185	}
   186	
   187	if($view==='dashboard' && isset($_GET['jobs'])){
   188	  ensure_background_tables();
   189	  $uid=auth_user_id();
   190	  $pdo=db();
