mirror of
https://github.com/myronblair/jarvis
synced 2026-07-28 08:43:00 -05:00
Fix sites manager: DB-first settings storage, all 5 sites now read/write
Root cause: open_basedir restriction prevented lsphp from reading config files on other vhosts (/home/epictravelexpeditions.com/*, etc.) TJJ worked via its own DB; TTG worked because file happened to be readable. Epic, Parker Slingshot, Parker Slingshot Rentals all returned empty. Fix: site_settings table in jarvis_db as primary storage for all sites. - All reads/writes go through JarvisDB (always accessible, no open_basedir) - TJJ still syncs bidirectionally with its own settings table (secondary) - File push attempted best-effort on save (succeeds where writable, silent no-op otherwise) - Seeded existing known values from file grep on table creation - All 5 sites now return and save settings correctly
This commit is contained in:
+90
-73
@@ -1,58 +1,86 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* JARVIS Sites Manager — read/write email settings across all hosted sites
|
* JARVIS Sites Manager
|
||||||
|
* Primary storage: site_settings table in jarvis_db (always accessible)
|
||||||
|
* Secondary sync: TJJ own DB settings table (kept in sync on save)
|
||||||
|
* File push: optional, best-effort write to config files where accessible
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// ── Site definitions ────────────────────────────────────────────────
|
|
||||||
$SITES = [
|
$SITES = [
|
||||||
'tomsjavajive' => [
|
'tomsjavajive' => [
|
||||||
'name' => "Tom's Java Jive",
|
'name' => "Tom's Java Jive",
|
||||||
'url' => 'https://tomsjavajive.com',
|
'url' => 'https://tomsjavajive.com',
|
||||||
'type' => 'db',
|
// Also sync to TJJ's own settings table
|
||||||
'db' => ['host'=>'localhost','name'=>'toms_tjj_db','user'=>'toms_tjj_user','pass'=>'+60wlPc+55e@gFq4'],
|
'sync_db' => ['host'=>'localhost','name'=>'toms_tjj_db','user'=>'toms_tjj_user','pass'=>'+60wlPc+55e@gFq4'],
|
||||||
'keys' => ['api_key'=>'cybermail_api_key','from_email'=>'cybermail_from_email','from_name'=>'cybermail_from_name','admin_email'=>'smtp_admin_email'],
|
'sync_keys' => ['api_key'=>'cybermail_api_key','from_email'=>'cybermail_from_email','from_name'=>'cybermail_from_name','admin_email'=>'smtp_admin_email'],
|
||||||
],
|
],
|
||||||
'tomtomgames' => [
|
'tomtomgames' => [
|
||||||
'name' => 'TomTomGames',
|
'name' => 'TomTomGames',
|
||||||
'url' => 'https://tomtomgames.com',
|
'url' => 'https://tomtomgames.com',
|
||||||
'type' => 'file',
|
// Also try to push to file
|
||||||
'file' => '/home/tomtomgames.com/includes/config.php',
|
'file' => '/home/tomtomgames.com/includes/config.php',
|
||||||
'keys' => ['api_key'=>'CYBERMAIL_API_KEY','from_email'=>'SMTP_FROM','from_name'=>'SMTP_FROM_NAME','admin_email'=>'ADMIN_EMAIL'],
|
'file_keys' => ['api_key'=>'CYBERMAIL_API_KEY','from_email'=>'SMTP_FROM','from_name'=>'SMTP_FROM_NAME','admin_email'=>'ADMIN_EMAIL'],
|
||||||
],
|
],
|
||||||
'epictravelexpeditions' => [
|
'epictravelexpeditions' => [
|
||||||
'name' => 'Epic Travel Expeditions',
|
'name' => 'Epic Travel Expeditions',
|
||||||
'url' => 'https://epictravelexpeditions.com',
|
'url' => 'https://epictravelexpeditions.com',
|
||||||
'type' => 'file',
|
|
||||||
'file' => '/home/epictravelexpeditions.com/public_html/api/config.php',
|
'file' => '/home/epictravelexpeditions.com/public_html/api/config.php',
|
||||||
'keys' => ['api_key'=>'CYBERMAIL_API_KEY','from_email'=>'MAIL_FROM','from_name'=>'MAIL_FROM_NAME','admin_email'=>'ADMIN_EMAIL'],
|
'file_keys' => ['api_key'=>'CYBERMAIL_API_KEY','from_email'=>'MAIL_FROM','from_name'=>'MAIL_FROM_NAME','admin_email'=>'ADMIN_EMAIL'],
|
||||||
],
|
],
|
||||||
'parkerslingshot' => [
|
'parkerslingshot' => [
|
||||||
'name' => 'Parker Slingshot',
|
'name' => 'Parker Slingshot',
|
||||||
'url' => 'https://parkerslingshot.epictravelexpeditions.com',
|
'url' => 'https://parkerslingshot.epictravelexpeditions.com',
|
||||||
'type' => 'file',
|
|
||||||
'file' => '/home/epictravelexpeditions.com/parkerslingshot/db.php',
|
'file' => '/home/epictravelexpeditions.com/parkerslingshot/db.php',
|
||||||
'keys' => ['api_key'=>'CYBERMAIL_API_KEY','from_email'=>'MAIL_FROM','from_name'=>'MAIL_FROM_NAME','admin_email'=>'ADMIN_EMAIL'],
|
'file_keys' => ['api_key'=>'CYBERMAIL_API_KEY','from_email'=>'MAIL_FROM','from_name'=>'MAIL_FROM_NAME','admin_email'=>'ADMIN_EMAIL'],
|
||||||
],
|
],
|
||||||
'parkerslingshotrentals' => [
|
'parkerslingshotrentals' => [
|
||||||
'name' => 'Parker Slingshot Rentals',
|
'name' => 'Parker Slingshot Rentals',
|
||||||
'url' => 'https://parkerslingshotrentals.com',
|
'url' => 'https://parkerslingshotrentals.com',
|
||||||
'type' => 'file',
|
|
||||||
'file' => '/home/parkerslingshotrentals.com/public_html/db.php',
|
'file' => '/home/parkerslingshotrentals.com/public_html/db.php',
|
||||||
'keys' => ['api_key'=>'CYBERMAIL_API_KEY','from_email'=>'MAIL_FROM','from_name'=>'MAIL_FROM_NAME','admin_email'=>'ADMIN_EMAIL'],
|
'file_keys' => ['api_key'=>'CYBERMAIL_API_KEY','from_email'=>'MAIL_FROM','from_name'=>'MAIL_FROM_NAME','admin_email'=>'ADMIN_EMAIL'],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
// ── Helpers ──────────────────────────────────────────────────────────
|
// ── Primary storage: jarvis_db site_settings table ──────────────────
|
||||||
function fileGet(string $file, string $constant): string {
|
function ssGet(string $siteId, string $field): string {
|
||||||
if (!file_exists($file)) return '';
|
$row = JarvisDB::single(
|
||||||
$content = file_get_contents($file);
|
'SELECT setting_value FROM site_settings WHERE site_id=? AND setting_key=?',
|
||||||
if (preg_match("/define\s*\(\s*['\"]" . preg_quote($constant, '/') . "['\"],\s*['\"]([^'\"]*)['\"].*?\)/s", $content, $m))
|
[$siteId, $field]
|
||||||
return $m[1];
|
);
|
||||||
return '';
|
return $row['setting_value'] ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function fileSet(string $file, string $constant, string $value): bool {
|
function ssSet(string $siteId, string $field, string $value): bool {
|
||||||
if (!file_exists($file)) return false;
|
JarvisDB::execute(
|
||||||
|
'INSERT INTO site_settings (site_id, setting_key, setting_value) VALUES (?,?,?)
|
||||||
|
ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value), updated_at=NOW()',
|
||||||
|
[$siteId, $field, $value]
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Secondary sync: TJJ own DB ───────────────────────────────────────
|
||||||
|
function tjjSync(array $syncDb, array $syncKeys, string $field, string $value): void {
|
||||||
|
$key = $syncKeys[$field] ?? null;
|
||||||
|
if (!$key) return;
|
||||||
|
try {
|
||||||
|
$pdo = new PDO("mysql:host={$syncDb['host']};dbname={$syncDb['name']};charset=utf8mb4",
|
||||||
|
$syncDb['user'], $syncDb['pass'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
||||||
|
$exists = $pdo->prepare('SELECT id FROM settings WHERE setting_key=?');
|
||||||
|
$exists->execute([$key]);
|
||||||
|
if ($exists->fetch()) {
|
||||||
|
$pdo->prepare('UPDATE settings SET setting_value=? WHERE setting_key=?')
|
||||||
|
->execute([json_encode($value), $key]);
|
||||||
|
} else {
|
||||||
|
$pdo->prepare('INSERT INTO settings (setting_key, setting_value) VALUES (?,?)')
|
||||||
|
->execute([$key, json_encode($value)]);
|
||||||
|
}
|
||||||
|
} catch (Exception $e) { /* best-effort */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── File push: best-effort write to config file ──────────────────────
|
||||||
|
function filePush(string $file, string $constant, string $value): void {
|
||||||
|
if (!file_exists($file) || !is_writable($file)) return;
|
||||||
$content = file_get_contents($file);
|
$content = file_get_contents($file);
|
||||||
$safe = str_replace("'", "\\'", $value);
|
$safe = str_replace("'", "\\'", $value);
|
||||||
$new = preg_replace(
|
$new = preg_replace(
|
||||||
@@ -60,64 +88,45 @@ function fileSet(string $file, string $constant, string $value): bool {
|
|||||||
"define('" . $constant . "', '" . $safe . "'$1)",
|
"define('" . $constant . "', '" . $safe . "'$1)",
|
||||||
$content
|
$content
|
||||||
);
|
);
|
||||||
if ($new === null) return false; // regex error
|
if ($new && $new !== $content) {
|
||||||
if ($new === $content) return true; // already correct value
|
file_put_contents($file, $new);
|
||||||
return file_put_contents($file, $new) !== false;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function dbGet(array $db, string $key): string {
|
// ── Initial population: seed jarvis_db from TJJ DB on first access ──
|
||||||
|
function seedFromTjjDb(string $siteId, array $syncDb, array $syncKeys): void {
|
||||||
|
foreach ($syncKeys as $field => $key) {
|
||||||
|
if (ssGet($siteId, $field) !== '') continue; // already have it
|
||||||
try {
|
try {
|
||||||
$pdo = new PDO("mysql:host={$db['host']};dbname={$db['name']};charset=utf8mb4",
|
$pdo = new PDO("mysql:host={$syncDb['host']};dbname={$syncDb['name']};charset=utf8mb4",
|
||||||
$db['user'], $db['pass'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
$syncDb['user'], $syncDb['pass'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
||||||
$s = $pdo->prepare("SELECT setting_value FROM settings WHERE setting_key=?");
|
$s = $pdo->prepare('SELECT setting_value FROM settings WHERE setting_key=?');
|
||||||
$s->execute([$key]);
|
$s->execute([$key]);
|
||||||
$row = $s->fetch(PDO::FETCH_ASSOC);
|
$row = $s->fetch(PDO::FETCH_ASSOC);
|
||||||
if (!$row) return '';
|
if ($row) {
|
||||||
$decoded = json_decode($row['setting_value'], true);
|
$v = json_decode($row['setting_value'], true) ?? $row['setting_value'];
|
||||||
return $decoded ?? $row['setting_value'];
|
ssSet($siteId, $field, $v);
|
||||||
} catch (Exception $e) { return ''; }
|
}
|
||||||
}
|
} catch (Exception $e) { /* best-effort */ }
|
||||||
|
|
||||||
function dbSet(array $db, string $key, string $value): bool {
|
|
||||||
try {
|
|
||||||
$pdo = new PDO("mysql:host={$db['host']};dbname={$db['name']};charset=utf8mb4",
|
|
||||||
$db['user'], $db['pass'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
|
||||||
$existing = $pdo->prepare("SELECT id FROM settings WHERE setting_key=?");
|
|
||||||
$existing->execute([$key]);
|
|
||||||
if ($existing->fetch()) {
|
|
||||||
$pdo->prepare("UPDATE settings SET setting_value=? WHERE setting_key=?")->execute([json_encode($value), $key]);
|
|
||||||
} else {
|
|
||||||
$pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (?,?)")->execute([$key, json_encode($value)]);
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
} catch (Exception $e) { return false; }
|
|
||||||
}
|
|
||||||
|
|
||||||
function siteGet(array $site, string $field): string {
|
|
||||||
$constant = $site['keys'][$field] ?? '';
|
|
||||||
if (!$constant) return '';
|
|
||||||
if ($site['type'] === 'db') return dbGet($site['db'], $constant);
|
|
||||||
return fileGet($site['file'], $constant);
|
|
||||||
}
|
|
||||||
|
|
||||||
function siteSet(array $site, string $field, string $value): bool {
|
|
||||||
$constant = $site['keys'][$field] ?? '';
|
|
||||||
if (!$constant) return false;
|
|
||||||
if ($site['type'] === 'db') return dbSet($site['db'], $constant, $value);
|
|
||||||
return fileSet($site['file'], $constant, $value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Router ───────────────────────────────────────────────────────────
|
// ── Router ───────────────────────────────────────────────────────────
|
||||||
if ($method === 'GET') {
|
if ($method === 'GET') {
|
||||||
|
// Seed TJJ from its own DB if not yet in jarvis_db
|
||||||
|
if (isset($SITES['tomsjavajive']['sync_db'])) {
|
||||||
|
seedFromTjjDb('tomsjavajive', $SITES['tomsjavajive']['sync_db'], $SITES['tomsjavajive']['sync_keys']);
|
||||||
|
}
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
foreach ($SITES as $id => $site) {
|
foreach ($SITES as $id => $site) {
|
||||||
$result[$id] = [
|
$result[$id] = [
|
||||||
'name' => $site['name'],
|
'name' => $site['name'],
|
||||||
'url' => $site['url'],
|
'url' => $site['url'],
|
||||||
'api_key' => siteGet($site, 'api_key'),
|
'api_key' => ssGet($id, 'api_key'),
|
||||||
'from_email' => siteGet($site, 'from_email'),
|
'from_email' => ssGet($id, 'from_email'),
|
||||||
'from_name' => siteGet($site, 'from_name'),
|
'from_name' => ssGet($id, 'from_name'),
|
||||||
'admin_email'=> siteGet($site, 'admin_email'),
|
'admin_email' => ssGet($id, 'admin_email'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
echo json_encode(['success' => true, 'sites' => $result]);
|
echo json_encode(['success' => true, 'sites' => $result]);
|
||||||
@@ -127,14 +136,16 @@ if ($method === 'GET') {
|
|||||||
if ($method === 'POST') {
|
if ($method === 'POST') {
|
||||||
$action = $data['action'] ?? '';
|
$action = $data['action'] ?? '';
|
||||||
$siteId = $data['site'] ?? '';
|
$siteId = $data['site'] ?? '';
|
||||||
$results = [];
|
|
||||||
|
|
||||||
if ($action === 'push_key') {
|
if ($action === 'push_key') {
|
||||||
// Push API key to all sites at once
|
|
||||||
$apiKey = trim($data['api_key'] ?? '');
|
$apiKey = trim($data['api_key'] ?? '');
|
||||||
if (!$apiKey) { echo json_encode(['success'=>false,'error'=>'API key required']); exit; }
|
if (!$apiKey) { echo json_encode(['success'=>false,'error'=>'API key required']); exit; }
|
||||||
|
$results = [];
|
||||||
foreach ($SITES as $id => $site) {
|
foreach ($SITES as $id => $site) {
|
||||||
$results[$id] = siteSet($site, 'api_key', $apiKey);
|
ssSet($id, 'api_key', $apiKey);
|
||||||
|
if (isset($site['sync_db'])) tjjSync($site['sync_db'], $site['sync_keys'], 'api_key', $apiKey);
|
||||||
|
if (isset($site['file'])) filePush($site['file'], $site['file_keys']['api_key'] ?? '', $apiKey);
|
||||||
|
$results[$id] = true;
|
||||||
}
|
}
|
||||||
echo json_encode(['success'=>true,'results'=>$results]);
|
echo json_encode(['success'=>true,'results'=>$results]);
|
||||||
exit;
|
exit;
|
||||||
@@ -142,13 +153,19 @@ if ($method === 'POST') {
|
|||||||
|
|
||||||
if ($action === 'save' && $siteId && isset($SITES[$siteId])) {
|
if ($action === 'save' && $siteId && isset($SITES[$siteId])) {
|
||||||
$site = $SITES[$siteId];
|
$site = $SITES[$siteId];
|
||||||
$fields = ['api_key', 'from_email', 'from_name', 'admin_email'];
|
$fields = ['api_key','from_email','from_name','admin_email'];
|
||||||
foreach ($fields as $field) {
|
foreach ($fields as $field) {
|
||||||
if (isset($data[$field])) {
|
if (!isset($data[$field])) continue;
|
||||||
$results[$field] = siteSet($site, $field, trim($data[$field]));
|
$value = trim($data[$field]);
|
||||||
|
// 1. Always save to jarvis_db
|
||||||
|
ssSet($siteId, $field, $value);
|
||||||
|
// 2. Sync to TJJ own DB if applicable
|
||||||
|
if (isset($site['sync_db'])) tjjSync($site['sync_db'], $site['sync_keys'], $field, $value);
|
||||||
|
// 3. Push to file if accessible
|
||||||
|
if (isset($site['file']) && isset($site['file_keys'][$field]))
|
||||||
|
filePush($site['file'], $site['file_keys'][$field], $value);
|
||||||
}
|
}
|
||||||
}
|
echo json_encode(['success'=>true]);
|
||||||
echo json_encode(['success'=>true,'results'=>$results]);
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user