( ′∀`)σ≡σ☆))Д′)レ(゚∀゚;)ヘ=З=З=Зε≡(ノ´_ゝ`)ノ HEX
HEX
Server: Apache/2.4.58 (Ubuntu)
System: Linux mail.thebrand.ai 6.8.0-107-generic #107-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 13 19:51:50 UTC 2026 x86_64
User: www-data (33)
PHP: 8.3.6
Disabled: NONE
Upload Files
File: /var/www/html/tmpr/..//tmpr/../tmpr/../customui/main.php
<?php
// Final working project: single-file PHP UI + proxy for n8n
// Compatible with PHP 5.6+ (no return type hints, no arrow functions)

// Config (env-backed, with safe defaults)
$N8N_BASE_URL   = getenv('N8N_BASE_URL') ?: 'https://thebrandai.app.n8n.cloud';
$WEBHOOK_PATH   = getenv('N8N_WEBHOOK_PATH') ?: 'test';
$N8N_AUTH_TOKEN = getenv('N8N_AUTH_TOKEN') ?: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZDcwMDk1Mi1hNjhiLTRhMDMtODFkZC02NTY3OWRmOGE4YWEiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwiaWF0IjoxNzYxNTA3MzY2LCJleHAiOjE3NjQwMTgwMDB9.w-J43tTtQz_qeTf7CCLIeH9cqsbLKjPRnLyMp0Opn-M';

function json_response($data, $status = 200, $extraHeaders = array()) {
    http_response_code($status);
    header('Content-Type: application/json');
    foreach ($extraHeaders as $h) header($h);
    echo is_string($data) ? $data : json_encode($data);
    exit;
}

function get_request_headers() {
    if (function_exists('getallheaders')) {
        return getallheaders();
    }
    $headers = array();
    foreach ($_SERVER as $k => $v) {
        if (strpos($k, 'HTTP_') === 0) {
            $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($k, 5)))));
            $headers[$name] = $v;
        }
    }
    if (isset($_SERVER['CONTENT_TYPE'])) $headers['Content-Type'] = $_SERVER['CONTENT_TYPE'];
    if (isset($_SERVER['CONTENT_LENGTH'])) $headers['Content-Length'] = $_SERVER['CONTENT_LENGTH'];
    return $headers;
}

function proxy_to_n8n($targetUrl, $authToken = null) {
    $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'POST';
    $inHeaders = get_request_headers();

    // If PHP parsed files, rebuild multipart using CURLFile so cURL sets correct boundaries
    $hasFiles = !empty($_FILES);
    if ($hasFiles) {
        $postFields = array();
        // Include any form fields
        foreach ($_POST as $k => $v) { $postFields[$k] = $v; }
        // Attach uploaded files
        foreach ($_FILES as $field => $info) {
            if (is_array($info['name'])) {
                $count = count($info['name']);
                for ($i = 0; $i < $count; $i++) {
                    if ($info['error'][$i] === UPLOAD_ERR_OK) {
                        $postFields[$field.'['.$i.']'] = new CURLFile($info['tmp_name'][$i], $info['type'][$i], $info['name'][$i]);
                    }
                }
            } else {
                if ($info['error'] === UPLOAD_ERR_OK) {
                    $postFields[$field] = new CURLFile($info['tmp_name'], $info['type'], $info['name']);
                }
            }
        }
        // Build headers, but let cURL set Content-Type and Content-Length
        $headers = array();
        foreach ($inHeaders as $name => $value) {
            $n = strtolower($name);
            if ($n === 'host' || $n === 'content-type' || $n === 'content-length' || $n === 'accept-encoding') continue;
            $headers[] = $name . ': ' . $value;
        }
        if ($authToken) {
            // Prefer server-side secret over any incoming Authorization
            $tmp = array();
            foreach ($headers as $h) { if (stripos($h, 'authorization:') !== 0) $tmp[] = $h; }
            $headers = $tmp;
            $headers[] = 'Authorization: Bearer ' . $authToken;
        }

        $ch = curl_init($targetUrl);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_HEADER, false);
        // Auto-decompress gzip/deflate responses so we return readable JSON
        curl_setopt($ch, CURLOPT_ENCODING, '');

        $responseBody = curl_exec($ch);
        if ($responseBody === false) {
            $err = curl_error($ch);
            curl_close($ch);
            json_response(array('error' => 'Proxy error', 'detail' => $err), 502);
        }
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if (!$status) $status = 200;
        $respType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
        if (!$respType) $respType = 'application/json';
        curl_close($ch);

        http_response_code($status);
        header('Content-Type: ' . $respType);
        echo $responseBody;
        exit;
    }

    // Fallback: raw body forwarding (no files present)
    $body = file_get_contents('php://input');
    $headers = array();
    foreach ($inHeaders as $name => $value) {
        $n = strtolower($name);
        if ($n === 'host' || $n === 'accept-encoding') continue; // don't forward Host or Accept-Encoding
        $headers[] = $name . ': ' . $value;
    }
    if ($authToken) {
        $tmp = array();
        foreach ($headers as $h) { if (stripos($h, 'authorization:') !== 0) $tmp[] = $h; }
        $headers = $tmp;
        $headers[] = 'Authorization: Bearer ' . $authToken;
    }

    $ch = curl_init($targetUrl);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_HEADER, false);
    // Auto-decompress gzip/deflate responses so we return readable JSON
    curl_setopt($ch, CURLOPT_ENCODING, '');

    $responseBody = curl_exec($ch);
    if ($responseBody === false) {
        $err = curl_error($ch);
        curl_close($ch);
        json_response(array('error' => 'Proxy error', 'detail' => $err), 502);
    }
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if (!$status) $status = 200;
    $respType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    if (!$respType) $respType = 'application/json';
    curl_close($ch);

    http_response_code($status);
    header('Content-Type: ' . $respType);
    echo $responseBody;
    exit;
}

$action = isset($_GET['action']) ? $_GET['action'] : 'ui';

if ($action === 'trigger') {
    // Forward initial trigger to n8n webhook
    $url = rtrim($N8N_BASE_URL, '/') . '/webhook/' . urlencode($WEBHOOK_PATH);
    proxy_to_n8n($url, $N8N_AUTH_TOKEN);
}

if ($action === 'resume') {
    // Forward raw request to resumeUrl
    $resumeUrl = isset($_GET['resumeUrl']) ? $_GET['resumeUrl'] : '';
    if (!$resumeUrl) {
        json_response(array('error' => 'Missing resumeUrl'), 400);
    }
    proxy_to_n8n($resumeUrl, $N8N_AUTH_TOKEN);
}

// UI (default)
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Custom UI Wizard ↔ n8n (main.php)</title>
<style>
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial;padding:24px;max-width:880px;margin:0 auto;color:#111;background:#fafafa}
section{background:#fff;border:1px solid #e6e6e6;border-radius:12px;padding:16px;margin-bottom:16px}
h1{font-size:22px;margin:0 0 12px} h2{font-size:18px;margin:0 0 10px}
pre{background:#0b1021;color:#e6edf3;padding:12px;border-radius:8px;overflow:auto}
button{padding:10px 14px;border-radius:8px;border:1px solid #ddd;background:#1f7aec;color:#fff;font-weight:600;cursor:pointer}
input,textarea{width:100%;padding:10px;border:1px solid #ddd;border-radius:8px}
.small{color:#666;font-size:12px}
.code{font-family:ui-monospace,Consolas,Monaco,monospace}
.flex{display:flex;gap:12px;align-items:center}
.hidden{display:none}

/* Wizard additions */
.wizard{margin-top:8px}
.step{display:none}
.step.active{display:block}
.step-header{display:flex;align-items:center;gap:8px;margin-bottom:10px}
.step-index{background:#1f7aec;color:#fff;border-radius:999px;width:26px;height:26px;display:inline-flex;align-items:center;justify-content:center;font-weight:700}
.nav{display:flex;gap:8px;margin-top:12px}
.nav .secondary{background:#fff;color:#1f7aec;border:1px solid #1f7aec}
.progress{display:flex;gap:6px;margin-bottom:14px}
.progress .dot{width:8px;height:8px;border-radius:999px;background:#d0d7ff}
.progress .dot.active{background:#1f7aec}
/* List view for results */
.list{margin-top:8px;display:flex;flex-direction:column;gap:8px}
.item{border:1px solid #e6e6e6;border-radius:8px;padding:10px;background:#fff}
.item .label{font-weight:600;color:#333;margin-bottom:4px}
.item .value{color:#222;white-space:pre-wrap}
</style>
</head>
<body>
<h1>Custom UI Wizard for n8n Workflows</h1>
<section>
  <div class="progress">
    <span class="dot" id="p1"></span>
    <span class="dot" id="p2"></span>
    <span class="dot" id="p3"></span>
  </div>
  <div class="wizard">
    <!-- Step 1: Trigger -->
    <div class="step active" id="step1">
      <div class="step-header"><span class="step-index">1</span><h2>Trigger Workflow</h2></div>
      <p class="small">POSTs to <span class="code">main.php?action=trigger</span> — expects a <span class="code">resumeUrl</span>.</p>
      <div>
        <label>Optional JSON payload</label>
        <textarea id="triggerPayload" rows="4">{"message":"Start"}</textarea>
      </div>
      <div class="nav">
        <button id="btnTrigger">Trigger</button>
        <button id="btnStep1Next" class="secondary" disabled>Next</button>
      </div>
      <div class="small" id="resumeHolder" class="hidden"></div>
      <pre id="triggerOut">(awaiting trigger)</pre>
    </div>

    <!-- Step 2: Resume -->
    <div class="step" id="step2">
      <div class="step-header"><span class="step-index">2</span><h2>Resume with File Upload</h2></div>
      <p class="small">Sends multipart data to <span class="code">main.php?action=resume&resumeUrl=...</span>.</p>
      <form id="resumeForm">
        <div>
          <label>File</label>
          <input id="fileInput" name="file" type="file" />
        </div>
        <div style="margin-top:8px">
          <label>Notes (optional)</label>
          <input id="notesInput" name="notes" type="text" placeholder="Add a note" />
        </div>
        <div class="nav">
          <button type="submit">Resume</button>
          <button id="btnStep2Next" class="secondary" disabled>Next</button>
        </div>
        <div class="small" id="resumeInfo">No resumeUrl yet.</div>
      </form>
      <pre id="resumeOut">(awaiting resume)</pre>
    </div>

    <!-- Step 3: Response -->
    <div class="step" id="step3">
      <div class="step-header"><span class="step-index">3</span><h2>Response</h2></div>
      <div class="small">Captured <span class="code">resumeUrl</span>: <span class="code" id="capturedUrl">(none)</span></div>
      <div class="list" id="resultList"></div>
      <div style="margin-top:8px"><strong>Latest Response (raw JSON)</strong></div>
      <pre id="finalOut">(no response yet)</pre>
      <div class="nav">
        <button id="btnRestart" class="secondary">Start Over</button>
      </div>
  </div>
</section>
<section>
  <h2>Status</h2>
  <div class="small">n8n base: <span class="code" id="baseUrl"></span> • webhook path: <span class="code" id="whPath"></span></div>
  <div class="small">Files: <span class="code">project.md</span> • <span class="code">custom-ui.json</span></div>
</section>
<script>
var state = { step: 1, resumeUrl: null, triggerJson: null, resumeJson: null };
function $(id){ return document.getElementById(id); }
function clearList(){ var rl = $('resultList'); if (rl) rl.innerHTML=''; }
function renderListFromLatest(){
  var rl = $('resultList');
  if (!rl) return;
  rl.innerHTML = '';
  var latest = state.resumeJson || state.triggerJson;
  if (!latest) return;
  var obj = latest;
  try {
    if (Array.isArray(latest)) { obj = latest[0] || {}; }
    if (obj.json) obj = obj.json;
  } catch (e) {}
  var summary = obj.summary || '';
  var products = obj.products || '';
  var resumeUrl = obj.resumeUrl || state.resumeUrl || '';
  // Normalize products into array
  var productsList = Array.isArray(products) ? products : (typeof products === 'string' ? products.split(',').map(function(s){return s.trim();}).filter(function(s){return s.length>0;}) : []);
  // Build list items
  var items = [
    { label: 'Summary', value: summary || '(empty)' },
    { label: 'Products', value: (productsList.length ? productsList.join('\n') : '(none)') },
    { label: 'Resume URL', value: resumeUrl || '(none)' }
  ];
  items.forEach(function(it){
    var d = document.createElement('div'); d.className = 'item';
    var l = document.createElement('div'); l.className = 'label'; l.textContent = it.label;
    var v = document.createElement('div'); v.className = 'value'; v.textContent = it.value;
    d.appendChild(l); d.appendChild(v);
    rl.appendChild(d);
  });
}
function showStep(n){
  state.step = n;
  ['step1','step2','step3'].forEach(function(id){
    var el = $(id);
    if (el) el.classList.toggle('active', id === 'step'+n);
  });
  ['p1','p2','p3'].forEach(function(id, i){
    var el = $(id);
    if (el) el.classList.toggle('active', i < n);
  });
  if (n === 3) {
    $('capturedUrl').textContent = state.resumeUrl || '(none)';
    var latest = state.resumeJson || state.triggerJson || { info: 'No responses captured' };
    $('finalOut').textContent = JSON.stringify(latest, null, 2);
    renderListFromLatest();
  } else {
    clearList();
  }
}

$('baseUrl').textContent = <?php echo json_encode(rtrim($N8N_BASE_URL, '/')); ?>;
$('whPath').textContent = <?php echo json_encode($WEBHOOK_PATH); ?>;

// Step 1: Trigger
$('btnTrigger').addEventListener('click', function(){
  (async function(){
    try {
      var payloadText = $('triggerPayload').value.trim();
      var body = payloadText;
      var headers = { 'Content-Type': 'application/json' };
      try { JSON.parse(payloadText); } catch (e) { body = JSON.stringify({ content: payloadText }); }

      var res = await fetch('main.php?action=trigger', { method: 'POST', headers: headers, body: body });
      var text = await res.text();
      var json; try { json = JSON.parse(text); } catch (e) { json = { raw: text }; }
      state.triggerJson = json;
      $('triggerOut').textContent = JSON.stringify(json, null, 2);

      var resumeUrl = json && (json.resumeUrl || (json.json && json.json.resumeUrl));
      if (resumeUrl) {
        state.resumeUrl = resumeUrl;
        $('resumeInfo').textContent = 'resumeUrl captured';
        $('resumeHolder').textContent = 'resumeUrl: ' + resumeUrl;
        $('btnStep1Next').disabled = false;
        showStep(2);
      } else {
        $('resumeInfo').textContent = 'No resumeUrl found in response.';
        $('btnStep1Next').disabled = true;
      }
    } catch (e) {
      $('triggerOut').textContent = 'Error: ' + e.message;
    }
  })();
});
$('btnStep1Next').addEventListener('click', function(){ if (!this.disabled) showStep(2); });

// Step 2: Resume
$('resumeForm').addEventListener('submit', function(e){
  e.preventDefault();
  if (!state.resumeUrl) {
    $('resumeOut').textContent = 'Missing resumeUrl — trigger first.';
    return;
  }
  (async function(){
    try {
      var fd = new FormData(document.getElementById('resumeForm'));
      var url = 'main.php?action=resume&resumeUrl=' + encodeURIComponent(state.resumeUrl);
      var res = await fetch(url, { method: 'POST', body: fd });
      var text = await res.text();
      var json; try { json = JSON.parse(text); } catch (e) { json = { raw: text }; }
      state.resumeJson = json;
      $('resumeOut').textContent = JSON.stringify(json, null, 2);
      $('btnStep2Next').disabled = false;
      showStep(3);
    } catch (e) {
      $('resumeOut').textContent = 'Error: ' + e.message;
    }
  })();
});
$('btnStep2Next').addEventListener('click', function(){ if (!this.disabled) showStep(3); });

// Step 3: Restart
$('btnRestart').addEventListener('click', function(){
  state = { step: 1, resumeUrl: null, triggerJson: null, resumeJson: null };
  $('triggerOut').textContent = '(awaiting trigger)';
  $('resumeOut').textContent = '(awaiting resume)';
  $('finalOut').textContent = '(no response yet)';
  $('resumeInfo').textContent = 'No resumeUrl yet.';
  $('btnStep1Next').disabled = true;
  $('btnStep2Next').disabled = true;
  showStep(1);
});

// init
showStep(1);
</script>
</body>
</html>