Server : LiteSpeed
System : Linux terra.hostitbro.com 5.14.0-611.54.3.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Thu May 7 16:31:24 EDT 2026 x86_64
User : outerorb ( 1091)
PHP Version : 8.1.34
Disable Function : mail
Directory :  /home2/outerorb/.trash/mplanner/

📁 Create New:
⬆️ Upload File:
Current Dir [ Writable ] Root [ Writable ]


OR Upload from URL:
URL: Save as:

📄 File: index.php

Path: /home2/outerorb/.trash/mplanner/index.php

Size: 36.97 KB

Permissions: 0644

<?php
/*
  index.php - OTT Weekend Content Planner (Server-side OpenAI + PHP + SQLite)
  Single-file app (PHP + HTML + CSS + JS)
  - Uses OpenAI Responses API (server-side) with provided API key
  - Stores users & history in SQLite (mplan_lib.sqlite)
  - Endpoints:
     * signup, login, logout (session)
     * generate (calls OpenAI /v1/responses) -> returns strict parsed JSON recommendations
     * save_search (store results in history)
     * get_history (retrieve user's history)
  - No local/mocked suggestions: on AI failure, shows explicit error
  - All IDs/classes prefixed with mplan_lib
*/

/* ---------------- CONFIG ---------------- */
session_start();

// Put your OpenAI key here (user-provided)
define('MPLAN_LIB_OPENAI_KEY', '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');

// OpenAI endpoint
define('MPLAN_LIB_OPENAI_ENDPOINT', 'https://api.openai.com/v1/responses');

// SQLite DB file (in same directory)
define('MPLAN_LIB_DB', __DIR__ . '/mplan_lib.sqlite');

/* ---------------- DB SETUP ---------------- */
function mplan_lib_db() {
  $dbfile = MPLAN_LIB_DB;
  $init = !file_exists($dbfile);
  $pdo = new PDO('sqlite:' . $dbfile);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  if ($init) {
    $pdo->exec("PRAGMA journal_mode = WAL;");
    $pdo->exec("CREATE TABLE users (
      username TEXT PRIMARY KEY,
      password_hash TEXT NOT NULL
    );");
    $pdo->exec("CREATE TABLE history (
      id TEXT PRIMARY KEY,
      username TEXT NOT NULL,
      title TEXT NOT NULL,
      created_at TEXT NOT NULL,
      prefs_json TEXT NOT NULL,
      results_json TEXT NOT NULL,
      FOREIGN KEY(username) REFERENCES users(username)
    );");
  }
  return $pdo;
}

/* ---------------- Helpers ---------------- */
function mplan_lib_user() {
  return $_SESSION['mplan_lib_user'] ?? null;
}
function mplan_lib_json_header() {
  header('Content-Type: application/json; charset=utf-8');
}
function mplan_lib_now_iso() {
  return gmdate('c');
}

/* ---------------- AJAX / ACTIONS ---------------- */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $action = $_POST['mplan_lib_action'] ?? '';

  // SIGNUP
  if ($action === 'signup') {
    mplan_lib_json_header();
    $username = trim($_POST['mplan_lib_username'] ?? '');
    $password = $_POST['mplan_lib_password'] ?? '';
    $confirm  = $_POST['mplan_lib_confirm'] ?? '';
    if ($username === '' || $password === '' || $confirm === '') {
      echo json_encode(['ok'=>false,'error'=>'All fields required']); exit;
    }
    if (!preg_match('/^[A-Za-z0-9._-]{3,32}$/', $username)) {
      echo json_encode(['ok'=>false,'error'=>'Username invalid (3–32 chars: letters/numbers/_ . -)']); exit;
    }
    if ($password !== $confirm) {
      echo json_encode(['ok'=>false,'error'=>'Passwords do not match']); exit;
    }
    $pdo = mplan_lib_db();
    $stmt = $pdo->prepare('SELECT 1 FROM users WHERE username = ?');
    $stmt->execute([$username]);
    if ($stmt->fetch()) {
      echo json_encode(['ok'=>false,'error'=>'Username already exists']); exit;
    }
    $hash = password_hash($password, PASSWORD_DEFAULT);
    $stmt = $pdo->prepare('INSERT INTO users (username, password_hash) VALUES (?, ?)');
    $stmt->execute([$username, $hash]);
    $_SESSION['mplan_lib_user'] = $username;
    echo json_encode(['ok'=>true,'user'=>$username]); exit;
  }

  // LOGIN
  if ($action === 'login') {
    mplan_lib_json_header();
    $username = trim($_POST['mplan_lib_username'] ?? '');
    $password = $_POST['mplan_lib_password'] ?? '';
    if ($username === '' || $password === '') { echo json_encode(['ok'=>false,'error'=>'All fields required']); exit; }
    $pdo = mplan_lib_db();
    $stmt = $pdo->prepare('SELECT password_hash FROM users WHERE username = ?');
    $stmt->execute([$username]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$row || !password_verify($password, $row['password_hash'])) {
      echo json_encode(['ok'=>false,'error'=>'Invalid credentials']); exit;
    }
    $_SESSION['mplan_lib_user'] = $username;
    echo json_encode(['ok'=>true,'user'=>$username]); exit;
  }

  // LOGOUT
  if ($action === 'logout') {
    mplan_lib_json_header();
    session_destroy();
    echo json_encode(['ok'=>true]); exit;
  }

  // GENERATE (call OpenAI Responses API server-side)
  if ($action === 'generate') {
    mplan_lib_json_header();
    // Accept preferences (allow generation even if not logged in)
    $genre = trim($_POST['mplan_lib_genre'] ?? '');
    $mood  = trim($_POST['mplan_lib_mood'] ?? '');
    $platform = trim($_POST['mplan_lib_platform'] ?? '');
    $duration = trim($_POST['mplan_lib_duration'] ?? '');
    if ($genre === '' || $mood === '' || $platform === '' || $duration === '') {
      echo json_encode(['ok'=>false,'error'=>'All fields are required']); exit;
    }

    // Build strict prompt instructing JSON-only output
    $system = "You are an OTT weekend content recommendation assistant for users in India. Return STRICT JSON only with this exact schema and nothing else:

{
  \"recommendations\": [
    {
      \"title\": \"string\",
      \"type\": \"movie\" | \"series\",
      \"imdb\": \"string or N/A\",
      \"year\": \"optional year number or empty string\",
      \"synopsis\": \"short <= 40 words\",
      \"trailer\": \"https URL or empty string\",
      \"ott\": \"https URL or empty string\",
      \"poster\": \"https URL or empty string\",
      \"platforms\": [\"Netflix\",\"Prime Video\",\"Hotstar\",\"JioCinema\",\"Sony LIV\",\"ZEE5\",\"Apple TV+\"]
    }
  ]
}

Rules:
- Return exactly 5 recommendation objects.
- Ensure titles are realistic/popular and likely available in India.
- If you cannot provide a valid URL for trailer or ott, return an empty string for that field and include platform names in platforms array.
- No extra commentary or text outside the JSON.";
    $user = "User preferences:
Genre: {$genre}
Mood: {$mood}
Preferred platform: {$platform}
Duration: {$duration}
Return the JSON now.";

    // Prepare payload according to OpenAI Responses API
    $payload = [
      "model" => "gpt-4o-mini",
      "input" => $system . "\n\n" . $user,
      "temperature" => 0.6,
      "max_output_tokens" => 1200
    ];

    // Call OpenAI via cURL
    $ch = curl_init(MPLAN_LIB_OPENAI_ENDPOINT);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      "Content-Type: application/json",
      "Authorization: Bearer " . MPLAN_LIB_OPENAI_KEY
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    $resp = curl_exec($ch);
    $err = curl_error($ch);
    $http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($err || !$resp || $http >= 400) {
      // return explicit error (no fallback)
      $msg = $err ?: ('OpenAI HTTP ' . $http);
      echo json_encode(['ok'=>false,'error'=>'AI service unavailable: ' . $msg]); exit;
    }

    // Parse OpenAI response. Responses API returns 'output' array. Extract textual content.
    $j = json_decode($resp, true);
    $text = '';
    if (isset($j['output']) && is_array($j['output'])) {
      foreach ($j['output'] as $part) {
        if (isset($part['content']) && is_array($part['content'])) {
          foreach ($part['content'] as $c) {
            // 'type' may be 'output_text' with 'text', or content may have 'text' directly
            if (isset($c['text'])) $text .= $c['text'];
            elseif (isset($c['type']) && $c['type'] === 'output_text' && isset($c['text'])) $text .= $c['text'];
            // Some shapes may have 'content' => ['type'=>'output_text','text'=>...]
          }
        }
      }
    }
    // Fallback extraction: some API responses include 'output_text' top-level
    if ($text === '' && isset($j['output_text'])) {
      $text = $j['output_text'];
    }
    if ($text === '' && isset($j['choices']) && is_array($j['choices'])) {
      // older-style choices
      foreach ($j['choices'] as $chc) {
        if (isset($chc['text'])) $text .= $chc['text'];
        elseif (isset($chc['message']['content'])) $text .= $chc['message']['content'];
      }
    }

    if ($text === '') {
      // As a last resort try to json_encode the whole response body and search for JSON object
      $raw = $resp;
      $m = [];
      if (preg_match('/\{.*"recommendations".*\}/s', $raw, $m)) {
        $text = $m[0];
      }
    }

    if ($text === '') {
      echo json_encode(['ok'=>false,'error'=>'AI returned empty or unparsable response']); exit;
    }

    // Attempt to parse JSON from text
    $parsed = json_decode($text, true);
    if (!$parsed) {
      // try to extract first JSON object substring
      if (preg_match('/\{[\s\S]*\}/', $text, $m)) {
        $candidate = $m[0];
        $parsed = json_decode($candidate, true);
      }
    }

    if (!$parsed || !isset($parsed['recommendations']) || !is_array($parsed['recommendations'])) {
      echo json_encode(['ok'=>false,'error'=>'AI returned invalid JSON structure']); exit;
    }

    // Normalize recommendations: ensure each has required keys
    $results = [];
    foreach ($parsed['recommendations'] as $rec) {
      if (!isset($rec['title']) || trim($rec['title']) === '') continue;
      $results[] = [
        'title' => $rec['title'],
        'type'  => $rec['type'] ?? 'movie',
        'imdb'  => $rec['imdb'] ?? 'N/A',
        'year'  => $rec['year'] ?? '',
        'synopsis' => $rec['synopsis'] ?? '',
        'trailer'  => $rec['trailer'] ?? '',
        'ott'      => $rec['ott'] ?? '',
        'poster'   => $rec['poster'] ?? '',
        'platforms'=> is_array($rec['platforms']) ? $rec['platforms'] : []
      ];
    }

    // Ensure we have at least 1 result; otherwise error
    if (count($results) < 1) {
      echo json_encode(['ok'=>false,'error'=>'AI returned no valid recommendations']); exit;
    }

    // Return parsed recommendations to client
    echo json_encode(['ok'=>true,'recommendations'=>$results]); exit;
  }

  // SAVE SEARCH - requires login
  if ($action === 'save_search') {
    mplan_lib_json_header();
    $user = mplan_lib_user();
    if (!$user) { echo json_encode(['ok'=>false,'error'=>'Not logged in']); exit; }
    $title = trim($_POST['mplan_lib_title'] ?? '');
    $prefs = json_decode($_POST['mplan_lib_prefs'] ?? 'null', true);
    $results = json_decode($_POST['mplan_lib_results'] ?? 'null', true);
    if ($title === '' || !is_array($prefs) || !is_array($results)) { echo json_encode(['ok'=>false,'error'=>'Invalid payload']); exit; }
    $pdo = mplan_lib_db();
    $id = bin2hex(random_bytes(8));
    $stmt = $pdo->prepare('INSERT INTO history (id, username, title, created_at, prefs_json, results_json) VALUES (?, ?, ?, ?, ?, ?)');
    $stmt->execute([$id, $user, $title, mplan_lib_now_iso(), json_encode($prefs), json_encode($results)]);
    echo json_encode(['ok'=>true,'entry'=>['id'=>$id,'title'=>$title,'date'=>mplan_lib_now_iso()]]); exit;
  }

  // GET HISTORY - requires login
  if ($action === 'get_history') {
    mplan_lib_json_header();
    $user = mplan_lib_user();
    if (!$user) { echo json_encode(['ok'=>true,'history'=>[]]); exit; }
    $pdo = mplan_lib_db();
    $stmt = $pdo->prepare('SELECT id, title, created_at, prefs_json, results_json FROM history WHERE username = ? ORDER BY created_at DESC');
    $stmt->execute([$user]);
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
    $hist = [];
    foreach ($rows as $r) {
      $hist[] = [
        'id' => $r['id'],
        'title' => $r['title'],
        'date' => $r['created_at'],
        'prefs' => json_decode($r['prefs_json'], true),
        'results' => json_decode($r['results_json'], true)
      ];
    }
    echo json_encode(['ok'=>true,'history'=>$hist]); exit;
  }

  // Unknown action
  mplan_lib_json_header();
  echo json_encode(['ok'=>false,'error'=>'Unknown action']); exit;
}
/* ---------------- END ACTIONS ---------------- */

/* ---------------- PAGE STATE ---------------- */
$current_user = mplan_lib_user();
$history_items = [];
if ($current_user) {
  $pdo = mplan_lib_db();
  $stmt = $pdo->prepare('SELECT id, title, created_at FROM history WHERE username = ? ORDER BY created_at DESC');
  $stmt->execute([$current_user]);
  $history_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
}

/* ---------------- HTML / UI ---------------- */
?><!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>OTT Weekend Content Planner • mplan_lib</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
:root{
  --mplan_lib-bg:#071019; --mplan_lib-card:#0f1720; --mplan_lib-accent:#3ee7c8;
  --mplan_lib-text:#e6eef2; --mplan_lib-sub:#9fb4c6; --mplan_lib-border:#203241;
  --mplan_lib-error:#ffb4b4; --mplan_lib-good:#9ff7d7;
}
body.mplan_lib-body{margin:0;background:radial-gradient(1200px 600px at 10% -10%, #083a50 0%, #071019 60%),var(--mplan_lib-bg);color:var(--mplan_lib-text);font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif}
.mplan_lib-wrap{max-width:1080px;margin:28px auto;padding:0 16px}
.mplan_lib-card{background:linear-gradient(180deg,rgba(255,255,255,0.02),rgba(255,255,255,0)),var(--mplan_lib-card);border:1px solid var(--mplan_lib-border);border-radius:12px;padding:14px;box-shadow:0 8px 28px rgba(0,0,0,.35)}
.mplan_lib-header{display:flex;gap:12px;align-items:center;margin-bottom:14px}
.mplan_lib-logo{width:48px;height:48px;border-radius:12px;background:conic-gradient(from 180deg,#52d2ff,#3ee7c8,#a78bfa,#52d2ff);box-shadow:0 6px 20px rgba(34,170,220,.12)}
.mplan_lib-title{font-weight:800;font-size:clamp(18px,3.4vw,26px)}
.mplan_lib-sub{color:var(--mplan_lib-sub);font-size:13px}
.mplan_lib-grid{display:grid;grid-template-columns:320px 1fr;gap:14px}
.mplan_lib-panel{display:flex;flex-direction:column;gap:10px}
.mplan_lib-label{font-size:12px;color:var(--mplan_lib-sub);text-transform:uppercase;letter-spacing:.08em}
.mplan_lib-select,.mplan_lib-input,.mplan_lib-button{background:#0c1116;color:var(--mplan_lib-text);border:1px solid var(--mplan_lib-border);border-radius:8px;padding:9px 10px;font-size:14px;outline:none}
.mplan_lib-select:focus,.mplan_lib-input:focus{box-shadow:0 0 0 4px rgba(62,231,200,.08);border-color:var(--mplan_lib-accent)}
.mplan_lib-button{cursor:pointer;font-weight:700;border-color:#204a5b}
.mplan_lib-button[disabled]{opacity:.6;cursor:not-allowed}
.mplan_lib-note{color:var(--mplan_lib-sub);font-size:13px}
.mplan_lib-loginTabs{display:flex;gap:8px}
.mplan_lib-tabBtn{padding:8px 10px;border-radius:8px;border:1px solid var(--mplan_lib-border);background:#0a0f14;color:var(--mplan_lib-text);cursor:pointer}
.mplan_lib-tabBtn.mplan_lib-active{border-color:var(--mplan_lib-accent)}
.mplan_lib-historyList{display:flex;flex-direction:column;gap:8px;max-height:520px;overflow:auto}
.mplan_lib-historyItem{display:flex;justify-content:space-between;align-items:center;gap:8px;border:1px solid var(--mplan_lib-border);border-radius:8px;padding:10px;background:#08121a;cursor:pointer}
.mplan_lib-historyItem:hover{border-color:var(--mplan_lib-accent)}
.mplan_lib-date{color:var(--mplan_lib-sub);font-size:12px}
.mplan_lib-results{display:grid;gap:12px}
.mplan_lib-cardItem{display:grid;grid-template-columns:92px 1fr;gap:12px;padding:12px;border-radius:8px;background:linear-gradient(180deg,rgba(255,255,255,0.01),transparent)}
.mplan_lib-poster{width:92px;height:128px;border-radius:8px;background:#08121a;object-fit:cover;border:1px solid var(--mplan_lib-border)}
.mplan_lib-itemTitle{font-weight:700;font-size:16px;display:flex;gap:8px;align-items:center}
.mplan_lib-chip{font-size:12px;border-radius:999px;padding:4px 8px;border:1px solid var(--mplan_lib-border);color:var(--mplan_lib-sub)}
.mplan_lib-imdb{color:#ffd26a;font-weight:700}
.mplan_lib-meta{font-size:13px;color:var(--mplan_lib-sub);margin-top:4px}
.mplan_lib-syn{margin-top:8px;line-height:1.45;font-size:14px;color:var(--mplan_lib-text)}
.mplan_lib-links{margin-top:10px;display:flex;flex-wrap:wrap;gap:8px}
.mplan_lib-link{font-size:13px;border-radius:8px;padding:8px 10px;border:1px solid var(--mplan_lib-border);text-decoration:none;color:var(--mplan_lib-text);background:#051219}
.mplan_lib-link:hover{border-color:var(--mplan_lib-accent)}
.mplan_lib-topbar{display:flex;justify-content:space-between;align-items:center;gap:10px}
@media (max-width:980px){.mplan_lib-grid{grid-template-columns:1fr}}
@media (max-width:680px){.mplan_lib-cardItem{grid-template-columns:72px 1fr}.mplan_lib-poster{width:72px;height:108px}}
</style>
</head>
<body class="mplan_lib-body">
  <div class="mplan_lib-wrap">
    <header class="mplan_lib-header">
      <div class="mplan_lib-logo" aria-hidden="true"></div>
      <div>
        <div class="mplan_lib-title">OTT Weekend Content Planner</div>
        <div class="mplan_lib-sub">Server-side OpenAI (gpt-4o-mini) • Save searches to your account • Strict JSON output</div>
      </div>
    </header>

<?php if (!$current_user): ?>
  <!-- AUTH PANEL -->
  <section class="mplan_lib-card mplan_lib-panel">
    <div class="mplan_lib-topbar">
      <div class="mplan_lib-chip">Create account to save your plans</div>
      <div class="mplan_lib-loginTabs">
        <button id="mplan_lib_tab_login" class="mplan_lib-tabBtn mplan_lib-active">Login</button>
        <button id="mplan_lib_tab_signup" class="mplan_lib-tabBtn">Sign Up</button>
      </div>
    </div>
    <div id="mplan_lib_auth_msg" class="mplan_lib-note" style="min-height:22px"></div>

    <div id="mplan_lib_login_form">
      <label class="mplan_lib-label">Login</label>
      <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
        <input id="mplan_lib_login_username" class="mplan_lib-input" placeholder="Username">
        <input id="mplan_lib_login_password" class="mplan_lib-input" type="password" placeholder="Password">
      </div>
      <button id="mplan_lib_login_btn" class="mplan_lib-button" style="margin-top:8px">Login</button>
    </div>

    <div id="mplan_lib_signup_form" style="display:none">
      <label class="mplan_lib-label">Sign Up</label>
      <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
        <input id="mplan_lib_signup_username" class="mplan_lib-input" placeholder="Choose username">
        <input id="mplan_lib_signup_password" class="mplan_lib-input" type="password" placeholder="Password">
      </div>
      <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:6px">
        <input id="mplan_lib_signup_confirm" class="mplan_lib-input" type="password" placeholder="Confirm password">
        <div class="mplan_lib-note">3–32 chars: letters/numbers/_ . -</div>
      </div>
      <button id="mplan_lib_signup_btn" class="mplan_lib-button" style="margin-top:8px">Create account & continue</button>
    </div>

    <div class="mplan_lib-note">You may try the planner below even without an account; saving requires login.</div>
  </section>

  <!-- Planner (try without login) -->
  <section class="mplan_lib-card mplan_lib-panel" style="margin-top:12px">
    <div class="mplan_lib-topbar">
      <div class="mplan_lib-chip">Try Planner (no account required)</div>
      <div class="mplan_lib-note">Server (PHP) calls OpenAI. If it fails you will see an error message.</div>
    </div>
    <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
      <div class="mplan_lib-panel">
        <label class="mplan_lib-label">Genre</label>
        <select id="mplan_lib_genre" class="mplan_lib-select">
          <option value="">Choose…</option>
          <option>Action</option><option>Thriller</option><option>Crime</option><option>Drama</option><option>Comedy</option>
          <option>Romance</option><option>Sci-Fi</option><option>Fantasy</option><option>Documentary</option><option>Horror</option><option>Family</option>
        </select>
        <label class="mplan_lib-label">Mood</label>
        <select id="mplan_lib_mood" class="mplan_lib-select">
          <option value="">Choose…</option>
          <option>Feel-good</option><option>Adrenaline</option><option>Thoughtful</option><option>Dark & Gritty</option>
          <option>Romantic</option><option>Family Night</option><option>Mind-bending</option><option>Chill</option><option>Laugh Out Loud</option>
        </select>
        <label class="mplan_lib-label">Preferred OTT</label>
        <select id="mplan_lib_platform" class="mplan_lib-select">
          <option value="">Choose…</option>
          <option>Netflix</option><option>Prime Video</option><option>Hotstar</option><option>JioCinema</option><option>Sony LIV</option><option>ZEE5</option><option>Apple TV+</option><option>Any</option>
        </select>
        <label class="mplan_lib-label">Duration</label>
        <select id="mplan_lib_duration" class="mplan_lib-select">
          <option value="">Choose…</option>
          <option>Short binge (2–4h)</option><option>Medium (4–6h)</option><option>Long binge (6–12h)</option>
        </select>
        <div style="margin-top:8px;display:flex;align-items:center;gap:8px">
          <input id="mplan_lib_save_consent" type="checkbox"><label for="mplan_lib_save_consent" class="mplan_lib-note">Ask to save results</label>
        </div>
        <button id="mplan_lib_generate_btn" class="mplan_lib-button" style="margin-top:10px">Generate Weekend Plan</button>
      </div>

      <div class="mplan_lib-panel">
        <div id="mplan_lib_results_msg" style="min-height:26px"></div>
        <div id="mplan_lib_results" class="mplan_lib-results"></div>
      </div>
    </div>
  </section>

<?php else: ?>
  <!-- DASHBOARD -->
  <section class="mplan_lib-card mplan_lib-panel">
    <div class="mplan_lib-topbar">
      <div class="mplan_lib-chip">Hi, <?= htmlspecialchars($current_user) ?> — your dashboard</div>
      <div style="display:flex;gap:8px;align-items:center">
        <button id="mplan_lib_logout_btn" class="mplan_lib-button">Logout</button>
      </div>
    </div>

    <div class="mplan_lib-grid" style="margin-top:8px">
      <aside class="mplan_lib-panel">
        <label class="mplan_lib-label">Your saved searches</label>
        <div id="mplan_lib_history" class="mplan_lib-historyList">
          <?php if (empty($history_items)): ?>
            <div class="mplan_lib-note">No saved searches yet.</div>
          <?php else: foreach ($history_items as $h): ?>
            <div class="mplan_lib-historyItem" data-mplan_lib-id="<?= htmlspecialchars($h['id']) ?>">
              <div>
                <div><?= htmlspecialchars($h['title']) ?></div>
                <div class="mplan_lib-date"><?= htmlspecialchars($h['created_at'] ?? $h['date'] ?? '') ?></div>
              </div>
              <div class="mplan_lib-chip">Open ▸</div>
            </div>
          <?php endforeach; endif; ?>
        </div>
      </aside>

      <main class="mplan_lib-panel">
        <div class="mplan_lib-chip">New Search</div>
        <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
          <div>
            <label class="mplan_lib-label">Genre</label>
            <select id="mplan_lib_genre" class="mplan_lib-select">
              <option value="">Choose…</option>
              <option>Action</option><option>Thriller</option><option>Crime</option><option>Drama</option><option>Comedy</option>
              <option>Romance</option><option>Sci-Fi</option><option>Fantasy</option><option>Documentary</option><option>Horror</option><option>Family</option>
            </select>
          </div>
          <div>
            <label class="mplan_lib-label">Mood</label>
            <select id="mplan_lib_mood" class="mplan_lib-select">
              <option value="">Choose…</option>
              <option>Feel-good</option><option>Adrenaline</option><option>Thoughtful</option><option>Dark & Gritty</option>
              <option>Romantic</option><option>Family Night</option><option>Mind-bending</option><option>Chill</option><option>Laugh Out Loud</option>
            </select>
          </div>
        </div>

        <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:8px">
          <div>
            <label class="mplan_lib-label">Preferred OTT</label>
            <select id="mplan_lib_platform" class="mplan_lib-select">
              <option value="">Choose…</option>
              <option>Netflix</option><option>Prime Video</option><option>Hotstar</option><option>JioCinema</option><option>Sony LIV</option><option>ZEE5</option><option>Apple TV+</option><option>Any</option>
            </select>
          </div>
          <div>
            <label class="mplan_lib-label">Duration</label>
            <select id="mplan_lib_duration" class="mplan_lib-select">
              <option value="">Choose…</option>
              <option>Short binge (2–4h)</option><option>Medium (4–6h)</option><option>Long binge (6–12h)</option>
            </select>
          </div>
        </div>

        <div style="margin-top:8px;display:flex;align-items:center;gap:8px">
          <input id="mplan_lib_save_consent" type="checkbox" checked><label for="mplan_lib_save_consent" class="mplan_lib-note">Save this search</label>
        </div>

        <button id="mplan_lib_generate_btn" class="mplan_lib-button" style="margin-top:8px">Generate Weekend Plan</button>

        <div id="mplan_lib_results_msg" style="margin-top:12px;min-height:26px"></div>
        <div id="mplan_lib_results" class="mplan_lib-results" style="margin-top:8px"></div>
      </main>
    </div>
  </section>
<?php endif; ?>

  <footer style="margin:18px 4px" class="mplan_lib-note">Server-side OpenAI • sqlite storage • Strict JSON parsing • All classes/IDs prefixed with mplan_lib</footer>
  </div>

<script>
/* ---------------- Client-side JS: UI, AJAX, render ---------------- */
function mplan_lib_msg(text, type='note') {
  const el = document.getElementById('mplan_lib_results_msg') || document.getElementById('mplan_lib_auth_msg');
  if (!el) return;
  el.className = type === 'error' ? 'mplan_lib-error' : (type === 'success' ? 'mplan_lib-success' : 'mplan_lib-note');
  el.textContent = text;
}
function mplan_lib_trailerLink(title){ return 'https://www.youtube.com/results?search_query=' + encodeURIComponent(title + ' official trailer'); }
function mplan_lib_platformLink(platform, title){ 
  const q = encodeURIComponent(title);
  switch ((platform||'').toLowerCase()){
    case 'netflix': return title ? `https://www.netflix.com/search?q=${q}` : 'https://www.netflix.com/in/';
    case 'prime video': return title ? `https://www.primevideo.com/search?phrase=${q}` : 'https://www.primevideo.com/';
    case 'hotstar': case 'disney+ hotstar': return title ? `https://www.hotstar.com/in/search?q=${q}` : 'https://www.hotstar.com/in/';
    case 'jiocinema': return title ? `https://www.jiocinema.com/search/${q}` : 'https://www.jiocinema.com/';
    case 'sony liv': case 'sonyliv': return title ? `https://www.sonyliv.com/search/${q}` : 'https://www.sonyliv.com/';
    case 'zee5': return title ? `https://www.zee5.com/search?q=${q}` : 'https://www.zee5.com/';
    case 'apple tv+': case 'apple tv': return title ? `https://tv.apple.com/in/search?term=${q}` : 'https://tv.apple.com/in';
    default: return title ? `https://www.google.com/search?q=${encodeURIComponent(title + ' watch online')}` : 'https://www.justwatch.com/in';
  }
}
function mplan_lib_buildTitle(prefs){
  const d = new Date();
  const label = d.toLocaleString(undefined, {year:'numeric', month:'short', day:'2-digit'});
  return `${prefs.genre} • ${prefs.mood} • ${prefs.platform} • ${prefs.duration} — ${label}`;
}
function mplan_lib_renderResults(items){
  const box = document.getElementById('mplan_lib_results');
  if (!box) return;
  box.innerHTML = '';
  items.forEach(it=>{
    const poster = it.poster || 'https://images.unsplash.com/photo-1489599849927-2ee91cede3ba?q=80&w=400&auto=format&fit=crop';
    const imdbBadge = (it.imdb && it.imdb!=='N/A') ? `<span class="mplan_lib-chip">IMDb <span class="mplan_lib-imdb">${it.imdb}</span></span>` : '';
    const type = it.type || 'movie';
    const yearPart = it.year ? ` • ${it.year}` : '';
    const typeBadge = `<span class="mplan_lib-chip">${type.charAt(0).toUpperCase()+type.slice(1)}${yearPart}</span>`;
    const links = [];
    if (it.trailer) links.push(`<a class="mplan_lib-link" href="${it.trailer}" target="_blank" rel="noopener">Watch trailer ▸</a>`);
    if (it.ott) links.push(`<a class="mplan_lib-link" href="${it.ott}" target="_blank" rel="noopener">Open OTT ▸</a>`);
    if (it.platforms && Array.isArray(it.platforms)) {
      it.platforms.forEach(p => links.push(`<a class="mplan_lib-link" href="${mplan_lib_platformLink(p, it.title)}" target="_blank" rel="noopener">Open on ${p}</a>`));
    }
    const el = document.createElement('article');
    el.className = 'mplan_lib-card mplan_lib-cardItem';
    el.innerHTML = `
      <img class="mplan_lib-poster" src="${poster}" alt="${it.title} poster" loading="lazy">
      <div>
        <div class="mplan_lib-itemTitle">
          <span>${it.title}</span>
          ${imdbBadge}
          ${typeBadge}
        </div>
        <div class="mplan_lib-syn">${it.synopsis || 'Synopsis not provided.'}</div>
        <div class="mplan_lib-links">${links.join('')}</div>
      </div>
    `;
    box.appendChild(el);
  });
}

/* ---------------- AJAX helper ---------------- */
async function mplan_lib_postForm(formData) {
  const resp = await fetch('', { method: 'POST', body: formData });
  return resp.json();
}

/* ---------------- Generate (calls server /generate) ---------------- */
async function mplan_lib_generate() {
  const genre = document.getElementById('mplan_lib_genre').value.trim();
  const mood  = document.getElementById('mplan_lib_mood').value.trim();
  const platform = document.getElementById('mplan_lib_platform').value.trim();
  const duration = document.getElementById('mplan_lib_duration').value.trim();
  if (!genre || !mood || !platform || !duration) { mplan_lib_msg('Please fill all fields.', 'error'); return; }
  mplan_lib_msg('Generating — contacting OpenAI (server)...', 'note');
  const fd = new FormData();
  fd.append('mplan_lib_action', 'generate');
  fd.append('mplan_lib_genre', genre);
  fd.append('mplan_lib_mood', mood);
  fd.append('mplan_lib_platform', platform);
  fd.append('mplan_lib_duration', duration);
  try {
    const j = await mplan_lib_postForm(fd);
    if (!j.ok) { mplan_lib_msg(j.error || 'AI service unavailable.', 'error'); document.getElementById('mplan_lib_results').innerHTML = ''; return; }
    const recs = j.recommendations || [];
    // ensure each rec has trailer/ott links
    const items = recs.map(r=>({
      title: r.title || '',
      type: r.type || 'movie',
      imdb: r.imdb || 'N/A',
      year: r.year || '',
      synopsis: r.synopsis || '',
      trailer: r.trailer || mplan_lib_trailerLink(r.title || ''),
      ott: r.ott || mplan_lib_platformLink(platform==='Any'?'':platform, r.title || ''),
      poster: r.poster || '',
      platforms: Array.isArray(r.platforms) ? r.platforms : []
    }));
    mplan_lib_renderResults(items);
    mplan_lib_msg('AI returned results. Use "Save this search" to store it.', 'success');

    // If user asked to save (checkbox) and is logged in, call save_search automatically
    const wantSave = !!(document.getElementById('mplan_lib_save_consent') && document.getElementById('mplan_lib_save_consent').checked);
    const isLoggedIn = <?= $current_user ? 'true' : 'false'; ?>;
    if (wantSave && isLoggedIn) {
      const prefs = { genre, mood, platform, duration };
      const title = mplan_lib_buildTitle(prefs);
      const fd2 = new FormData();
      fd2.append('mplan_lib_action','save_search');
      fd2.append('mplan_lib_title', title);
      fd2.append('mplan_lib_prefs', JSON.stringify(prefs));
      fd2.append('mplan_lib_results', JSON.stringify(items));
      const saved = await mplan_lib_postForm(fd2);
      if (saved.ok) {
        mplan_lib_msg('Saved to your account.', 'success');
        // update history UI if present
        const list = document.getElementById('mplan_lib_history');
        if (list) {
          const div = document.createElement('div');
          div.className = 'mplan_lib-historyItem';
          div.dataset.mplan_libId = saved.entry.id;
          div.innerHTML = `<div><div>${saved.entry.title}</div><div class="mplan_lib-date">${saved.entry.date}</div></div><div class="mplan_lib-chip">Open ▸</div>`;
          list.prepend(div);
        }
      } else {
        mplan_lib_msg('Saved failed: ' + (saved.error || 'unknown'), 'error');
      }
    }
  } catch (err) {
    console.error(err);
    mplan_lib_msg('AI service unavailable. Please try again later.', 'error');
    document.getElementById('mplan_lib_results').innerHTML = '';
  }
}

/* ---------------- Save & History handlers are in server endpoints ---------------- */
async function mplan_lib_setupAuth() {
  // Tabs
  const tabLogin = document.getElementById('mplan_lib_tab_login');
  const tabSignup = document.getElementById('mplan_lib_tab_signup');
  const loginBox = document.getElementById('mplan_lib_login_form');
  const signupBox = document.getElementById('mplan_lib_signup_form');
  if (tabLogin && tabSignup) {
    tabLogin.addEventListener('click', ()=>{ tabLogin.classList.add('mplan_lib-active'); tabSignup.classList.remove('mplan_lib-active'); loginBox.style.display='block'; signupBox.style.display='none'; });
    tabSignup.addEventListener('click', ()=>{ tabSignup.classList.add('mplan_lib-active'); tabLogin.classList.remove('mplan_lib-active'); signupBox.style.display='block'; loginBox.style.display='none'; });
  }

  // Login
  const loginBtn = document.getElementById('mplan_lib_login_btn');
  if (loginBtn) {
    loginBtn.addEventListener('click', async ()=>{
      const msg = document.getElementById('mplan_lib_auth_msg');
      msg.className=''; msg.textContent='';
      const u = document.getElementById('mplan_lib_login_username').value.trim();
      const p = document.getElementById('mplan_lib_login_password').value;
      if (!u || !p) { msg.className='mplan_lib-error'; msg.textContent='Enter username and password.'; return; }
      const fd = new FormData(); fd.append('mplan_lib_action','login'); fd.append('mplan_lib_username', u); fd.append('mplan_lib_password', p);
      const j = await mplan_lib_postForm(fd);
      if (j.ok) location.reload(); else { msg.className='mplan_lib-error'; msg.textContent = j.error || 'Login failed'; }
    });
  }

  // Signup
  const signupBtn = document.getElementById('mplan_lib_signup_btn');
  if (signupBtn) {
    signupBtn.addEventListener('click', async ()=>{
      const msg = document.getElementById('mplan_lib_auth_msg');
      msg.className=''; msg.textContent='';
      const u = document.getElementById('mplan_lib_signup_username').value.trim();
      const p = document.getElementById('mplan_lib_signup_password').value;
      const c = document.getElementById('mplan_lib_signup_confirm').value;
      if (!u || !p || !c) { msg.className='mplan_lib-error'; msg.textContent='Fill all fields.'; return; }
      const fd = new FormData(); fd.append('mplan_lib_action','signup'); fd.append('mplan_lib_username', u); fd.append('mplan_lib_password', p); fd.append('mplan_lib_confirm', c);
      const j = await mplan_lib_postForm(fd);
      if (j.ok) location.reload(); else { msg.className='mplan_lib-error'; msg.textContent = j.error || 'Signup failed'; }
    });
  }

  // Logout
  const logoutBtn = document.getElementById('mplan_lib_logout_btn');
  if (logoutBtn) {
    logoutBtn.addEventListener('click', async ()=>{
      const fd = new FormData(); fd.append('mplan_lib_action','logout');
      await mplan_lib_postForm(fd);
      location.reload();
    });
  }
}

function mplan_lib_historyClick() {
  const list = document.getElementById('mplan_lib_history');
  if (!list) return;
  list.addEventListener('click', async (e) => {
    const item = e.target.closest('.mplan_lib-historyItem');
    if (!item) return;
    const id = item.dataset.mplan_libId;
    const fd = new FormData(); fd.append('mplan_lib_action','get_history');
    const j = await mplan_lib_postForm(fd);
    if (!j.ok) { mplan_lib_msg('Could not load history', 'error'); return; }
    const found = (j.history || []).find(x => x.id === id);
    if (!found) { mplan_lib_msg('Saved item not found', 'error'); return; }
    mplan_lib_renderResults(found.results || []);
    mplan_lib_msg('Opened saved plan: ' + found.title, 'success');
  });
}

document.addEventListener('DOMContentLoaded', function(){
  // wire up
  const genBtn = document.getElementById('mplan_lib_generate_btn');
  if (genBtn) genBtn.addEventListener('click', async ()=>{ genBtn.setAttribute('disabled','true'); await mplan_lib_generate(); genBtn.removeAttribute('disabled'); });

  mplan_lib_setupAuth();
  mplan_lib_historyClick();
});
</script>
</body>
</html>

← Back to Directory Edit File 🔒 Chmod

WP File Manager