<?php
/**
 * EditTag Admin — GTAG + Conversion у <head> та окреме скачування зображень
 *
 * Можливості:
 *  - Авторизація за паролем (bcrypt-хеш у коді, пароль вводиш у браузері).
 *  - GTAG (повний фрагмент <script>...) -> index.(html|php) і result.php
 *  - Conversion (повний фрагмент <script>...) -> лише result.php (додатково до GTAG)
 *  - Опція прибрати дубльоване підключення gtag.js у Conversion-коді
 *  - Ідемпотентні маркери, резервна копія перед записом, CSRF
 *  - Пошук папок з картинками (img, images, media + підпапки 1-го рівня), селектор папки
 *  - Перегляд та скачування зображень ОКРЕМИМИ файлами (всі / вибрані)
 */

session_start();
mb_internal_encoding('UTF-8');

// ======= ПАРОЛЬ (встав свій bcrypt-хеш) =======
// Згенеруй: php -r "echo password_hash('ТВІЙ_ПАРОЛЬ', PASSWORD_BCRYPT), PHP_EOL;"
$PASSWORD_HASH = '$2a$12$Qi.jS0w6U39GnCNeAppXE.NPPl3vZPUFTj.QNr6p2qav7Y8MXEzUq';
// ===============================================

// Дозволені до редагування файли (відносно директорії СКРИПТА)
$ALLOWED_FILES = ['index.php', 'index.html', 'result.php'];

// Базові кандидати папок зі зображеннями (відносно директорії СКРИПТА)
$IMG_DIR_CANDIDATES = ['img', 'images', 'media'];
$ACTIVE_IMG_DIR = $IMG_DIR_CANDIDATES[0]; // дефолт

// Маркери
$GTAG_BEGIN = '<!-- BEGIN_GTAG -->';
$GTAG_END   = '<!-- END_GTAG -->';

$CONV_BEGIN = '<!-- BEGIN_CONVERSION -->';
$CONV_END   = '<!-- END_CONVERSION -->';

// Обмеження
$MAX_FILE_BYTES = 7 * 1024 * 1024;   // 7MB на файл лендингу

// ---------- Helpers ----------
function is_logged_in(){ return !empty($_SESSION['edittag_auth']); }
function csrf_token(){ if(empty($_SESSION['csrf'])) $_SESSION['csrf']=bin2hex(random_bytes(16)); return $_SESSION['csrf']; }
function check_csrf($t){ return isset($_SESSION['csrf']) && hash_equals($_SESSION['csrf'], $t ?? ''); }
function sanitize_rel_path($p){ $p=str_replace(["\0","\r","\n"],'',$p); $p=ltrim($p,'/\\'); if(strpos($p,'../')!==false||strpos($p,'..\\')!==false) return false; return $p; }
function root_path(){ return rtrim(__DIR__, '/\\'); } // шукаємо від місця, де лежить edittag.php
function rel_to_abs($rel){ return root_path().DIRECTORY_SEPARATOR.$rel; }
function remove_block($c,$b,$e){ $re='#'.preg_quote($b,'#').'.*?'.preg_quote($e,'#').'#is'; return preg_replace($re,'',$c); }
function insert_before_head($c,$inj){
  // Шукаємо ОСТАННЄ </head>, а не перше
  $pos = strripos($c, '</head>');
  if ($pos === false) {
    return [false, 'Не знайдено </head>.'];
  }
  return [true, substr($c, 0, $pos) . $inj . substr($c, $pos)];
}

function backup_file($abs){ $b=$abs.'.bak_'.date('Ymd_His'); if(!@copy($abs,$b)) return [false,'Бекап не створено']; return [true,$b]; }
function safe_write($abs,$data){ $fh=fopen($abs,'c+'); if(!$fh) return [false,'Не відкрився файл']; if(!flock($fh,LOCK_EX)){fclose($fh);return[false,'Не заблоковано файл'];} ftruncate($fh,0); rewind($fh); $ok=fwrite($fh,$data)!==false; fflush($fh); flock($fh,LOCK_UN); fclose($fh); return $ok?[true,null]:[false,'Помилка запису']; }
function NL($s){ return preg_replace("/\r\n?/", "\n", $s); }
function bytes_fmt($b){ $u=['B','KB','MB','GB']; $i=0; while($b>=1024 && $i<count($u)-1){ $b/=1024; $i++; } return round($b,2).' '.$u[$i]; }
function list_images_recursive($baseAbs){
  $allow = ['jpg','jpeg','png','gif','webp','svg','ico','bmp','avif','jfif'];
  $files = [];
  if (!is_dir($baseAbs)) return $files;
  $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($baseAbs, FilesystemIterator::SKIP_DOTS));
  foreach ($it as $f) {
    if (!$f->isFile()) continue;
    $ext = strtolower(pathinfo($f->getFilename(), PATHINFO_EXTENSION));
    if (!in_array($ext, $allow, true)) continue;
    $files[] = $f->getPathname();
  }
  return $files;
}
function strip_gtag_loader_once($html){
  // Прибрати <script src="https://www.googletagmanager.com/gtag/js?id=..."></script> у conversion-фрагменті (щоб не дублювати завантаження)
  $re = '#<script[^>]*src=["\']https?://www\.googletagmanager\.com/gtag/js\?id=[^"\']+["\'][^>]*>\s*</script>\s*#i';
  return preg_replace($re, '', $html);
}

// Знайти папки з картинками серед кандидатів (+ підпапки 1-го рівня)
function discover_image_dirs($candidates){
  $found = [];
  foreach ($candidates as $rel) {
    $abs = rel_to_abs($rel);
    if (!is_dir($abs)) continue;
    $files = list_images_recursive($abs);
    if (!empty($files)) $found[] = $rel;
    // підпапки 1-го рівня
    $it = new DirectoryIterator($abs);
    foreach ($it as $entry) {
      if ($entry->isDot() || !$entry->isDir()) continue;
      $subRel = rtrim($rel, '/\\').'/'.$entry->getFilename();
      $subAbs = $entry->getPathname();
      $files2 = list_images_recursive($subAbs);
      if (!empty($files2)) $found[] = $subRel;
    }
  }
  return array_values(array_unique($found));
}

// ---------- Routing: logout ----------
if (isset($_GET['logout'])) { $_SESSION=[]; session_destroy(); header('Location: '.$_SERVER['PHP_SELF']); exit; }

// ---------- Routing: login ----------
if ($_SERVER['REQUEST_METHOD']==='POST' && ($_POST['action']??'')==='login') {
  if (!check_csrf($_POST['csrf']??'')) die('CSRF помилка');
  if (!$PASSWORD_HASH || strlen($PASSWORD_HASH) < 20) die('Пароль не налаштовано: встав bcrypt-хеш у $PASSWORD_HASH.');
  $pass = $_POST['password'] ?? '';
  if (password_verify($pass, $PASSWORD_HASH)) {
    $_SESSION['edittag_auth']=true;
    header('Location: '.$_SERVER['PHP_SELF']); exit;
  } else $login_err='Невірний пароль.';
}

// ---------- Routing: save GTAG/Conversion ----------
if ($_SERVER['REQUEST_METHOD']==='POST' && ($_POST['action']??'')==='save' && is_logged_in()) {
  if (!check_csrf($_POST['csrf']??'')) die('CSRF помилка');

  $gtag_full = trim($_POST['gtag'] ?? '');
  $conv_full = trim($_POST['conversion'] ?? '');
  $dedupe    = isset($_POST['dedupe']);

  $rel_index = sanitize_rel_path($_POST['file_index'] ?? 'index.php');
  $rel_result= sanitize_rel_path($_POST['file_result']?? 'result.php');

  $msgs=[]; $errs=[];

  if ($gtag_full==='') $errs[]='GTAG не може бути порожнім. Встав повний фрагмент з підключенням gtag.js.';

  foreach ([['index',$rel_index],['result',$rel_result]] as [$name,$rel]){
    if (!$rel || !in_array($rel,$ALLOWED_FILES,true)) $errs[]="Файл для {$name} не дозволено: ".htmlspecialchars($rel);
    else {
      $abs=rel_to_abs($rel);
      if (!is_file($abs)) $errs[]="Файл не існує: {$rel}";
      elseif (filesize($abs)>$MAX_FILE_BYTES) $errs[]="Файл завеликий (> ".bytes_fmt($MAX_FILE_BYTES)."): {$rel}";
    }
  }

  if (!$errs){
    // 1) index.* — тільки GTAG
    $inj_gtag = PHP_EOL.$GTAG_BEGIN.PHP_EOL.NL($gtag_full).PHP_EOL.$GTAG_END.PHP_EOL;

    $abs = rel_to_abs($rel_index);
    $c = file_get_contents($abs);
    if ($c===false) $errs[]="Не прочитано {$rel_index}";
    else {
      $c = remove_block($c,$GTAG_BEGIN,$GTAG_END);
      [$ok,$new]=insert_before_head($c,$inj_gtag);
      if(!$ok) $errs[]="{$rel_index}: ".$new;
      else {
        [$okb,$bmsg]=backup_file($abs); if(!$okb) $errs[]="{$rel_index}: ".$bmsg;
        else { [$okw,$wmsg]=safe_write($abs,$new); if(!$okw) $errs[]="{$rel_index}: ".$wmsg; else $msgs[]="Оновлено GTAG у «{$rel_index}»."; }
      }
    }

    // 2) result.php — GTAG + Conversion (повний, опційно без дублю gtag.js)
    $abs = rel_to_abs($rel_result);
    $c = file_get_contents($abs);
    if ($c===false) $errs[]="Не прочитано {$rel_result}";
    else {
      $c = remove_block($c,$GTAG_BEGIN,$GTAG_END);
      $c = remove_block($c,$CONV_BEGIN,$CONV_END);

      $inj_total = $inj_gtag;

      if ($conv_full!=='') {
        $conv_to_insert = $dedupe ? strip_gtag_loader_once($conv_full) : $conv_full;
        $inj_conv = PHP_EOL.$CONV_BEGIN.PHP_EOL.NL($conv_to_insert).PHP_EOL.$CONV_END.PHP_EOL;
        $inj_total .= $inj_conv;
      }

      [$ok,$new]=insert_before_head($c,$inj_total);
      if(!$ok) $errs[]="{$rel_result}: ".$new;
      else {
        [$okb,$bmsg]=backup_file($abs); if(!$okb) $errs[]="{$rel_result}: ".$bmsg;
        else { [$okw,$wmsg]=safe_write($abs,$new); if(!$okw) $errs[]="{$rel_result}: ".$wmsg; else {
          $msg="Оновлено GTAG у «{$rel_result}».";
          if ($conv_full!=='') $msg.=" Додано Conversion-код.";
          $msgs[]=$msg;
        } }
      }
    }
  }
}

// ---------- Routing: discover image dirs (JSON) ----------
if (isset($_GET['action']) && $_GET['action']==='discover_img_dirs' && is_logged_in()) {
  if (!check_csrf($_GET['csrf'] ?? '')) { http_response_code(403); echo 'CSRF'; exit; }
  $dirs = discover_image_dirs($IMG_DIR_CANDIDATES);
  if (!$dirs) $dirs = $IMG_DIR_CANDIDATES; // якщо нічого не знайшли, віддамо кандидатів
  header('Content-Type: application/json; charset=utf-8');
  echo json_encode(['dirs'=>$dirs], JSON_UNESCAPED_UNICODE);
  exit;
}

// ---------- Routing: list images (JSON) ----------
if (isset($_GET['action']) && $_GET['action']==='list_images' && is_logged_in()) {
  if (!check_csrf($_GET['csrf'] ?? '')) { http_response_code(403); echo 'CSRF'; exit; }

  // обрана папка з параметра або дефолт
  $dir = $_GET['dir'] ?? $ACTIVE_IMG_DIR;

  // дозвіл: злиття знайдених і кандидатів
  $allowedDirs = array_values(array_unique(array_merge(discover_image_dirs($IMG_DIR_CANDIDATES), $IMG_DIR_CANDIDATES)));
  if (!in_array($dir, $allowedDirs, true)) { http_response_code(400); echo 'BAD_DIR'; exit; }

  $imgRel = sanitize_rel_path($dir);
  $imgAbs = rel_to_abs($imgRel);
  if (!$imgRel || !is_dir($imgAbs)) { http_response_code(404); echo 'NOT_FOUND'; exit; }

  $absFiles = list_images_recursive($imgAbs);
  $items = [];
  $baseLen = strlen($imgAbs) + 1;
  $scriptDir = rtrim(dirname($_SERVER['PHP_SELF']), '/'); // напр. /admin
  foreach ($absFiles as $abs) {
    $local = substr($abs, $baseLen); // foo/bar.png
    $relUrl = $scriptDir . '/' . $imgRel . '/' . str_replace('\\','/',$local); // /admin/images/foo.png
    $items[] = [
      'url'   => $relUrl,
      'name'  => $local,
      'bytes' => @filesize($abs) ?: null,
    ];
  }
  header('Content-Type: application/json; charset=utf-8');
  echo json_encode(['dir'=>$imgRel,'count'=>count($items),'items'=>$items], JSON_UNESCAPED_UNICODE);
  exit;
}

// ---------- UI ----------
?>
<!doctype html>
<html lang="uk"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>EditTag Admin — GTAG, Conversion, Images</title>
<style>
  body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;margin:24px}
  .card{max-width:1000px;margin:0 auto;border:1px solid #e5e7eb;border-radius:12px;padding:20px}
  .row{margin-bottom:14px}
  textarea{width:100%;height:220px;font-family:ui-monospace,Consolas,monospace}
  input,select,button{font-size:16px;padding:8px 10px}
  .success{background:#e8f8ef;border:1px solid #b9ebcd;padding:10px;border-radius:8px;color:#146c2e}
  .error{background:#ffefef;border:1px solid #ffcaca;padding:10px;border-radius:8px;color:#b00020}
  .grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
  .muted{color:#6b7280;font-size:13px}
  code.inline{background:#f6f8fa;padding:2px 6px;border-radius:6px}
  .toolbar{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
  table{width:100%;border-collapse:collapse}
  th,td{padding:6px;border-bottom:1px solid #f3f4f6}
</style>
</head><body>
<div class="card">
  <div class="row" style="display:flex;justify-content:space-between;align-items:center">
    <h2>GTAG + Conversion у &lt;head&gt; та зображення</h2>
    <?php if (is_logged_in()): ?><a href="?logout=1">Вийти</a><?php endif; ?>
  </div>

  <?php if (!is_logged_in()): ?>
    <?php if (!empty($login_err)) echo '<div class="row error">'.htmlspecialchars($login_err).'</div>'; ?>
    <form method="post">
      <input type="hidden" name="action" value="login">
      <input type="hidden" name="csrf" value="<?php echo htmlspecialchars(csrf_token(), ENT_QUOTES); ?>">
      <div class="row">
        <label>Пароль доступу:<br><input type="password" name="password" required></label>
      </div>
      <div class="muted">Пароль порівнюється з вшитим bcrypt-хешем у коді (<code class="inline">$PASSWORD_HASH</code>).</div>
      <button type="submit">Увійти</button>
    </form>

  <?php else: ?>
    <?php
      if (!empty($errs)) echo '<div class="row error"><strong>Помилки:</strong><br>'.implode('<br>',$errs).'</div>';
      if (!empty($msgs)) echo '<div class="row success">'.implode('<br>',$msgs).'</div>';
    ?>

    <!-- Панель керування зображеннями -->
    <div class="row" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap">
      <label>Папка зображень:
        <select id="img-dir"></select>
      </label>
      <button type="button" id="btn-apply-dir">🔎 Сканувати / Показати список</button>
      <button type="button" id="btn-download-all">⬇️ Скачати ВСІ окремо</button>
      <span class="muted">Підтримувані формати: jpg, jpeg, png, gif, webp, svg, ico, bmp, avif, jfif.</span>
    </div>

    <div id="img-list" class="row" style="display:none">
      <div class="muted" id="img-summary"></div>
      <div style="margin:8px 0; display:flex; gap:8px; align-items:center">
        <label><input type="checkbox" id="chk-all"> Обрати всі</label>
        <button type="button" id="btn-download-selected">⬇️ Скачати вибрані</button>
      </div>
      <div style="max-height:420px; overflow:auto; border:1px solid #eee; border-radius:8px; padding:8px">
        <table>
          <thead>
            <tr>
              <th style="text-align:left">✓</th>
              <th style="text-align:left">Файл</th>
              <th style="text-align:right">Розмір</th>
              <th style="text-align:left">Посилання</th>
            </tr>
          </thead>
          <tbody id="img-tbody"></tbody>
        </table>
      </div>
    </div>

    <!-- Форма GTAG / Conversion -->
    <form method="post">
      <input type="hidden" name="action" value="save">
      <input type="hidden" name="csrf" value="<?php echo htmlspecialchars(csrf_token(), ENT_QUOTES); ?>">

      <div class="row">
        <label>GTAG (повний фрагмент з підключенням gtag.js):</label>
        <textarea name="gtag" placeholder="&lt;script async src=&quot;https://www.googletagmanager.com/gtag/js?id=AW-XXXXXXXXX&quot;&gt;&lt;/script&gt;
&lt;script&gt;
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'AW-XXXXXXXXX');
&lt;/script&gt;"></textarea>
        <div class="muted">Буде додано у <code class="inline">index.html / index.php</code> та <code class="inline">result.php</code> перед <code class="inline">&lt;/head&gt;</code>.</div>
      </div>

      <div class="row">
        <label>Conversion (повний фрагмент так, як дає Google):</label>
        <textarea name="conversion" placeholder="Сюди вставляй повний Global site tag або Event snippet для конверсії..."></textarea>
        <label style="display:inline-flex;align-items:center;gap:8px;margin-top:8px">
          <input type="checkbox" name="dedupe">  
          <!-- щоб поставити галочку за замовчуванням, зроби ось так <input type="checkbox" name="dedupe" checked> -->
          Прибрати дубльоване підключення <code class="inline">gtag.js</code> із Conversion-коду (рекомендовано)
        </label>
        <div class="muted">Conversion-код буде додано <u>додатково</u> лише в <code class="inline">result.php</code>.</div>
      </div>

      <div class="row grid">
        <label>Файл index:
          <select name="file_index">
            <?php foreach ($ALLOWED_FILES as $f){ if(!preg_match('/^index\.(html|php)$/i',$f)) continue; echo '<option>'.htmlspecialchars($f).'</option>'; } ?>
          </select>
        </label>
        <label>Файл result:
          <select name="file_result">
            <?php foreach ($ALLOWED_FILES as $f){ if(strtolower($f)!=='result.php') continue; echo '<option>'.htmlspecialchars($f).'</option>'; } ?>
          </select>
        </label>
      </div>

      <button type="submit">💾 Зберегти теги</button>

      <div class="row muted">
        Скрипт прибере старі блоки між
        <code class="inline">&lt;!-- BEGIN_GTAG --&gt;…&lt;!-- END_GTAG --&gt;</code> та
        <code class="inline">&lt;!-- BEGIN_CONVERSION --&gt;…&lt;!-- END_CONVERSION --&gt;</code>,
        створить резервну копію <code class="inline">*.bak_YYYYMMDD_HHMMSS</code> і вставить нові перед <code class="inline">&lt;/head&gt;</code>.
      </div>
    </form>
  <?php endif; ?>
</div>

<script>
(function(){
  const csrf = "<?php echo htmlspecialchars(csrf_token(), ENT_QUOTES); ?>";
  const q = s => document.querySelector(s);

  const dirSel  = q('#img-dir');
  const btnApply= q('#btn-apply-dir');
  const btnAll  = q('#btn-download-all');
  const wrapList= q('#img-list');
  const tbody   = q('#img-tbody');
  const summary = q('#img-summary');
  const chkAll  = q('#chk-all');
  const btnSel  = q('#btn-download-selected');

  let cached = null;

  async function loadDirs(){
    const res = await fetch(`?action=discover_img_dirs&csrf=${encodeURIComponent(csrf)}`, {credentials:'same-origin'});
    if (!res.ok) throw new Error('Помилка отримання списку папок');
    const data = await res.json();
    const dirs = data.dirs || [];
    dirSel.innerHTML = '';
    dirs.forEach(d=>{
      const opt=document.createElement('option'); opt.value=d; opt.textContent=d; dirSel.appendChild(opt);
    });
  }

  async function fetchList() {
    const dir = encodeURIComponent(dirSel?.value || '');
    const res = await fetch(`?action=list_images&dir=${dir}&csrf=${encodeURIComponent(csrf)}`, {credentials:'same-origin'});
    if (!res.ok) throw new Error('Помилка отримання списку файлів');
    return res.json();
  }

  const fmtBytes = b => {
    if (b == null) return '—';
    const u = ['B','KB','MB','GB']; let i=0, v=b;
    while (v>=1024 && i<u.length-1){ v/=1024; i++; }
    return (Math.round(v*100)/100)+' '+u[i];
  };

  async function downloadOne(url, name) {
    const res = await fetch(url, {credentials:'same-origin'});
    if (!res.ok) throw new Error('Помилка скачування: '+url);
    const blob = await res.blob();
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = name || url.split('/').pop();
    document.body.appendChild(a);
    a.click();
    setTimeout(()=>{ URL.revokeObjectURL(a.href); a.remove(); }, 4000);
  }

  async function downloadMany(items, statusEl) {
    let ok=0, fail=0;
    for (let i=0; i<items.length; i++) {
      const it = items[i];
      statusEl && (statusEl.textContent = `Скачуємо ${i+1}/${items.length}…`);
      try { await downloadOne(it.url, it.name); ok++; }
      catch(e){ console.warn(e); fail++; }
      await new Promise(r=>setTimeout(r, 250));
    }
    statusEl && (statusEl.textContent = `Готово: ${ok} успішно, ${fail} з помилкою.`);
  }

  function renderTable(items){
    tbody.innerHTML = '';
    let total = 0;
    items.forEach(it=>{
      total += (it.bytes||0);
      const tr = document.createElement('tr');
      tr.innerHTML = `
        <td><input type="checkbox" data-url="${it.url}" data-name="${it.name}"></td>
        <td>${escapeHtml(it.name)}</td>
        <td style="text-align:right">${fmtBytes(it.bytes)}</td>
        <td><a href="${it.url}" target="_blank" rel="noopener">відкрити</a></td>
      `;
      tbody.appendChild(tr);
    });
    summary.textContent = `Папка: ${dirSel.value} — Файлів: ${items.length}, сумарно ≈ ${fmtBytes(total)}.`;
    chkAll.checked = false;
  }

  function escapeHtml(s){
    return (s||'').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
  }

  // Події
  btnApply?.addEventListener('click', async ()=>{
    wrapList.style.display = 'block';
    summary.textContent = 'Сканування…';
    try { cached = await fetchList(); renderTable(cached.items || []); }
    catch(e){ summary.textContent = 'Помилка: ' + e.message; }
  });

  btnAll?.addEventListener('click', async ()=>{
    try { if (!cached) { cached = await fetchList(); renderTable(cached.items || []); wrapList.style.display='block'; } }
    catch(e){ return alert('Помилка: '+e.message); }
    if (!cached.items || !cached.items.length) return alert('Зображень не знайдено.');
    await downloadMany(cached.items, summary);
  });

  chkAll?.addEventListener('change', ()=>{
    tbody.querySelectorAll('input[type="checkbox"]').forEach(ch => ch.checked = chkAll.checked);
  });

  btnSel?.addEventListener('click', async ()=>{
    const selected = [...tbody.querySelectorAll('input[type="checkbox"]:checked')].map(ch => ({
      url: ch.dataset.url,
      name: ch.dataset.name
    }));
    if (!selected.length) return alert('Нічого не вибрано.');
    await downloadMany(selected, summary);
  });

  // Ініціалізація: підвантажити варіанти папок
  loadDirs().catch(e=>console.warn(e));
})();
</script>
</body></html>