diff --git a/cms/bootstrap.php b/cms/bootstrap.php new file mode 100644 index 0000000..eeaa439 --- /dev/null +++ b/cms/bootstrap.php @@ -0,0 +1,16 @@ + label[for="filename"] { margin: 4px 0 6px; font-weight: 600; } +#newPageForm input[type="text"] { width: 100%; box-sizing: border-box; padding: 9px; margin-bottom: 16px; border: 1px solid #c9cfd8; border-radius: 6px; } +#newPageForm button[type="submit"] { align-self: flex-end; padding: 9px 22px; border: 0; border-radius: 6px; background: #3b6ea5; color: #fff; font-weight: 600; cursor: pointer; } +#newPageForm button[type="submit"]:hover { background: #2f5a8a; } + +/* Primary submit buttons inside dialogue forms. */ +.cms-dialog form button[type="submit"] { padding: 9px 18px; border: 0; border-radius: 6px; background: #3b6ea5; color: #fff; font-weight: 600; cursor: pointer; } +.cms-dialog form button[type="submit"]:hover { background: #2f5a8a; } +#sharedList, #menuList { margin-bottom: 16px; } diff --git a/cms/image_upload.php b/cms/image_upload.php index 4d8a669..a88e556 100644 --- a/cms/image_upload.php +++ b/cms/image_upload.php @@ -7,16 +7,12 @@ */ // Load credentials, role assignments, and shared application settings. -require_once "config.php"; - -// Resolve project classes without requiring Composer for this deliberately lightweight CMS. -spl_autoload_register(function ($class) { - $classPath = str_replace('\\', DIRECTORY_SEPARATOR, $class); - require_once "./src/{$classPath}.php"; -}); +require_once __DIR__ . '/bootstrap.php'; +use NeoCMS\Activity; use NeoCMS\Authentication; use NeoCMS\FileStore; +use NeoCMS\Logger; use NeoCMS\SecurityHeaders; SecurityHeaders::json(isset($config['security']['cookieSecure']) ? (bool) $config['security']['cookieSecure'] : null); @@ -164,7 +160,7 @@ if (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) { chmod($target_file, 0644); // Seed media metadata from the original basename; editors can improve the alt text later. - $location = '/uploads/' . $filename; + $location = $config['basePath'] . '/uploads/' . $filename; $metadata = $store->read('media'); $originalBase = pathinfo((string) $file['name'], PATHINFO_FILENAME); $altText = substr(preg_replace('/[\x00-\x1F\x7F]/u', '', $originalBase) ?? '', 0, 200); @@ -181,6 +177,13 @@ if (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) { } flock($uploadLock, LOCK_UN); fclose($uploadLock); + // The file is saved by now, so a failure to write the audit entry must not fail the upload. + try { + (new Activity($store, new Logger($config['audit'] ?? true, $config['security'] ?? []))) + ->record($authentication->getLoggedInUser(), 'Uploaded image', $filename . ' (' . max(1, (int) round(((int) $file['size']) / 1024)) . ' KB)'); + } catch (\Throwable $exception) { + // Deliberately ignored. + } echo json_encode(['location' => $location]); } else { flock($uploadLock, LOCK_UN); diff --git a/cms/index.php b/cms/index.php index c437f03..5c3157e 100644 --- a/cms/index.php +++ b/cms/index.php @@ -2,13 +2,7 @@ /** Server-rendered shell for the authenticated NeoCMS administration application. */ // Load configuration before initialising sessions and role-aware controls. -require_once "config.php"; - -// Resolve project classes without requiring Composer. -spl_autoload_register(function ($class) { - $classPath = str_replace('\\', DIRECTORY_SEPARATOR, $class); - require_once "./src/{$classPath}.php"; -}); +require_once __DIR__ . '/bootstrap.php'; use NeoCMS\Authentication; use NeoCMS\SecurityHeaders; @@ -24,7 +18,7 @@ if (!is_string($editableClass) || !preg_match('/^[a-zA-Z_][a-zA-Z0-9_-]*$/', $ed // Keep the administration shell private by redirecting anonymous requests to login. if (!$authentication->isLoggedIn()) { - header("Location: /cms/login/"); + header("Location: " . $config['basePath'] . "/cms/login/"); exit; } SecurityHeaders::html(true, isset($config['security']['cookieSecure']) ? (bool) $config['security']['cookieSecure'] : null); @@ -35,16 +29,21 @@ SecurityHeaders::html(true, isset($config['security']['cookieSecure']) ? (bool)