# Technology Stack

**Analysis Date:** 2026-03-12

---

## Languages

**Primary:**
- PHP 8.4 — all backend logic, routing, controllers, repositories, CLI scripts
- SQL — raw migration files, all database queries via PDO prepared statements

**Secondary:**
- SCSS — compiled to CSS for frontend styling (`resources/scss/`)
- JavaScript — custom jQuery-based UI alert/confirm module (`resources/modules/jquery-alerts/`)

---

## Runtime & Server

**PHP:**
- Version: `^8.4` (strict, declared via `declare(strict_types=1)` in every file)
- Extensions required: `pdo_mysql`, `openssl`, `curl`, `hash`, `mbstring`, `session`
- Dev server: `php -S localhost:8000 -t public public/index.php` (via `composer serve`)
- Production: any standard PHP-FPM + Apache/Nginx setup targeting `public/` as document root

**Entry point:**
- `public/index.php` — bootstraps the application
- `index.php` (root) — likely a redirect or secondary entry

**CLI scripts (in `bin/`):**
- `bin/migrate.php` — runs database migrations (`composer migrate`)
- `bin/cron.php` — executes scheduled cron jobs (`composer cron`)

---

## Framework

**No third-party framework.** The application uses a fully custom, hand-rolled framework located in `src/Core/`:

| Component | Class | File |
|---|---|---|
| Application container | `App\Core\Application` | `src/Core/Application.php` |
| HTTP Router | `App\Core\Routing\Router` | `src/Core/Routing/Router.php` |
| HTTP Request | `App\Core\Http\Request` | `src/Core/Http/Request.php` |
| HTTP Response | `App\Core\Http\Response` | `src/Core/Http/Response.php` |
| Template engine | `App\Core\View\Template` | `src/Core/View/Template.php` |
| Database factory | `App\Core\Database\ConnectionFactory` | `src/Core/Database/ConnectionFactory.php` |
| Migrator | `App\Core\Database\Migrator` | `src/Core/Database/Migrator.php` |
| Session | `App\Core\Support\Session` | `src/Core/Support/Session.php` |
| Logger | `App\Core\Support\Logger` | `src/Core/Support/Logger.php` |
| CSRF protection | `App\Core\Security\Csrf` | `src/Core/Security/Csrf.php` |
| Translator (i18n) | `App\Core\I18n\Translator` | `src/Core/I18n/Translator.php` |
| Env helper | `App\Core\Support\Env` | `src/Core/Support/Env.php` |

**Routing style:** `GET`/`POST` registration with path-param support (`{id}`) and per-route middleware pipeline. Routes defined in `routes/web.php`.

**Template engine:** PHP native `require` with `ob_start()`/`ob_get_clean()`. Views live in `resources/views/`. Layout support via a second render pass. XSS helper `$e()` exposed to every view via `htmlspecialchars`. Translation helper `$t()` also injected.

**i18n:** Single-locale PHP array file. Currently Polish only: `resources/lang/pl.php`.

---

## Database

**Engine:** MySQL (only supported driver — enforced with `RuntimeException` in `ConnectionFactory`)
**Default port:** 3306
**Charset:** `utf8mb4`
**Client:** PHP PDO with `ERRMODE_EXCEPTION`, `FETCH_ASSOC`, `EMULATE_PREPARES = false`
**ORM:** None — raw PDO prepared statements everywhere

**Migrations:**
- Plain SQL files in `database/migrations/`, named `YYYYMMDD_NNNNNN_description.sql`
- Run via `bin/migrate.php` / `composer migrate`
- 46 migrations present (as of analysis date), dating from 2026-02-21

**Config file:** `config/database.php`

---

## Package Manager & Build Tools

**PHP dependencies (Composer):**
- `composer.json` — PSR-4 autoload: `App\` → `src/`, `Tests\` → `tests/`
- No runtime Composer packages — zero third-party PHP libraries
- Dev only: `phpunit/phpunit ^11.5`
- Lockfile: `composer.lock` (present)

**Node / Frontend (npm):**
- `package.json` — `devDependencies` only
- `sass ^1.97.3` — SCSS compiler (Dart Sass CLI)
- Lockfile: `package-lock.json` (present)

**Build commands (npm scripts):**

```bash
npm run build:css        # Compile app.scss + login.scss → public/assets/css/
npm run build:modules    # Compile jquery-alerts.scss + copy jquery-alerts.js → public/assets/
npm run build:assets     # Full build (build:css + build:modules)
npm run watch:css        # Watch mode for SCSS development
```

**Output paths:**
- `public/assets/css/app.css` — main application styles
- `public/assets/css/login.css` — login page styles
- `public/assets/css/modules/jquery-alerts.css` — alert/confirm modal styles
- `public/assets/js/modules/jquery-alerts.js` — alert/confirm module JS

**SCSS sources:**
- `resources/scss/app.scss` — main stylesheet
- `resources/scss/login.scss` — login page stylesheet
- `resources/scss/shared/_ui-components.scss` — shared component partials
- `resources/modules/jquery-alerts/jquery-alerts.scss` — module source

---

## Testing

**Framework:** PHPUnit `^11.5`
**Config:** `phpunit.xml`
**Run command:** `composer test` → `vendor/bin/phpunit -c phpunit.xml --testdox`
**Test root:** `tests/` (`Tests\` namespace)
**Bootstrap:** `tests/bootstrap.php`
**Cache dir:** `storage/cache/phpunit`
**Coverage source:** `src/`

---

## Security

**CSRF:** Session-based token via `App\Core\Security\Csrf` (`src/Core/Security/Csrf.php`) — `hash_equals()` comparison

**Credential encryption (integration API keys):** AES-256-CBC + HMAC-SHA256 via `App\Modules\Settings\IntegrationSecretCipher` (`src/Modules/Settings/IntegrationSecretCipher.php`)
- Encryption key and HMAC key are both derived from `INTEGRATIONS_SECRET` env var using `hash('sha256', 'enc|...')` / `hash('sha256', 'auth|...')`
- Encrypted values stored in DB as `v1:<base64>` format

**XSS:** `htmlspecialchars()` via `$e()` helper injected into every view by `Template::renderFile()`

**SQL injection:** PDO prepared statements enforced throughout all repositories

---

## External Integrations

### Allegro (Polish marketplace)

- **OAuth 2.0 client:** `App\Modules\Settings\AllegroOAuthClient` (`src/Modules/Settings/AllegroOAuthClient.php`)
  - Authorization code + refresh token flow
  - Environments: `production` (`https://allegro.pl`) and `sandbox` (`https://allegro.pl.allegrosandbox.pl`)
  - Scopes: `allegro:api:orders:read`, `allegro:api:sale:offers:read`, `allegro:api:shipments:read`, `allegro:api:shipments:write`
- **REST API client:** `App\Modules\Settings\AllegroApiClient` (`src/Modules/Settings/AllegroApiClient.php`)
  - Endpoints used: checkout forms (order list/detail), shipments, delivery services, product offers, shipment labels (binary PDF)
  - Accept header: `application/vnd.allegro.public.v1+json`
  - HTTP transport: native PHP `curl`
- **Sync services:**
  - `AllegroOrdersSyncService` — order import from Allegro
  - `AllegroStatusSyncService` — order status synchronization
  - `AllegroOrderImportService` — individual order import
- **Cron handlers:** `AllegroOrdersImportHandler`, `AllegroStatusSyncHandler`, `AllegroTokenRefreshHandler`
- **Credentials stored:** in DB `integrations` table, API key encrypted with `IntegrationSecretCipher`

### shopPRO (Polish e-commerce platform)

- **REST API client:** `App\Modules\Settings\ShopproApiClient` (`src/Modules/Settings/ShopproApiClient.php`)
  - Endpoint pattern: `{baseUrl}/api.php?endpoint=orders&action=list`
  - Auth: `X-Api-Key` HTTP header
  - Supports: order list (paginated), order by ID, product by ID
- **Sync services:**
  - `ShopproOrdersSyncService` — order import from shopPRO
  - `ShopproStatusSyncService` — order status sync
  - `ShopproPaymentStatusSyncService` — payment status sync
- **Cron handlers:** `ShopproOrdersImportHandler`, `ShopproStatusSyncHandler`, `ShopproPaymentStatusSyncHandler`

### Apaczka (Polish shipping aggregator)

- **REST API client:** `App\Modules\Settings\ApaczkaApiClient` (`src/Modules/Settings/ApaczkaApiClient.php`)
  - Base URL: `https://www.apaczka.pl/api/v2`
  - Auth: HMAC-SHA256 signature (`app_id:route:requestJson:expires` + `appSecret`)
  - Signature variant fallback: tries multiple formats to handle Apaczka API quirks
  - Endpoints: `service_structure`, `order_send`, `order/{id}`, `waybill/{id}`, `points/{type}`
- **Shipment provider:** `App\Modules\Shipments\ApaczkaShipmentService` (`src/Modules/Shipments/ApaczkaShipmentService.php`)
- **Settings repo:** `ApaczkaIntegrationRepository`, `ApaczkaIntegrationController`

### InPost ShipX

- **API base URL:** `https://api-shipx-pl.easypack24.net`
- **Auth:** Bearer token (stored encrypted in DB)
- **Settings repo:** `App\Modules\Settings\InpostIntegrationRepository` (`src/Modules/Settings/InpostIntegrationRepository.php`)
- **Shipment provider:** `App\Modules\Shipments\AllegroShipmentService` also covers InPost services through Allegro's shipment management API
- **Config stored:** `inpost_integration_settings` DB table
- **Features:** sandbox/production toggle, dispatch method, locker size, dimensions, label format (PDF/ZPL/EPL), weekend delivery, multi-parcel

### GS1 (product barcode data)

- Referenced in `bin/test_gs1_api.php` and `bin/fix_gs1_brand.php`
- Settings stored in `app_settings` table (GS1 API key, GLN, etc.)

---

## Shipment Provider Abstraction

Interface: `App\Modules\Shipments\ShipmentProviderInterface` (`src/Modules/Shipments/ShipmentProviderInterface.php`)

Methods:
- `code(): string`
- `getDeliveryServices(): array`
- `createShipment(int $orderId, array $formData): array`
- `checkCreationStatus(int $packageId): array`
- `downloadLabel(int $packageId, string $storagePath): array`

Registry: `App\Modules\Shipments\ShipmentProviderRegistry` (`src/Modules/Shipments/ShipmentProviderRegistry.php`)

Implementations:
- `AllegroShipmentService` — creates shipments via Allegro shipment management API
- `ApaczkaShipmentService` — creates shipments via Apaczka API

---

## Cron System

**Runner:** `App\Modules\Cron\CronRunner` (`src/Modules/Cron/CronRunner.php`)
**Repository:** `App\Modules\Cron\CronRepository` (`src/Modules/Cron/CronRepository.php`)
**CLI entry:** `bin/cron.php`
**Web trigger (optional):** Cron can run on each web request (throttled, DB-locked) when `CRON_RUN_ON_WEB=true`

**Registered jobs:**

| Job key | Handler | Purpose |
|---|---|---|
| `allegro_token_refresh` | `AllegroTokenRefreshHandler` | Refresh Allegro OAuth tokens |
| `allegro_orders_import` | `AllegroOrdersImportHandler` | Import orders from Allegro |
| `allegro_status_sync` | `AllegroStatusSyncHandler` | Sync order statuses to Allegro |
| `shoppro_orders_import` | `ShopproOrdersImportHandler` | Import orders from shopPRO |
| `shoppro_order_status_sync` | `ShopproStatusSyncHandler` | Sync order statuses to shopPRO |
| `shoppro_payment_status_sync` | `ShopproPaymentStatusSyncHandler` | Sync payment statuses from shopPRO |

---

## Environment Configuration

**File:** `.env` (not committed) — see `.env.example` for all required vars

| Variable | Purpose | Default |
|---|---|---|
| `APP_NAME` | Application display name | `orderPRO` |
| `APP_ENV` | Environment (`local`/`production`) | `local` |
| `APP_DEBUG` | Debug mode (display errors) | `true` |
| `APP_URL` | Application base URL | `http://localhost:8000` |
| `SESSION_NAME` | PHP session cookie name | `orderpro_session` |
| `INTEGRATIONS_SECRET` | Master key for encrypting API credentials | *(required, no default)* |
| `CRON_RUN_ON_WEB` | Enable web-triggered cron | `false` |
| `CRON_WEB_LIMIT` | Max cron jobs per web request | `5` |
| `DB_CONNECTION` | DB driver (only `mysql` supported) | `mysql` |
| `DB_HOST` | MySQL host (runtime) | `127.0.0.1` |
| `DB_HOST_REMOTE` | MySQL host for agent/migration use only — **not for runtime** | *(empty)* |
| `DB_PORT` | MySQL port | `3306` |
| `DB_DATABASE` | Database name | `orderpro` |
| `DB_USERNAME` | Database user | `root` |
| `DB_PASSWORD` | Database password | *(empty)* |
| `DB_CHARSET` | Connection charset | `utf8mb4` |

**Config files:** `config/app.php`, `config/database.php`

---

## Storage Directories (auto-created at boot)

| Path | Purpose |
|---|---|
| `storage/logs/` | Application log file (`app.log`) |
| `storage/sessions/` | PHP session files |
| `storage/cache/` | PHPUnit cache |
| `storage/tmp/` | Temporary files (e.g. label downloads) |

---

## Platform Requirements (Development)

- PHP 8.4+ with extensions: `pdo_mysql`, `openssl`, `curl`, `mbstring`
- MySQL 5.7+ or MySQL 8.x
- Node.js + npm (for SCSS compilation only)
- XAMPP on Windows: `C:\xampp\php\php.exe` (add to `PATH`)

---

*Stack analysis: 2026-03-12*
