1018 lines
21 KiB
PHP
1018 lines
21 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
|
|
|
|
/* ===================== Utilities ===================== */
|
|
|
|
function respond($data, int $code = 200): void {
|
|
|
|
http_response_code($code);
|
|
|
|
echo is_string($data) ? $data : json_encode($data, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
function fail(string $msg, $detail = null, int $code = 400): void {
|
|
|
|
respond(['ok'=>false,'error'=>$msg,'detail'=>$detail], $code);
|
|
|
|
}
|
|
|
|
function load_config(): array {
|
|
|
|
$paths = [
|
|
|
|
__DIR__ . '/../inc/config.php',
|
|
|
|
__DIR__ . '/inc/config.php',
|
|
|
|
__DIR__ . '/config.php',
|
|
|
|
];
|
|
|
|
foreach ($paths as $p) {
|
|
|
|
if (is_file($p)) {
|
|
|
|
$conf = @include $p;
|
|
|
|
if (is_array($conf)) return $conf;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fail('Invalid config.php', 'config.php not found or not returning array', 500);
|
|
|
|
}
|
|
|
|
function cors(array $conf): void {
|
|
|
|
$cors = $conf['cors'] ?? '*';
|
|
|
|
if ($cors) {
|
|
|
|
header('Access-Control-Allow-Origin: ' . $cors);
|
|
|
|
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
|
|
|
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
|
|
|
header('Access-Control-Allow-Credentials: true');
|
|
|
|
}
|
|
|
|
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'OPTIONS') respond(['ok'=>true]);
|
|
|
|
}
|
|
|
|
function get_input(): array {
|
|
|
|
$data = [];
|
|
|
|
$ct = $_SERVER['CONTENT_TYPE'] ?? '';
|
|
|
|
if (stripos($ct, 'application/json') !== false) {
|
|
|
|
$raw = file_get_contents('php://input');
|
|
|
|
if ($raw !== false && $raw !== '') {
|
|
|
|
$js = json_decode($raw, true);
|
|
|
|
if (is_array($js)) $data = $js;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
foreach ($_POST as $k=>$v) $data[$k]=$v;
|
|
|
|
foreach ($_GET as $k=>$v) if (!array_key_exists($k,$data)) $data[$k]=$v;
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
function val(array $in, $keys, $default=null) {
|
|
|
|
if (!is_array($keys)) $keys = [$keys];
|
|
|
|
foreach ($keys as $k) if (array_key_exists($k,$in)) return $in[$k];
|
|
|
|
return $default;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ===================== DB helpers ===================== */
|
|
|
|
function pdo_templates(array $conf): PDO {
|
|
|
|
if (!isset($conf['templates']) || !is_array($conf['templates'])) {
|
|
|
|
fail('Missing templates DB config', null, 500);
|
|
|
|
}
|
|
|
|
$c = $conf['templates'];
|
|
|
|
$host = $c['db_host'] ?? 'localhost';
|
|
|
|
$db = $c['db_name'] ?? ($c['database'] ?? '');
|
|
|
|
$user = $c['db_user'] ?? ($c['username'] ?? '');
|
|
|
|
$pass = $c['db_pass'] ?? ($c['password'] ?? '');
|
|
|
|
$charset = $c['db_charset'] ?? 'utf8mb4';
|
|
|
|
$port = $c['db_port'] ?? 3306;
|
|
|
|
$dsn = "mysql:host=$host;port=$port;dbname=$db;charset=$charset";
|
|
|
|
$opt = [
|
|
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
|
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
|
|
];
|
|
|
|
return new PDO($dsn, $user, $pass, $opt);
|
|
|
|
}
|
|
|
|
function verify_password(string $input, string $stored, array $authDbConf): bool {
|
|
|
|
if (preg_match('~^\$2[aby]\$~', $stored) || strpos($stored, '$argon2') === 0) return password_verify($input, $stored);
|
|
|
|
$legacy = strtolower($authDbConf['legacy'] ?? '');
|
|
|
|
if ($legacy === 'md5') return hash_equals($stored, md5($input));
|
|
|
|
if ($legacy === 'sha1') return hash_equals($stored, sha1($input));
|
|
|
|
if ($legacy === 'plain')return hash_equals($stored, $input);
|
|
|
|
if (password_get_info($stored)['algo'] !== 0) return password_verify($input, $stored);
|
|
|
|
return hash_equals($stored, $input);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ============ schema helpers ============ */
|
|
|
|
function table_columns(PDO $pdo, string $table): array {
|
|
|
|
$cols = [];
|
|
|
|
$stmt = $pdo->query("SHOW COLUMNS FROM `$table`");
|
|
|
|
foreach ($stmt->fetchAll() as $r) $cols[] = $r['Field'];
|
|
|
|
return $cols;
|
|
|
|
}
|
|
|
|
function primary_key(PDO $pdo, string $table): ?string {
|
|
|
|
$stmt = $pdo->prepare("SHOW KEYS FROM `$table` WHERE Key_name = 'PRIMARY'");
|
|
|
|
$stmt->execute();
|
|
|
|
$row = $stmt->fetch();
|
|
|
|
return $row['Column_name'] ?? null;
|
|
|
|
}
|
|
|
|
function first_existing(array $columns, array $candidates): ?string {
|
|
|
|
foreach ($candidates as $c) if (in_array($c, $columns, true)) return $c;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ===================== Boot ===================== */
|
|
|
|
try {
|
|
|
|
$conf = load_config();
|
|
|
|
cors($conf);
|
|
|
|
if (session_status() === PHP_SESSION_NONE) session_start();
|
|
|
|
if (!empty($conf['auth']['cookie'])) {
|
|
|
|
$c = $conf['auth']['cookie'];
|
|
|
|
$params = session_get_cookie_params();
|
|
|
|
$params['lifetime'] = $c['lifetime'] ?? $params['lifetime'];
|
|
|
|
$params['path'] = $c['path'] ?? $params['path'];
|
|
|
|
$params['domain'] = $c['domain'] ?? $params['domain'];
|
|
|
|
$params['secure'] = $c['secure'] ?? $params['secure'];
|
|
|
|
$params['httponly'] = $c['httponly'] ?? $params['httponly'];
|
|
|
|
if (isset($c['samesite'])) $params['samesite'] = $c['samesite'];
|
|
|
|
session_set_cookie_params($params);
|
|
|
|
}
|
|
|
|
|
|
|
|
$in = get_input();
|
|
|
|
|
|
|
|
/* ---- Compat: ?resource=blocks&action=list -> blocks.list ---- */
|
|
|
|
$action = val($in, 'action', '');
|
|
|
|
$resource = val($in, 'resource', null);
|
|
|
|
$allowedResources = ['templates','sections','blocks','snippets'];
|
|
|
|
if ($resource && in_array($resource, $allowedResources, true) && strpos((string)$action, '.') === false) {
|
|
|
|
$verb = strtolower((string)$action);
|
|
|
|
if (in_array($verb, ['list','get','create','update','delete'], true)) $action = $resource.'.'.$verb;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ---- Multi-tenant ---- */
|
|
|
|
$multi = $conf['multi'] ?? [];
|
|
|
|
$tenantCol = $multi['tenant_col'] ?? null;
|
|
|
|
$mapSess = $multi['map_session_to'] ?? 'id'; // 'id'|'email'|'name'
|
|
|
|
|
|
|
|
$tenantWhere = function(array $session) use ($tenantCol, $mapSess) {
|
|
|
|
if (!$tenantCol) return ['', []];
|
|
|
|
if (!$session) return [' AND 1=0 ', []];
|
|
|
|
$val = $session[$mapSess] ?? null;
|
|
|
|
if ($val===null || $val==='') return [' AND 1=0 ', []];
|
|
|
|
return [" AND `$tenantCol` = :__tenant", [':__tenant'=>$val]];
|
|
|
|
};
|
|
|
|
$tenantAssign = function(array $session, array $columns) use ($tenantCol, $mapSess) {
|
|
|
|
if (!$tenantCol || !in_array($tenantCol, $columns, true)) return [];
|
|
|
|
$val = $session[$mapSess] ?? null;
|
|
|
|
return ($val===null || $val==='') ? [] : [$tenantCol => $val];
|
|
|
|
};
|
|
|
|
$requireAuth = function() {
|
|
|
|
if (empty($_SESSION['auth'])) fail('Not authenticated', null, 401);
|
|
|
|
return $_SESSION['auth'];
|
|
|
|
};
|
|
|
|
|
|
|
|
/* ---- config tables/columns ---- */
|
|
|
|
$tables = $conf['tables'] ?? [];
|
|
|
|
$tableMap = [
|
|
|
|
'templates' => $tables['templates'] ?? 'emailtemplate_templates',
|
|
|
|
'sections' => $tables['sections'] ?? 'emailtemplate_sections',
|
|
|
|
'blocks' => $tables['blocks'] ?? 'emailtemplate_blocks',
|
|
|
|
'snippets' => $tables['snippets'] ?? 'emailtemplate_snippets',
|
|
|
|
];
|
|
|
|
$colsDefault = [
|
|
|
|
'id' => 'id',
|
|
|
|
'name' => 'name',
|
|
|
|
'desc' => 'description',
|
|
|
|
'cat' => 'category',
|
|
|
|
'upd' => 'updated_at',
|
|
|
|
];
|
|
|
|
|
|
|
|
$pdo = pdo_templates($conf);
|
|
|
|
|
|
|
|
/* helper: resolve id column for a table */
|
|
|
|
$resolveIdCol = function(string $kind) use ($conf, $colsDefault, $tableMap, $pdo) {
|
|
|
|
$t = $tableMap[$kind];
|
|
|
|
$cfg = $conf['columns'][$kind] ?? [];
|
|
|
|
$cols = table_columns($pdo, $t);
|
|
|
|
$idCol = $cfg['id'] ?? (in_array('id', $cols, true) ? 'id' : primary_key($pdo, $t));
|
|
|
|
if (!$idCol) $idCol = 'id'; // fallback
|
|
|
|
return [$idCol, $cols];
|
|
|
|
};
|
|
|
|
|
|
|
|
/* helper: accept many id aliases */
|
|
|
|
$pullId = function(array $src) {
|
|
|
|
$aliases = ['id','item_id','template_id','tpl_id','section_id','sec_id','block_id','blk_id','snippet_id','snip_id'];
|
|
|
|
foreach ($aliases as $a) if (isset($src[$a]) && $src[$a] !== '') return $src[$a];
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* ===================== Router ===================== */
|
|
|
|
switch ($action) {
|
|
|
|
case 'health':
|
|
|
|
respond(['ok'=>true,'time'=>date('c')]);
|
|
|
|
|
|
|
|
/* ---------- AUTH ---------- */
|
|
|
|
case 'auth.login': {
|
|
|
|
$identifier = trim((string)val($in, ['username','user','email','login'], ''));
|
|
|
|
$password = (string)val($in, ['password','pass','pwd'], '');
|
|
|
|
if ($identifier === '' || $password === '') fail('username/password required', null, 422);
|
|
|
|
|
|
|
|
$authDb = $conf['auth']['db'] ?? [];
|
|
|
|
$table = $authDb['table'] ?? 'emailtemplate_users';
|
|
|
|
$colUser = $authDb['col_user'] ?? 'email';
|
|
|
|
$colPass = $authDb['col_pass'] ?? 'password';
|
|
|
|
$colName = $authDb['col_name'] ?? 'name';
|
|
|
|
$colId = $authDb['col_id'] ?? 'id';
|
|
|
|
$colStatus = $authDb['col_status']?? null;
|
|
|
|
$activeValues = $authDb['active_values'] ?? ['active','1',1];
|
|
|
|
|
|
|
|
$stmt = $pdo->prepare("SELECT * FROM `$table` WHERE `$colUser` = :u LIMIT 1");
|
|
|
|
$stmt->execute([':u'=>$identifier]);
|
|
|
|
$row = $stmt->fetch();
|
|
|
|
if (!$row) fail('Invalid credentials', null, 401);
|
|
|
|
|
|
|
|
if ($colStatus && isset($row[$colStatus])) {
|
|
|
|
if (!in_array($row[$colStatus], $activeValues, true)) {
|
|
|
|
fail('Account inactive', null, 403);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$stored = (string)($row[$colPass] ?? '');
|
|
|
|
if ($stored === '' || !verify_password($password, $stored, $authDb)) {
|
|
|
|
fail('Invalid credentials', null, 401);
|
|
|
|
}
|
|
|
|
|
|
|
|
$_SESSION['auth'] = [
|
|
|
|
'id' => $row[$colId] ?? null,
|
|
|
|
'name' => $row[$colName] ?? ($row[$colUser] ?? $identifier),
|
|
|
|
'email' => $row[$colUser] ?? $identifier,
|
|
|
|
'at' => time(),
|
|
|
|
];
|
|
|
|
$token = base64_encode(hash('sha256', ($_SESSION['auth']['id'] ?? $identifier).'|'.session_id(), true));
|
|
|
|
respond(['ok'=>true,'user'=>$_SESSION['auth'],'token'=>$token]);
|
|
|
|
}
|
|
|
|
|
|
|
|
case 'auth.me':
|
|
|
|
if (empty($_SESSION['auth'])) fail('Not authenticated', null, 401);
|
|
|
|
respond(['ok'=>true,'user'=>$_SESSION['auth']]);
|
|
|
|
|
|
|
|
case 'auth.logout':
|
|
|
|
$_SESSION = [];
|
|
|
|
if (session_id() !== '') session_destroy();
|
|
|
|
respond(['ok'=>true]);
|
|
|
|
|
|
|
|
/* ---------- LIST (mit Parent-Filtern) ---------- */
|
|
|
|
case 'templates.list':
|
|
|
|
case 'sections.list':
|
|
|
|
case 'blocks.list':
|
|
|
|
case 'snippets.list': {
|
|
|
|
$auth = $requireAuth();
|
|
|
|
|
|
|
|
$kind = explode('.', $action)[0];
|
|
|
|
$t = $tableMap[$kind];
|
|
|
|
[$idCol, $allCols] = $resolveIdCol($kind);
|
|
|
|
$cfg = $conf['columns'][$kind] ?? [];
|
|
|
|
$nameCol = $cfg['name'] ?? (in_array('name',$allCols,true) ? 'name' : $idCol);
|
|
|
|
$descCol = $cfg['desc'] ?? first_existing($allCols, ['description','desc','descr']);
|
|
|
|
$catCol = $cfg['cat'] ?? first_existing($allCols, ['category','cat']);
|
|
|
|
$updCol = $cfg['upd'] ?? first_existing($allCols, ['updated_at','updated','updatedAt']);
|
|
|
|
|
|
|
|
$q = trim((string)val($in,'q',''));
|
|
|
|
$limit = max(1, (int)val($in,'limit', 500));
|
|
|
|
$offset = max(0, (int)val($in,'offset',0));
|
|
|
|
|
|
|
|
$where = ' WHERE 1=1 ';
|
|
|
|
$params = [];
|
|
|
|
|
|
|
|
if ($q !== '') {
|
|
|
|
$parts = ["`$nameCol` LIKE :q"];
|
|
|
|
if ($descCol) $parts[] = "`$descCol` LIKE :q";
|
|
|
|
if ($catCol) $parts[] = "`$catCol` LIKE :q";
|
|
|
|
$where .= " AND (".implode(' OR ', $parts).") ";
|
|
|
|
$params[':q'] = '%'.$q.'%';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parent-Filtern (falls Spalten existieren)
|
|
|
|
$parentFilters = [
|
|
|
|
'template_id' => val($in, ['template_id','tpl_id'], null),
|
|
|
|
'section_id' => val($in, ['section_id','sec_id'], null),
|
|
|
|
'block_id' => val($in, ['block_id','blk_id'], null),
|
|
|
|
];
|
|
|
|
foreach ($parentFilters as $col => $v) {
|
|
|
|
if ($v === null || $v === '') continue;
|
|
|
|
if (in_array($col, $allCols, true)) { $where .= " AND `$col` = :$col "; $params[":$col"] = $v; }
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mandant
|
|
|
|
[$tw,$tp] = $tenantWhere($auth);
|
|
|
|
$where .= $tw; foreach ($tp as $k=>$v) $params[$k]=$v;
|
|
|
|
|
|
|
|
$order = $updCol ? " ORDER BY `$updCol` DESC " : " ORDER BY `$nameCol` ASC ";
|
|
|
|
$sql = "SELECT * FROM `$t` $where $order LIMIT :off,:lim";
|
|
|
|
$stmt = $pdo->prepare($sql);
|
|
|
|
foreach ($params as $k=>$v) $stmt->bindValue($k,$v,is_int($v)?PDO::PARAM_INT:PDO::PARAM_STR);
|
|
|
|
$stmt->bindValue(':off',$offset,PDO::PARAM_INT);
|
|
|
|
$stmt->bindValue(':lim',$limit,PDO::PARAM_INT);
|
|
|
|
$stmt->execute();
|
|
|
|
$rows = $stmt->fetchAll();
|
|
|
|
|
|
|
|
$out = [];
|
|
|
|
foreach ($rows as $r) {
|
|
|
|
$item = [
|
|
|
|
'id' => $r[$idCol] ?? null,
|
|
|
|
'name' => $r[$nameCol] ?? null,
|
|
|
|
];
|
|
|
|
if ($descCol && isset($r[$descCol])) $item['desc'] = $r[$descCol];
|
|
|
|
if ($catCol && isset($r[$catCol])) $item['category'] = $r[$catCol];
|
|
|
|
if ($updCol && isset($r[$updCol])) $item['updated_at'] = $r[$updCol];
|
|
|
|
$out[] = $item;
|
|
|
|
}
|
|
|
|
respond(['ok'=>true,'kind'=>$kind,'items'=>$out,'data'=>$out,'count'=>count($out),'offset'=>$offset,'limit'=>$limit]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ---------- GET (JETZT mit top-level html/content) ---------- */
|
|
|
|
case 'templates.get':
|
|
|
|
case 'sections.get':
|
|
|
|
case 'blocks.get':
|
|
|
|
case 'snippets.get': {
|
|
|
|
$auth = $requireAuth();
|
|
|
|
|
|
|
|
$kind = explode('.', $action)[0];
|
|
|
|
$t = $tableMap[$kind];
|
|
|
|
[$idCol, $allCols] = $resolveIdCol($kind);
|
|
|
|
|
|
|
|
$id = $pullId($in);
|
|
|
|
if ($id === null || $id === '') fail('id required', null, 422);
|
|
|
|
|
|
|
|
[$tw,$tp] = $tenantWhere($auth);
|
|
|
|
$sql = "SELECT * FROM `$t` WHERE `$idCol` = :id".$tw." LIMIT 1";
|
|
|
|
$stmt = $pdo->prepare($sql);
|
|
|
|
$stmt->bindValue(':id', $id);
|
|
|
|
foreach ($tp as $k=>$v) $stmt->bindValue($k,$v);
|
|
|
|
$stmt->execute();
|
|
|
|
$row = $stmt->fetch();
|
|
|
|
if (!$row) fail('Not found', ['kind'=>$kind,'id'=>$id], 404);
|
|
|
|
|
|
|
|
$rowOut = ['id' => $row[$idCol] ?? $id] + $row;
|
|
|
|
|
|
|
|
// NEU: Spalten für HTML/JSON erkennen und top-level ausgeben
|
|
|
|
$htmlCol = first_existing($allCols, ['html','body','markup']);
|
|
|
|
$jsonCol = first_existing($allCols, ['content_json','json','content','structure_json']);
|
|
|
|
$topHtml = ($htmlCol && isset($row[$htmlCol])) ? (string)$row[$htmlCol] : null;
|
|
|
|
$topContent = ($jsonCol && isset($row[$jsonCol])) ? $row[$jsonCol] : null;
|
|
|
|
|
|
|
|
respond([
|
|
|
|
'ok'=>true,
|
|
|
|
'kind'=>$kind,
|
|
|
|
'id'=>$rowOut['id'],
|
|
|
|
'item'=>$rowOut,
|
|
|
|
'data'=>$rowOut,
|
|
|
|
'html'=>$topHtml, // <— wichtig für Vorschau
|
|
|
|
'content'=>$topContent // optional (z. B. GrapesJS JSON)
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ---------- CREATE ---------- */
|
|
|
|
case 'templates.create':
|
|
|
|
case 'sections.create':
|
|
|
|
case 'blocks.create':
|
|
|
|
case 'snippets.create': {
|
|
|
|
$auth = $requireAuth();
|
|
|
|
|
|
|
|
$kind = explode('.', $action)[0];
|
|
|
|
$t = $tableMap[$kind];
|
|
|
|
[$idCol, $allCols] = $resolveIdCol($kind);
|
|
|
|
$cfg = $conf['columns'][$kind] ?? [];
|
|
|
|
$nameCol = $cfg['name'] ?? (in_array('name',$allCols,true) ? 'name' : $idCol);
|
|
|
|
$descCol = $cfg['desc'] ?? first_existing($allCols, ['description','desc','descr']);
|
|
|
|
$catCol = $cfg['cat'] ?? first_existing($allCols, ['category','cat']);
|
|
|
|
$updCol = $cfg['upd'] ?? first_existing($allCols, ['updated_at','updated','updatedAt']);
|
|
|
|
|
|
|
|
$name = trim((string)val($in, ['name','title'], ''));
|
|
|
|
if ($name === '') fail('name required', null, 422);
|
|
|
|
|
|
|
|
$desc = (string)val($in, ['description','desc'], null);
|
|
|
|
$cat = (string)val($in, ['category','cat'], null);
|
|
|
|
$html = (string)val($in, ['html','body','markup'], null);
|
|
|
|
$json = val($in, ['content_json','json','content','structure_json'], null);
|
|
|
|
$settings = val($in, ['settings_json','settings'], null);
|
|
|
|
|
|
|
|
$templateId = val($in, ['template_id','tpl_id'], null);
|
|
|
|
$sectionId = val($in, ['section_id','sec_id'], null);
|
|
|
|
$blockId = val($in, ['block_id','blk_id'], null);
|
|
|
|
|
|
|
|
$data = [ $nameCol => $name ];
|
|
|
|
if ($desc !== null && $descCol) $data[$descCol] = $desc;
|
|
|
|
if ($cat !== null && $catCol) $data[$catCol] = $cat;
|
|
|
|
|
|
|
|
$c = first_existing($allCols, ['html','body','markup']); if ($c && $html !== null) $data[$c]=$html;
|
|
|
|
$c = first_existing($allCols, ['content_json','json','content','structure_json']); if ($c && $json !== null) $data[$c]= is_string($json)?$json:json_encode($json, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
|
|
|
$c = first_existing($allCols, ['settings_json','settings']); if ($c && $settings !== null) $data[$c]= is_string($settings)?$settings:json_encode($settings, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
|
|
|
|
|
|
|
if ($templateId !== null && in_array('template_id',$allCols,true)) $data['template_id']=$templateId;
|
|
|
|
if ($sectionId !== null && in_array('section_id',$allCols,true)) $data['section_id']=$sectionId;
|
|
|
|
if ($blockId !== null && in_array('block_id',$allCols,true)) $data['block_id']=$blockId;
|
|
|
|
|
|
|
|
$data = $data + $tenantAssign($_SESSION['auth'] ?? [], $allCols);
|
|
|
|
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
|
|
$createdCol = first_existing($allCols, ['created_at','created','createdAt']);
|
|
|
|
if ($createdCol) $data[$createdCol] = $now;
|
|
|
|
if ($updCol) $data[$updCol] = $now;
|
|
|
|
|
|
|
|
$fields = array_keys($data);
|
|
|
|
$place = array_map(fn($c)=>":$c", $fields);
|
|
|
|
$sql = "INSERT INTO `$t` (".implode(',', array_map(fn($c)=>"`$c`",$fields)).") VALUES (".implode(',', $place).")";
|
|
|
|
$stmt = $pdo->prepare($sql);
|
|
|
|
foreach ($data as $k=>$v) $stmt->bindValue(":$k", $v);
|
|
|
|
$stmt->execute();
|
|
|
|
$newId = $pdo->lastInsertId();
|
|
|
|
|
|
|
|
$out = ['id'=>$newId,'name'=>$name];
|
|
|
|
if ($desc !== null) $out['desc']=$desc;
|
|
|
|
if ($cat !== null) $out['category']=$cat;
|
|
|
|
respond(['ok'=>true,'kind'=>$kind,'id'=>$newId,'item'=>$out,'data'=>$out]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ---------- UPDATE ---------- */
|
|
|
|
case 'templates.update':
|
|
|
|
case 'sections.update':
|
|
|
|
case 'blocks.update':
|
|
|
|
case 'snippets.update': {
|
|
|
|
$auth = $requireAuth();
|
|
|
|
|
|
|
|
$kind = explode('.', $action)[0];
|
|
|
|
$t = $tableMap[$kind];
|
|
|
|
[$idCol, $allCols] = $resolveIdCol($kind);
|
|
|
|
$cfg = $conf['columns'][$kind] ?? [];
|
|
|
|
$nameCol = $cfg['name'] ?? (in_array('name',$allCols,true) ? 'name' : $idCol);
|
|
|
|
$descCol = $cfg['desc'] ?? first_existing($allCols, ['description','desc','descr']);
|
|
|
|
$catCol = $cfg['cat'] ?? first_existing($allCols, ['category','cat']);
|
|
|
|
$updCol = $cfg['upd'] ?? first_existing($allCols, ['updated_at','updated','updatedAt']);
|
|
|
|
|
|
|
|
$id = $pullId($in);
|
|
|
|
if ($id === null || $id === '') fail('id required', null, 422);
|
|
|
|
|
|
|
|
$data = [];
|
|
|
|
$name = val($in, ['name','title'], null);
|
|
|
|
$desc = val($in, ['description','desc'], null);
|
|
|
|
$cat = val($in, ['category','cat'], null);
|
|
|
|
$html = val($in, ['html','body','markup'], null);
|
|
|
|
$json = val($in, ['content_json','json','content','structure_json'], null);
|
|
|
|
$settings = val($in, ['settings_json','settings'], null);
|
|
|
|
|
|
|
|
if ($name !== null) $data[$nameCol] = (string)$name;
|
|
|
|
if ($desc !== null && $descCol) $data[$descCol] = (string)$desc;
|
|
|
|
if ($cat !== null && $catCol) $data[$catCol] = (string)$cat;
|
|
|
|
$c = first_existing($allCols, ['html','body','markup']); if ($html !== null && $c) $data[$c]=(string)$html;
|
|
|
|
$c = first_existing($allCols, ['content_json','json','content','structure_json']); if ($json !== null && $c) $data[$c]= is_string($json)?$json:json_encode($json, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
|
|
|
$c = first_existing($allCols, ['settings_json','settings']); if ($settings !== null && $c) $data[$c]= is_string($settings)?$settings:json_encode($settings, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
|
|
|
|
|
|
|
$tpl = val($in, ['template_id','tpl_id'], null); if ($tpl !== null && in_array('template_id',$allCols,true)) $data['template_id']=$tpl;
|
|
|
|
$sec = val($in, ['section_id','sec_id'], null); if ($sec !== null && in_array('section_id',$allCols,true)) $data['section_id']=$sec;
|
|
|
|
$blk = val($in, ['block_id','blk_id'], null); if ($blk !== null && in_array('block_id',$allCols,true)) $data['block_id']=$blk;
|
|
|
|
|
|
|
|
if ($updCol) $data[$updCol] = date('Y-m-d H:i:s');
|
|
|
|
if (!$data) fail('nothing to update', null, 422);
|
|
|
|
|
|
|
|
[$tw,$tp] = $tenantWhere($auth);
|
|
|
|
$set = []; foreach (array_keys($data) as $c) $set[] = "`$c` = :$c";
|
|
|
|
$sql = "UPDATE `$t` SET ".implode(',',$set)." WHERE `$idCol` = :__id".$tw." LIMIT 1";
|
|
|
|
$stmt = $pdo->prepare($sql);
|
|
|
|
foreach ($data as $k=>$v) $stmt->bindValue(":$k", $v);
|
|
|
|
$stmt->bindValue(':__id', $id);
|
|
|
|
foreach ($tp as $k=>$v) $stmt->bindValue($k,$v);
|
|
|
|
$stmt->execute();
|
|
|
|
|
|
|
|
respond(['ok'=>true,'kind'=>$kind,'id'=>$id,'updated'=>array_keys($data)]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ---------- DELETE ---------- */
|
|
|
|
case 'templates.delete':
|
|
|
|
case 'sections.delete':
|
|
|
|
case 'blocks.delete':
|
|
|
|
case 'snippets.delete': {
|
|
|
|
$auth = $requireAuth();
|
|
|
|
|
|
|
|
$kind = explode('.', $action)[0];
|
|
|
|
$t = $tableMap[$kind];
|
|
|
|
[$idCol, $allCols] = $resolveIdCol($kind);
|
|
|
|
|
|
|
|
$id = $pullId($in);
|
|
|
|
if ($id === null || $id === '') fail('id required', null, 422);
|
|
|
|
|
|
|
|
[$tw,$tp] = $tenantWhere($auth);
|
|
|
|
$sql = "DELETE FROM `$t` WHERE `$idCol` = :__id".$tw." LIMIT 1";
|
|
|
|
$stmt = $pdo->prepare($sql);
|
|
|
|
$stmt->bindValue(':__id', $id);
|
|
|
|
foreach ($tp as $k=>$v) $stmt->bindValue($k,$v);
|
|
|
|
$stmt->execute();
|
|
|
|
|
|
|
|
respond(['ok'=>true,'kind'=>$kind,'id'=>$id,'deleted'=>true]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ---------- Platzhalter für kommende Features ---------- */
|
|
|
|
case 'render.preview':
|
|
|
|
fail('Not implemented', 'Preview wird im Feature-Patch geliefert', 501);
|
|
|
|
case 'templates.test_send':
|
|
|
|
fail('Not implemented', 'Testversand wird im Feature-Patch geliefert', 501);
|
|
|
|
case 'export.render':
|
|
|
|
fail('Not implemented', 'Export-API wird im Feature-Patch geliefert', 501);
|
|
|
|
|
|
|
|
default:
|
|
|
|
fail('Unknown action', $action ?: 'missing', 404);
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (Throwable $e) {
|
|
|
|
fail('Server error', get_class($e).': '.$e->getMessage(), 500);
|
|
|
|
}
|