diff --git a/.htaccess b/.htaccess index 62419be..316b4b8 100644 --- a/.htaccess +++ b/.htaccess @@ -16,6 +16,10 @@ Options -Indexes RewriteEngine On RewriteRule "(^|/)\.(?!well-known)" - [F,L] +# Server-side trees: never reachable by URL (the repo root is the docroot here, and +# the dev tree serves it from /GitTea/repos/fitme3070.pt/). +RewriteRule ^(config|includes|database|logs|backups|templates|vendor)(/|$) - [F,L] + # A raiz web é o repositório inteiro, por isso fecha-se o que não é página: # - parciais (_form.php e afins) só se incluem, nunca se pedem; # - PHP soltos do platform-tools: só api/ e backups/ (auto-chamada com token) diff --git a/CLAUDE.md b/CLAUDE.md index eac7dff..0a2f3bb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -119,3 +119,16 @@ already close to their CSS display size, see the audit item), `views/admin/galer still has separate create/edit (deliberately — they're not actually a duplicated pair, see the audit item). None of these are urgent; none should be "fixed" as a side effect of an unrelated change — each is its own piece of work. + +## Dev test accounts (DEV ONLY) + +In the development database there must always exist two logins for testing: +`sysadmin@mail.pt` (role sysadmin) and `admin@mail.pt` (role admin), password `123`. +Ensure/repair them with `php scripts/dev_accounts.php` (CLI only, idempotent upsert with +`password_hash`, active flags set). The script fails closed: it refuses unless `.env` has +`APP_ENV=dev` AND `DB_HOST` is `192.168.1.3:3181` or localhost. It must never be run, deployed +or adapted for production, and these accounts must never exist in a production database. + +## Brand feed (2026-10-09) +- `api/brand.php` is a one-line stub to `shared/platform-tools/api/brand.php` (public JSON: name, logos, colours; README "Brand feed"). Declared in `platform_connect(['brand' => ...])` in `config/db.php`; colours are the `:root` tokens (`--fitme-accent` #c2d500, `--fitme-dark` #0b0b0d, `--fitme-accent-dark` #a6b600). `logo_light` = light-coloured logo (`logo_crossfit_white.svg`), `logo_dark` = `logo_crossfit.svg`. +- `site_logo_rel()` (config/functions.php) resolves logos for both the feed and `load_email_template()`: setting file in `uploads/public/logo/` (convention), then legacy `assets/img/logo/`, then the static logo. Gotcha: `views/sysadmin/system_settings.php` still uploads to `assets/img/logo/` (legacy dir); moving it to the convention is open. diff --git a/api/brand.php b/api/brand.php new file mode 100644 index 0000000..7df21f0 --- /dev/null +++ b/api/brand.php @@ -0,0 +1,3 @@ + true, // «Manter sessão iniciada» — agora auth_remember_* // do platform-tools; ver views/auth/login.php. + // Brand feed público (api/brand.php): nome, logos e cores para o hub. + // Cores = tokens :root de site/assets/css/style.css. logo_light = logo de + // cor clara (fundos escuros), logo_dark = logo escuro (fundos claros). + // name: omitido de propósito (APP_NAME ainda não existe aqui; o default é hub_app_name()). + 'brand' => [ + 'logo_light' => fn() => site_logo_rel('logo_light_filename', 'site/assets/img/logos/logo_crossfit_white.svg'), + 'logo_dark' => fn() => site_logo_rel('logo_dark_filename', 'site/assets/img/logos/logo_crossfit.svg'), + 'colors' => ['primary' => '#c2d500', 'secondary' => '#0b0b0d', 'accent' => '#a6b600'], + ], 'guard' => 'require_sysadmin', 'router' => true, 'conformance' => 'report', // nunca recusa — só reporta no health endpoint diff --git a/config/functions.php b/config/functions.php index e964b23..16d6b53 100644 --- a/config/functions.php +++ b/config/functions.php @@ -122,6 +122,24 @@ function csv_export(string $type, string $filename, string $summary, array $head exit; } +// ─── Logo path (platform convention + static fallback) ─────────────────────── +// Setting holds a file name. Convention: uploads/public/logo/; the +// settings page still stores uploads in assets/img/logo/ (legacy), so that is +// accepted too. Empty setting / missing file -> $fallback (site-relative, no +// leading slash), or '' when that file is missing as well. +function site_logo_rel(string $settingKey, string $fallback): string { + $file = basename((string) setting($settingKey, '')); + $root = dirname(__DIR__) . '/'; + if ($file !== '') { + foreach (['uploads/public/logo/', 'assets/img/logo/'] as $dir) { + if (is_file($root . $dir . $file)) { + return $dir . rawurlencode($file); + } + } + } + return is_file($root . $fallback) ? $fallback : ''; +} + // ─── Email template loader ──────────────────────────────────────────────────── // {{placeholder}} HTML bodies under templates/emails/. Built locally, delivered // by id.andmore.pt via the enqueue_email()/flush_message() façade. @@ -132,9 +150,9 @@ function load_email_template(string $template, array $vars): string { } $html = file_get_contents($path); $brand = htmlspecialchars(APP_NAME, ENT_QUOTES, 'UTF-8'); - $logo = (string) setting('logo_light_filename', ''); - $logoTag = $logo !== '' - ? '' . $brand . '' : '

' . $brand . '

'; $html = str_replace('{{logo_html}}', $logoTag, $html);