# Architecture Overview

The app is plain PHP under `src/`. Routing is file-based and most page logic is procedural.
## Scope and Stack
- Main application is plain PHP under `src/` (no large framework).
- Models are split by source type in `src/model/`:
  - `base/` and `cms/` shared layers.
  - runtime-specific layers: `emulator*`, `stream*`, `trinity-stream`.
- Pages are file-based in `src/pages/{public|private|admin}/` with route folders and paired files:
  - `<route>.controller.php` (except if view is plain content)
  - `<route>.view.php`
  - `<route>.view.js` (optional)
- Core bootstrap/helpers are in `src/settings/helpers.php` and `src/core/model.class.php`.
- Tests are PHPUnit tests in `tests/auto/models/`.

## Environment and Setup
- Use PHP 8.3+ compatible tooling.
- Install dependencies:
  - `composer install`
- Ensure runtime settings exist:
  - create `src/settings/config.php` (typically from `src/settings/config.developer.php` or `src/settings/config.example.php`).
- DB-backed tests require valid game/CMS DB connection values in settings.


## Entry points

- `src/index.php` - web requests
- `src/cli.php` - CLI tasks

## Request Lifecycle (Web)

1. **index.php** starts output buffering and session
2. Loads **config.php** (secrets, database, constants)
3. Loads **helpers.php** which:
   - Defines path constants (`CORE`, `MODELS_BASE`, etc.)
   - Connects to game database (`Model::connectGame()`)
   - Connects to CMS database (`Model::connectCMS()`)
   - Loads source capabilities (`capabilities.php`)
   - Auto-loads `account` and `entities` models
   - Loads template components
   - Calls `loadUserAccount()` to set session constants
   - Loads translations
4. Loads **routes.php** (defines `$mainPages`, `$userPanelPages`, `$adminPanelPages`)
5. Resolves the page based on `$_REQUEST['page']`:
   - Checks `DISABLED_SECTIONS`
   - Checks auth requirements (`ACCOUNT_NAME`, `GM_STATE`)
   - Sets path to `public/`, `private/`, or `admin/`
6. Includes **controller** (`{page}.controller.php`) if exists
7. Includes **view** (`{page}.view.php`)
8. Template header/footer wrap the content

Every theme header must include the admin bar immediately after opening `<body>`, using `isAdminAccount()` before requiring `CORE.'admin-bar/admin-bar.php'` and calling `renderAdminBar()`. The module also performs the GM check; the bootstrap must not call it globally.

## Page layout

Pages live under `src/pages/{public|private|admin}/<route>/`.

- `<route>.controller.php` - request handling, guards, model calls, view variables
- `<route>.view.php` - HTML output
- `<route>.view.js` - optional page script

Controllers run in global scope. Views read variables set by the controller.


## Routing

Routes are defined in `src/pages/routes.php`:

```php
$mainPages = ['index' => [...], 'login' => [...], 'ranking' => [...], ...];
$userPanelPages = ['account-panel' => [...], 'chpass' => [...], ...];
$adminPanelPages = ['manual-sell' => [...], 'welcome-reward' => [...], ...];
```

The routing logic in `index.php`:
1. If `page` is empty → `index`
2. If page in `DISABLED_SECTIONS` → `404`
3. If page in `$userPanelPages`:
   - No `ACCOUNT_NAME` → redirect to `login`
   - Has account → use `private/` path
4. If page in `$adminPanelPages`:
   - No `ACCOUNT_NAME` or not GM → `404`
   - Has admin → use `admin/` path
5. If page in `$mainPages` → use `public/` path
6. Otherwise → `404`

Pretty URLs are rewritten in `src/.htaccess`. The language-only rewrite accepts one to three lowercase characters, so `/mfa` requires an explicit rewrite before it; otherwise it is treated as a language root (`lang=mfa&page=index`). Localized MFA URLs such as `/en/mfa` use the generic localized-page rule. Explicit short-route rewrites must use `QSA` when their query parameters are significant.

### Redirect URLs

`redirectTo()` writes the URL directly to the `Location` header. For language-prefixed routes, use an absolute URL built as `WEB_URL.LANGUAGE.'/route'`; do not pass `LANGUAGE.'/route'` alone, because a relative redirect from `/es/...` can become `/es/es/...`.

## Controllers

Controllers are **PHP files** that run in the global scope of `index.php`. They:
- Read from `$_POST`, `$_GET`, `$_SESSION`
- Call model static methods
- Set variables for the view
- May define functions used by the view

### Controller Example

```php
<?php
// src/pages/public/login/login.controller.php

if (ACCOUNT_NAME) {
  redirectTo('account-panel');
}

$login_errors = '';

if (isset($_POST['login'])) {
  $username = $_POST['Username'];
  $password = $_POST['Password'];

  if (empty($username)) {
    $login_errors = __('Username is required');
  } else {
    $account = AccountModel::getWithLogin($username, $password);
    if ($account) {
      $_SESSION['acc'] = $account['Username'];
      redirectTo('account-panel');
    } else {
      $login_errors = __('Wrong credentials');
    }
  }
}
```

### Error Handling

Accumulate errors as strings:

```php
$errors = '';

if (empty($username)) {
  $errors .= __('Username is required').'<br>';
}
if (empty($password)) {
  $errors .= __('Password is required').'<br>';
}
```

Use guard clauses with early returns:

```php
public static function getByUID($uid) {
  if (!$uid) {
    return null;
  }
  // ... rest of method
}
```

Try/catch for exceptions:

```php
try {
  $result = $pdo->query($sql);
} catch (PDOException $e) {
  echo 'Database error: ' . $e->getMessage();
  return null;
}
```

### Key Controller Patterns

- **Auth check**: `if (!ACCOUNT_NAME) redirectTo('login');`
- **Admin check**: `if (ACCOUNT_DATA['State'] < GM_STATE) { /* 404 */ }`
- **Error accumulation**: `$errors .= __('Message').'<br>';`
- **View variables**: Just set `$variableName = value;` (view reads from global scope)

## Views

Views are PHP files that output HTML. They:
- Read variables set by the controller (global scope)
- Call template helpers (`ComponentsTemplate::method()`)
- May call model methods directly for data loading
- Views need template components, check API: `Docs/api/views-components.md`
- Views might need forms, check API: `Docs/api/form-components.md`

### View Example

```php
<?php
// src/pages/public/login/login.view.php
ComponentsTemplate::StartPage('page-login');
ComponentsTemplate::PageTitle(__('Log In'));
ComponentsTemplate::StartBlock('login');

FormComponents::startForm('', 'post');
FormComponents::showSuccessError($Done, $login_errors);

FormComponents::showInputText('Username', $_POST['Username'] ?? '', 'Username', '', true, ['maxlength' => 10], '');
FormComponents::showInputPassword('Password', $_POST['Password'] ?? '', 'Password', '', true, ['maxlength' => 20], '');
FormComponents::showTextRaw(__('Start playing now').'<br /><a href="'.LANGUAGE.'/register">'.__('Sign Up!').'</a>');
FormComponents::showTextRaw('<a href="'.LANGUAGE.'/forgotpassword">'.__('Forgot Password').'</a>');

FormComponents::showButton('login', 'submit', 'Log In');

FormComponents::endForm();

ComponentsTemplate::EndBlock();
ComponentsTemplate::EndPage();
```

### JavaScript

#### Frontend (in pages)

Plain jQuery, no modules:

```js
$(document).ready(function () {
  $('#myForm').on('submit', function(e) {
    e.preventDefault();
    // ...
  });
});
```

Third-party editor assets are copied from the root npm dependencies with `npm run copy-assets`. The copy script stores SunEditor runtime files under `src/core/editors/sun-editor/assets/` and FLMngr runtime files under `src/core/file-manager/flmngr/assets/`. The SunEditor wrapper is `src/core/editors/sun-editor/suneditor-wrapper.js`. FLMngr's PHP protocol backend is provided by the Composer dependency `edsdk/flmngr-server-php` and is routed through the authenticated content-management AJAX controller when enabled in `config-admin.php`.

### CMS Content Flow

The admin route `admin/content-management` uses the content model to manage a soft-deletable category/content tree and language-specific translations. The controller handles editor saves and AJAX tree actions, prepares view data, and reuses the model's cached tree; the views render the supplied tree and SunEditor without querying the full tree themselves. Page scripts handle drag-and-drop ordering, local editor recovery, and navigation protection.

Public content is resolved by `src/core/modules/Content/Content.php`, which delegates node and translation lookup to `ContentModel`. Publication checks include translation draft state, future publish dates, deleted nodes, and unpublished ancestors. Read [13-content-management.md](13-content-management.md) for the data model and workflow.


## Models

Models live under `src/model/` and are selected by `SOURCE_TYPE`. Read `03-models.md` before changing shared or source-specific model behavior.

Check [03-models.md](03-models.md) if you need more information about models.

## Core Systems

### Database Connections

`Model` class manages two PDO connections:
- `$conn` - Game database
- `$connCMS` - CMS database (can be same as game)

### Template System

Templates extend base classes:
- `ComponentsTemplateBase` → `ComponentsTemplate`
- `FormComponentsBase` → `FormComponents`

Methods output HTML for pages, blocks, buttons, forms.

### Translation System

`__($string)` function translates strings:
- Looks up `$locales[LANGUAGE][$string]`
- Falls back to original string if not found
- Locale files are `src/core/translations/en.php`, `es.php`, and `pt.php`.
- `src/core/translations/br.php` inherits all Portuguese entries and normally needs no separate entry.
- Add every new customer-visible string to `es.php`, and `pt.php` using the exact string passed to `__()` as the array key. Translate `en.php` only if you used a key for the text instead a english sentence.
- Translation values use HTML entities where needed because they are rendered directly in HTML.
- Themes can provide their own translations via `src/templates/{theme}/translations/{lang}.php`, which are automatically merged into `$locales[LANGUAGE]` by `helpers.php`.

### Cache System

`Cache` class provides file-based caching:
- `Cache::load()` - Include cached file if fresh
- `Cache::buildFromString()` - Write cache file

## Constants

Key constants defined in `config.php` or `helpers.php`:

- `WEB_URL` - Site URL for redirects/links
- `SOURCE_TYPE` - Current game source (emulator, stream, etc.)
- `ACCOUNT_NAME` - logged-in username or falsey
- `ACCOUNT_DATA` - logged-in account row
- `GM_STATE` - admin threshold
- `SOURCE_CAPABILITIES` - feature flags for the current source

## Sessions

- `$_SESSION['acc']` - Username of logged-in user
- `$_SESSION['lang']` - Selected language
- `$_SESSION['magic']` - Captcha value
- `$_SESSION['token']` - CSRF token
- `$_SESSION['voteTotalCheck']` - Vote verification state
