SIMON Master Console Rebuild
True command center for SIMON + WEB360
This rebuild gives you a cleaner production spine: interactive tree, live viewer, guarded editor, dynamic graphics, architecture map, and note storage. It is designed to sit beside your existing
console.php
and become the stronger replacement path.
Dashboard
Tree
Viewer
System Map
File Registry
Notes
Files
15,365
Folders
427
Scanned Size
3.85 GB
PHP Files
948
Editable Text Files
12,621
File viewer
guarded to /htdocs
/live/bootstrap.php
<?php declare(strict_types=1); const SIMON_FEED_VERSION = '3.0.0-mission-control'; const SIMON_ROOT = __DIR__; const SIMON_CACHE_DIR = SIMON_ROOT . '/cache'; const SIMON_LOG_DIR = SIMON_ROOT . '/logs'; const SIMON_DATA_DIR = SIMON_ROOT . '/data'; const SIMON_BACKUP_DIR = SIMON_ROOT . '/backups'; const SIMON_FEEDS_FILE = SIMON_ROOT . '/feeds.json'; const SIMON_DIAGNOSTIC_LOG = SIMON_LOG_DIR . '/diagnostics.ndjson'; // Production-safe error handling: log details, avoid leaking internals to visitors. ini_set('log_errors', '1'); ini_set('error_log', SIMON_LOG_DIR . '/php_errors.log'); if (!defined('SIMON_DEBUG')) define('SIMON_DEBUG', isset($_GET['debug']) && $_GET['debug'] === '1'); ini_set('display_errors', SIMON_DEBUG ? '1' : '0'); error_reporting(E_ALL); set_exception_handler(function (Throwable $e): void { @file_put_contents(SIMON_DIAGNOSTIC_LOG, json_encode([ 'at'=>gmdate('c'),'type'=>'uncaught_exception','message'=>$e->getMessage(), 'file'=>$e->getFile(),'line'=>$e->getLine() ], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE)." ", FILE_APPEND|LOCK_EX); if (!headers_sent()) http_response_code(500); if (SIMON_DEBUG) echo '<pre>'.htmlspecialchars((string)$e, ENT_QUOTES, 'UTF-8').'</pre>'; else echo 'SIMON encountered an error. Open system_check.php for diagnostics.'; }); foreach ([SIMON_CACHE_DIR, SIMON_LOG_DIR, SIMON_DATA_DIR, SIMON_BACKUP_DIR] as $dir) { if (!is_dir($dir)) @mkdir($dir, 0775, true); } function simon_slug(string $value, string $fallback = 'feed'): string { $value = strtolower(trim($value)); $value = preg_replace('/[^a-z0-9_\-]+/', '_', $value) ?? ''; $value = trim($value, '_-'); return $value !== '' ? $value : $fallback; } function simon_backup_file(string $path, string $reason = 'save'): ?string { if (!is_file($path)) return null; if (!is_dir(SIMON_BACKUP_DIR)) @mkdir(SIMON_BACKUP_DIR, 0775, true); $name = pathinfo($path, PATHINFO_FILENAME); $stamp = gmdate('Ymd_His'); $safeReason = simon_slug($reason, 'backup'); $dest = SIMON_BACKUP_DIR . '/' . $name . '_' . $safeReason . '_' . $stamp . '.json'; return @copy($path, $dest) ? $dest : null; } function simon_normalize_feed(array $input): array { $id = simon_slug((string)($input['id'] ?? ''), 'feed_' . substr(sha1((string)microtime(true)), 0, 8)); $engine = simon_slug((string)($input['engine'] ?? 'rss'), 'rss'); $feed = [ 'id' => $id, 'name' => trim((string)($input['name'] ?? $id)) ?: $id, 'category' => simon_slug((string)($input['category'] ?? 'general'), 'general'), 'engine' => $engine, 'enabled' => !empty($input['enabled']), 'refresh' => max(30, (int)($input['refresh'] ?? 900)), 'max_items' => max(1, min(100, (int)($input['max_items'] ?? 30))), 'priority' => max(0, min(100, (int)($input['priority'] ?? 70))), 'color' => preg_match('/^#[0-9a-fA-F]{6}$/', (string)($input['color'] ?? '')) ? (string)$input['color'] : '#4be3ff', ]; foreach (['url','stream','place'] as $k) { if (isset($input[$k]) && trim((string)$input[$k]) !== '') $feed[$k] = trim((string)$input[$k]); } foreach (['lat','lon'] as $k) { if (isset($input[$k]) && $input[$k] !== '') $feed[$k] = (float)$input[$k]; } if (isset($input['params']) && is_array($input['params'])) $feed['params'] = $input['params']; if (isset($input['map']) && is_array($input['map'])) $feed['map'] = $input['map']; return $feed; } function simon_json_response(array $payload, int $status = 200): void { http_response_code($status); header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); echo json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); exit; } function simon_read_json_file(string $path, $default = []): mixed { if (!is_file($path)) return $default; $raw = file_get_contents($path); if ($raw === false || trim($raw) === '') return $default; $data = json_decode($raw, true); return json_last_error() === JSON_ERROR_NONE ? $data : $default; } function simon_write_json_file(string $path, mixed $data): bool { $tmp = $path . '.tmp'; $json = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); if ($json === false) return false; if (file_put_contents($tmp, $json, LOCK_EX) === false) return false; return rename($tmp, $path); } function simon_secrets(): array { $paths = [ SIMON_ROOT . '/private/secrets.php', SIMON_ROOT . '/secrets.php', dirname(SIMON_ROOT) . '/private/secure/secrets.php', dirname(dirname(SIMON_ROOT)) . '/private/secure/secrets.php', ]; foreach ($paths as $path) { if (is_file($path)) { $data = require $path; return is_array($data) ? $data : []; } } return []; } function simon_log(string $type, array $event): void { $event['type'] = $type; $event['at'] = gmdate('c'); $line = json_encode($event, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n"; @file_put_contents(SIMON_LOG_DIR . '/feed_events.ndjson', $line, FILE_APPEND | LOCK_EX); } function simon_cache_key(array $feed, array $extra = []): string { return preg_replace('/[^a-zA-Z0-9_\-]/', '_', ($feed['id'] ?? 'feed')) . '_' . substr(sha1(json_encode([$feed, $extra])), 0, 12) . '.json'; } function simon_cache_get(string $key, int $ttl): ?array { $path = SIMON_CACHE_DIR . '/' . $key; if (!is_file($path)) return null; if ($ttl > 0 && (time() - filemtime($path)) > $ttl) return null; $data = simon_read_json_file($path, null); return is_array($data) ? $data : null; } function simon_cache_set(string $key, array $data): void { @simon_write_json_file(SIMON_CACHE_DIR . '/' . $key, $data); } function simon_http_get(string $url, array $headers = [], int $timeout = 12): array { $parts = parse_url($url); $scheme = strtolower((string)($parts['scheme'] ?? '')); $host = strtolower((string)($parts['host'] ?? '')); if (!in_array($scheme, ['http', 'https'], true) || $host === '') { return ['ok'=>false, 'status'=>0, 'body'=>'', 'error'=>'Only public HTTP/HTTPS feed URLs are allowed.', 'content_type'=>'']; } if ($host === 'localhost' || str_ends_with($host, '.local')) { return ['ok'=>false, 'status'=>0, 'body'=>'', 'error'=>'Local network feed URLs are not allowed.', 'content_type'=>'']; } $ch = curl_init($url); $defaultHeaders = [ 'Accept: application/json, application/rss+xml, application/xml, text/xml, */*', 'User-Agent: SIMON-FeedEngine/2.0 (+https://www.gaylordsinclair.com/)' ]; curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 4, CURLOPT_TIMEOUT => $timeout, CURLOPT_CONNECTTIMEOUT => 6, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_HTTPHEADER => array_merge($defaultHeaders, $headers), CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS, CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS, ]); $body = curl_exec($ch); $err = curl_error($ch); $code = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE); $ctype = (string)curl_getinfo($ch, CURLINFO_CONTENT_TYPE); if ($body === false) $body = ''; return ['ok' => $code >= 200 && $code < 300, 'status' => $code, 'body' => $body, 'error' => $err, 'content_type' => $ctype]; } function simon_get_path(array $data, string $path, mixed $default = null): mixed { $cur = $data; foreach (explode('.', $path) as $part) { if (is_array($cur) && array_key_exists($part, $cur)) $cur = $cur[$part]; else return $default; } return $cur; } function simon_clean_text(mixed $value, int $max = 600): string { $text = trim(strip_tags(html_entity_decode((string)$value, ENT_QUOTES | ENT_HTML5, 'UTF-8'))); $text = preg_replace('/\s+/', ' ', $text) ?? $text; if (mb_strlen($text) > $max) $text = mb_substr($text, 0, $max - 1) . '…'; return $text; } function simon_event(array $feed, array $raw): array { $title = simon_clean_text($raw['title'] ?? $raw['name'] ?? $raw['headline'] ?? 'Untitled'); $summary = simon_clean_text($raw['summary'] ?? $raw['description'] ?? $raw['text'] ?? $raw['body'] ?? '', 900); $url = trim((string)($raw['url'] ?? $raw['link'] ?? $raw['href'] ?? '')); if ($url !== '' && preg_match('/^www\./i', $url)) $url = 'https://' . $url; $published = (string)($raw['published'] ?? $raw['publishedAt'] ?? $raw['date'] ?? $raw['time'] ?? gmdate('c')); $timestamp = strtotime($published) ?: time(); $hash = sha1(($feed['id'] ?? '') . '|' . $title . '|' . $url . '|' . $timestamp); $priority = (float)($feed['priority'] ?? 50); $ageHours = max(0, (time() - $timestamp) / 3600); $fresh = max(0.15, 1 - min(1, $ageHours / 72)); $score = round(min(100, ($priority * 0.65) + ($fresh * 35)), 2); return [ 'id' => $hash, 'source_id' => (string)($feed['id'] ?? 'unknown'), 'source' => (string)($feed['name'] ?? ($feed['id'] ?? 'Unknown')), 'category' => (string)($feed['category'] ?? 'general'), 'engine' => (string)($feed['engine'] ?? 'unknown'), 'title' => $title, 'summary' => $summary, 'url' => $url, 'image' => (string)($raw['image'] ?? ''), 'published' => gmdate('c', $timestamp), 'score' => $score, 'color' => (string)($feed['color'] ?? '#4be3ff'), 'raw' => $raw, ]; } function simon_load_provider(string $engine): ?callable { $engine = preg_replace('/[^a-zA-Z0-9_\-]/', '', $engine); $path = SIMON_ROOT . '/providers/' . $engine . '.php'; if (!is_file($path)) return null; require_once $path; $fn = 'simon_provider_' . str_replace('-', '_', $engine); return function_exists($fn) ? $fn : null; } function simon_dedupe_events(array $events): array { $seen = []; $out = []; foreach ($events as $event) { $key = strtolower(trim((string)($event['url'] ?: $event['title']))); $key = preg_replace('/[?#].*$/', '', $key) ?: (string)$event['id']; if (isset($seen[$key])) continue; $seen[$key] = true; $out[] = $event; } return $out; }
Save file
Quick jump
open a path
Open