Initial commit

This commit is contained in:
Robert Nasarek 2026-06-25 09:11:23 +02:00
commit 05c65aad4d
155 changed files with 93617 additions and 0 deletions

View file

@ -0,0 +1,89 @@
<?php
require __DIR__ . '/common.php';
$root = workspace_root();
$body = file_get_contents('php://input');
$data = json_decode($body, true);
if (!$data || !isset($data['action'])) { http_response_code(400); json_response(['error'=>'invalid_request']); }
$action = $data['action'];
if ($action === 'clear_cache') {
$target = $root . '/viewer/build';
$out = '';
if (is_dir($target)) {
// remove all files
$cmd = 'rm -rf ' . escapeshellarg($target) . '/*';
$out = shell_exec($cmd . ' 2>&1');
} else { $out = 'no build dir'; }
json_response(['ok'=>true,'output'=>$out]);
}
if ($action === 'rebuild_thumbs') {
$script = $root . '/scripts/render.sh';
if (file_exists($script) && is_executable($script)) {
$out = shell_exec(escapeshellcmd($script) . ' 2>&1');
json_response(['ok'=>true,'output'=>$out]);
}
http_response_code(500); json_response(['error'=>'script_missing']);
}
if ($action === 'diagnostics') {
$out = [];
$out['php'] = shell_exec('php -v 2>&1');
$out['uname'] = shell_exec('uname -a 2>&1');
$out['df'] = shell_exec('df -h 2>&1');
json_response(['ok'=>true,'output'=>$out]);
}
if ($action === 'entity_resave') {
$entity_id = $data['entity_id'] ?? null;
$entity_type = $data['entity_type'] ?? 'wisski_individual';
if (!$entity_id) { http_response_code(400); json_response(['error'=>'missing_entity_id']); }
$out = "Attempting to re-save entity $entity_type:$entity_id ...\n\n";
// Try to bootstrap Drupal if available
$drupal_root = $root;
if (file_exists($drupal_root . '/index.php')) {
$out .= "Found index.php, attempting Drupal bootstrap...\n";
try {
// Push to Drupal context
$cwd = getcwd();
chdir($drupal_root);
// Simple Drupal load without full bootstrap (safer)
if (function_exists('drush_main')) {
$out .= "Drush detected, will attempt re-save.\n";
}
// Try using Drupal's entity loader via autoloader
if (file_exists($drupal_root . '/vendor/autoload.php')) {
require_once $drupal_root . '/vendor/autoload.php';
$out .= "Drupal autoloader loaded.\n";
// Simple check: can we access Drupal\Core ?
if (class_exists('Drupal\Core\Entity\EntityTypeManager')) {
$out .= "Drupal namespace available - attempting entity save.\n";
// Real bootstrap would require more setup
$out .= "INFO: Full entity re-save requires Drupal context.\n";
}
}
chdir($cwd);
$out .= "\nTo re-save this entity, use Drupal CLI:\n";
$out .= " drush entity:save $entity_type $entity_id\n";
$out .= "Or access Drupal admin panel and update the entity.\n";
} catch (\Throwable $e) {
$out .= "Bootstrap check failed: " . $e->getMessage() . "\n";
$out .= "\nTo re-save this entity, use Drupal CLI:\n";
$out .= " drush entity:save $entity_type $entity_id\n";
}
} else {
$out .= "Drupal not detected in this directory.\n";
$out .= "To re-save an entity, use:\n";
$out .= " drush entity:save $entity_type $entity_id\n";
$out .= "Or trigger via Drupal admin panel.\n";
}
json_response(['ok'=>true,'output'=>$out]);
}
http_response_code(400); json_response(['error'=>'unknown_action']);

View file

@ -0,0 +1,62 @@
<?php
require __DIR__ . '/common.php';
$root = workspace_root();
// GET ?target=settings|env -> list backups (most recent first, limited to 5)
// POST restore {target, file}
function list_backups($target) {
global $root;
if ($target === 'settings') {
$path = $root . '/viewer-settings.json';
$base = 'viewer-settings.json';
} elseif ($target === 'env') {
$path = $root . '/scripts/.env';
$base = '.env';
$dir = dirname($path);
$base = basename($path);
} else {
return ['error'=>'invalid_target'];
}
$dir = dirname($path);
$pattern = $dir . '/' . $base . '.*';
$files = glob($pattern);
// filter timestamped copies only (YYYYmmdd-HHMMSS)
$backups = array_filter($files, function($f) use ($base){ return preg_match('/' . preg_quote($base, '/') . '\.\d{8}-\d{6}$/', $f); });
usort($backups, function($a,$b){ return filemtime($b) - filemtime($a); });
$backups = array_slice($backups, 0, 5);
$out = [];
foreach($backups as $b) $out[] = ['file'=>basename($b),'path'=>$b,'ts'=>date('c', filemtime($b))];
return ['backups'=>$out];
}
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$target = $_GET['target'] ?? null;
if (!$target) { http_response_code(400); json_response(['error'=>'missing_target']); }
json_response(list_backups($target));
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$body = file_get_contents('php://input');
$data = json_decode($body, true);
if (!$data || !isset($data['target']) || !isset($data['file'])) { http_response_code(400); json_response(['error'=>'invalid_request']); }
$target = $data['target'];
$file = basename($data['file']);
if ($target === 'settings') {
$path = $root . '/viewer-settings.json';
$dir = dirname($path);
$src = $dir . '/' . $file;
} elseif ($target === 'env') {
$path = $root . '/scripts/.env';
$dir = dirname($path);
$src = $dir . '/' . $file;
} else { http_response_code(400); json_response(['error'=>'invalid_target']); }
if (!file_exists($src)) { http_response_code(404); json_response(['error'=>'not_found']); }
// make a backup of current file before restore
if (file_exists($path)) backup_file($path);
if (!copy($src, $path)) { http_response_code(500); json_response(['error'=>'restore_failed']); }
json_response(['ok'=>true]);
}
http_response_code(405); json_response(['error'=>'method_not_allowed']);

View file

@ -0,0 +1,55 @@
<?php
session_start();
if (!isset($_SESSION['admin'])) {
http_response_code(401);
echo json_encode(['error' => 'unauthorized']);
exit;
}
function json_response($data) {
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
function workspace_root() {
// admin/api is at viewer/admin/api -> go up 3
return realpath(__DIR__ . '/../../../');
}
function backup_file($path, $keep = 5) {
if (!file_exists($path)) return;
$dir = dirname($path);
$base = basename($path);
// simple .bak (latest)
copy($path, $dir . '/' . $base . '.bak');
// timestamped
$ts = date('Ymd-His');
$copy = $dir . '/' . $base . '.' . $ts;
copy($path, $copy);
// rotate old backups: match $base.* in same dir (exclude .bak)
$pattern = $dir . '/' . $base . '.*';
$files = glob($pattern);
// filter timestamped copies only (YYYYmmdd-HHMMSS)
$backups = array_filter($files, function($f) use ($base){ return preg_match('/' . preg_quote($base, '/') . '\.\d{8}-\d{6}$/', $f); });
usort($backups, function($a,$b){ return filemtime($b) - filemtime($a); });
if (count($backups) > $keep) {
$remove = array_slice($backups, $keep);
foreach($remove as $r) @unlink($r);
}
}
function save_uploaded_schema($targetPath) {
if (!isset($_FILES['file'])) return ['error'=>'no_file'];
$f = $_FILES['file'];
if ($f['error'] !== UPLOAD_ERR_OK) return ['error'=>'upload_error'];
// validate json
$content = file_get_contents($f['tmp_name']);
if (json_decode($content) === null) return ['error'=>'invalid_json'];
// backup existing
if (file_exists($targetPath)) backup_file($targetPath, intval(getenv('ADMIN_BACKUP_KEEP') ?: 10));
if (!is_dir(dirname($targetPath))) mkdir(dirname($targetPath), 0755, true);
if (!move_uploaded_file($f['tmp_name'], $targetPath)) return ['error'=>'move_failed'];
return ['ok'=>true];
}

53
viewer/admin/api/env.php Normal file
View file

@ -0,0 +1,53 @@
<?php
require __DIR__ . '/common.php';
$root = workspace_root();
$target = $root . '/scripts/.env';
$example = $root . '/scripts/.env.example';
// Ensure .env exists by copying example
if (!file_exists($target)) {
if (file_exists($example)) copy($example, $target);
else file_put_contents($target, "# .env\n");
}
function parse_env($path) {
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$out = [];
foreach ($lines as $line) {
$line = trim($line);
if ($line === '' || strpos($line, '#') === 0) continue;
if (strpos($line, '=') !== false) {
list($k,$v) = explode('=', $line, 2);
$out[trim($k)] = trim($v);
}
}
return $out;
}
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
json_response(parse_env($target));
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$body = file_get_contents('php://input');
$decoded = json_decode($body, true);
if (!is_array($decoded)) {
http_response_code(400);
json_response(['error' => 'invalid_json']);
}
// backup
backup_file($target);
// write env
$lines = [];
foreach ($decoded as $k => $v) {
$lines[] = $k . '=' . $v;
}
$tmp = $target . '.tmp';
file_put_contents($tmp, implode("\n", $lines) . "\n");
rename($tmp, $target);
json_response(['ok' => true]);
}
http_response_code(405);
json_response(['error' => 'method_not_allowed']);

View file

@ -0,0 +1,17 @@
<?php
require __DIR__ . '/common.php';
$root = workspace_root();
$p = $root . '/scripts/.env.schema.json';
if (file_exists($p)) {
if (isset($_GET['delete']) && $_SERVER['REQUEST_METHOD'] === 'POST') {
if (@unlink($p)) json_response(['ok'=>true]);
http_response_code(500); json_response(['error'=>'delete_failed']);
}
header('Content-Type: application/json');
echo file_get_contents($p);
exit;
}
http_response_code(204);
exit;

36
viewer/admin/api/hdri.php Normal file
View file

@ -0,0 +1,36 @@
<?php
require __DIR__ . '/common.php';
$root = workspace_root();
$dir = $root . '/viewer/hdri';
if (!is_dir($dir)) mkdir($dir, 0755, true);
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$files = array_values(array_filter(scandir($dir), function($f){ return !in_array($f, ['.','..']); }));
json_response(['files' => $files]);
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// If multipart upload
if (!empty($_FILES['file'])) {
$f = $_FILES['file'];
if ($f['error'] !== UPLOAD_ERR_OK) { http_response_code(400); json_response(['error'=>'upload_error']); }
$name = basename($f['name']);
$target = $dir . '/' . $name;
if (!move_uploaded_file($f['tmp_name'], $target)) { http_response_code(500); json_response(['error'=>'move_failed']); }
json_response(['ok'=>true,'file'=>$name]);
}
// JSON action (delete)
$body = file_get_contents('php://input');
$data = json_decode($body, true);
if (!$data) { http_response_code(400); json_response(['error'=>'invalid_json']); }
if (isset($data['action']) && $data['action'] === 'delete' && isset($data['file'])) {
$file = basename($data['file']);
$path = $dir . '/' . $file;
if (file_exists($path)) { unlink($path); json_response(['ok'=>true]); }
http_response_code(404); json_response(['error'=>'not_found']);
}
http_response_code(400); json_response(['error'=>'unknown_action']);
}
http_response_code(405); json_response(['error'=>'method_not_allowed']);

View file

@ -0,0 +1,41 @@
<?php
require __DIR__ . '/common.php';
$root = workspace_root();
$target = $root . '/viewer-settings.json';
$example1 = $root . '/viewer-settings-example.json';
$example2 = $root . '/viewer/viewer-settings-example.json';
// Ensure file exists by copying example if needed
if (!file_exists($target)) {
if (file_exists($example1)) copy($example1, $target);
elseif (file_exists($example2)) copy($example2, $target);
else file_put_contents($target, json_encode(new stdClass()));
}
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$data = file_get_contents($target);
header('Content-Type: application/json');
echo $data;
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$body = file_get_contents('php://input');
// validate JSON
$decoded = json_decode($body, true);
if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
http_response_code(400);
json_response(['error' => 'invalid_json']);
}
// backup
backup_file($target);
// atomic write
$tmp = $target . '.tmp';
file_put_contents($tmp, json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
rename($tmp, $target);
json_response(['ok' => true]);
}
http_response_code(405);
json_response(['error' => 'method_not_allowed']);

View file

@ -0,0 +1,24 @@
<?php
require __DIR__ . '/common.php';
$root = workspace_root();
$candidates = [
$root . '/viewer-settings.schema.json',
$root . '/viewer/viewer-settings.schema.json',
];
foreach ($candidates as $p) {
if (file_exists($p)) {
if (isset($_GET['delete']) && $_SERVER['REQUEST_METHOD'] === 'POST') {
if (@unlink($p)) json_response(['ok'=>true]);
http_response_code(500); json_response(['error'=>'delete_failed']);
}
header('Content-Type: application/json');
echo file_get_contents($p);
exit;
}
}
// no schema found
http_response_code(204);
exit;

View file

@ -0,0 +1,24 @@
<?php
require __DIR__ . '/common.php';
$root = workspace_root();
// expects multipart/form-data with field 'file' and 'target' in POST (values: 'settings' or 'env')
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405); json_response(['error'=>'method_not_allowed']);
}
$target = $_POST['target'] ?? $_GET['target'] ?? null;
if (!$target) { http_response_code(400); json_response(['error'=>'missing_target']); }
if ($target === 'settings') {
$dest = $root . '/viewer-settings.schema.json';
} elseif ($target === 'env') {
$dest = $root . '/scripts/.env.schema.json';
} else {
http_response_code(400); json_response(['error'=>'invalid_target']);
}
$res = save_uploaded_schema($dest);
if (isset($res['ok'])) json_response($res);
http_response_code(400); json_response($res);