Compare commits

...

40 Commits

Author SHA1 Message Date
root 1db80e76fe chore: clean up repo structure and polish docs
- Rewrite .gitignore in English with proper patterns for build artifacts, storage files, and IDE files
- Remove tracked build assets (hash-based) and storage files from git
- Update LICENSE copyright to 2026 Remco (Epicnabbo)
- Fix clone URLs in README (remove placeholder gitea-server)
- Sync docs/INSTALL.md with README installation guide
2026-06-06 20:12:08 +02:00
root 51e3876ed9 fix: correct file permissions for www-data access 2026-06-06 19:40:48 +02:00
root 483342602f chore: update composer and npm dependencies to latest versions
- composer.json: 19 package version bumps (laravel/framework ^13.11,
  filament/filament ^5.6, pestphp/pest ^4.7, phpunit ^12.5, etc.)
- package.json: 22 package version bumps (eslint ^10, flowbite 4.0,
  vite ^8.0, tailwindcss 4.3, sass 1.100, etc.)
2026-06-06 19:24:34 +02:00
root ccbd200464 feat: auto-merge all Nitro config .example files with missing key detection 2026-06-06 16:38:48 +02:00
root 834908df9f fix: always sync Gamedata configs from latest .example files 2026-06-06 16:30:24 +02:00
root 434f58d5aa fix: use systemctl cat instead of list-units for reliable service detection 2026-06-06 16:24:44 +02:00
root 4c085ac7eb Move .env.install to docs/INSTALL.md as Markdown 2026-06-06 14:35:22 +02:00
root bbefdce290 Add safety note to update-NitroV3 tutorial 2026-06-06 14:31:44 +02:00
root 5b8d1cda7d Move CHANGELOG to docs/ and add English update-NitroV3 tutorial with troubleshooting 2026-06-06 14:29:54 +02:00
root ea2160132a Nitro V3 update: CLI-only (Linux), settings via .env
- Moved Nitro V3 update from web UI (Commandocentrum) to CLI-only
- Removed configure paths form and runUpdateNitrov3() from admin panel
- update-Nitrov3.sh now loads .env automatically from its directory
- Added all NITRO_* env vars to .env.example.linux and .env
- Removed configurable paths from database (replaced by .env)
- Updated README and CHANGELOG
2026-06-06 14:11:50 +02:00
root 2ac1264b93 Fix: SQL import exclude backup dir, replace process substitution with pipe, error handling in loop 2026-06-05 23:20:12 +02:00
root 0f0de43da5 Fix: mariadb-dump || echo instead of hard fail on missing tables 2026-06-05 23:17:03 +02:00
root 1ee60405ce Cleanup: clean_node_modules helper, yarn cache clean || true, remove duplicate blocks 2026-06-05 23:13:15 +02:00
root 78704190bc Fix: rm -rf node_modules fallback to sudo on permission denied + chown safety 2026-06-05 23:09:01 +02:00
root f109b1f764 Fix: check if jar exists after mvn package, clear error message 2026-06-05 23:03:47 +02:00
root af2f76d9db Fix: mariadb-dump --skip-lock-tables --force for missing tables 2026-06-05 23:01:59 +02:00
root 6f11cae3ca Fix: unbound variable _UNBUFFERED () 2026-06-05 23:00:45 +02:00
root 16b2f5bbb7 Fix: unbuffer for realtime output, set -euo pipefail + trap for 0 errors guarantee 2026-06-05 22:59:24 +02:00
root 6e437be6d1 Improve update-Nitrov3.sh: realtime output, strict yarn install, perms fix, extra validation 2026-06-05 22:56:20 +02:00
root 4f4f40ac99 Fix radio Filament: actions() must receive array not Closure 2026-06-04 21:04:28 +02:00
root be6a578f5e Fix radio Filament: recordActions->actions, toolbarActions->headerActions, EmbedCode statePath 2026-06-04 20:59:40 +02:00
root 4d8d22f40a Security: admin radio routes now require auth+admin.security, CORS default no longer wildcard, README security section 2026-06-04 20:46:07 +02:00
root 8a324b3082 README: security note on sudoers, restricted chown path, install polish 2026-06-04 20:36:20 +02:00
root f7fe86efeb Refactor HotelApiController into 6 focused controllers + FurniEditorController Eloquent migration 2026-06-04 20:32:15 +02:00
root 36887244e6 Fix config/app.php missing semicolon (caused 500) 2026-06-04 20:22:51 +02:00
root 9b5c655c68 High priority fixes: PayPal env(), RadioApiKey Bearer-only, User restrict, SettingsService TTL, PHPStan config, + fix 7 broke points (forceFill) 2026-06-04 20:17:45 +02:00
root b2bb1811d0 Medium priority fixes: CORS from env, shared HasRadioSettings trait, lazy RconService, validated() fixes, LogoGenerator hardening, DB indexes, user profile consistency, radio rank N+1 fix 2026-06-04 20:05:36 +02:00
root 4b6872e5e0 Low priority fixes: debug comments, Fortify cleanup, badge cost setting, profile query merge, User model fixes, VPN constructor cleanup, PayPal POST, PII removal, Dutch→English translations, duplicate rank check, CHANGELOG 2026-06-04 19:57:01 +02:00
root 66cbd46f37 Add 'What's New in V3' section to README 2026-06-04 19:41:48 +02:00
root 889cec60e9 Clean README in Arcturus style: badges, tables, quick start, clear sections 2026-06-04 19:37:10 +02:00
root 8ce3fcca85 Bulletproof installation: Redis, build-essential, DB user creation, PHP tuning, SSL, firewall, verification 2026-06-04 19:34:06 +02:00
root b8a15c8412 Compact and correct README: PHP 8.5, php8.5-fpm, Ubuntu 26.04, remove changelogs 2026-06-04 19:31:54 +02:00
root 82d00ad11d Rename .env.example to .env.install with step-by-step guide, update README 2026-06-04 19:30:24 +02:00
root c468040792 Add .env.example.linux and .env.example.windows, remove .env.standard, update README 2026-06-04 19:27:53 +02:00
root 4487084614 Update README: PHP 8.5, Ubuntu 26.04 2026-06-04 19:24:51 +02:00
root e96e2a0fd3 Remove all XAMPP support from CMS (files, references, configs) 2026-06-04 19:22:01 +02:00
root 4378144f45 Remove server IP from README, use placeholder 2026-06-04 19:19:02 +02:00
root 59188a5f2c XAMPP warning in English 2026-06-04 19:18:19 +02:00
root ff364992ca Add XAMPP security warning: not supported, extremely unsafe for production 2026-06-04 19:17:18 +02:00
root 2997486662 Rewrite README: document Nitro V3 update system, sudoers config, Commandocentrum; remove outdated content 2026-06-04 19:14:40 +02:00
118 changed files with 3103 additions and 3539 deletions
-118
View File
@@ -1,118 +0,0 @@
# AtomCMS Environment Configuration
# Works on Linux (Nginx/Apache) and Windows (XAMPP/IIS)
# Copy to .env and adjust values for your setup
# ==========================================
# APPLICATION
# ==========================================
APP_NAME="AtomCMS"
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
# ==========================================
# DATABASE - Works on Linux & Windows
# ==========================================
DB_CONNECTION=mariadb
DB_HOST=127.0.0.1 # Linux: 127.0.0.1, Windows XAMPP: localhost
DB_PORT=3306
DB_DATABASE=habbo
DB_USERNAME=root
DB_PASSWORD=
# Windows IIS (SQL Server) - uncomment if using IIS/SQL Server:
# DB_CONNECTION=sqlsrv
# DB_HOST=(local)
# DB_DATABASE=habbo
# DB_USERNAME=sa
# DB_PASSWORD=your_password
# ==========================================
# SESSION & CACHE (Windows compatible)
# ==========================================
SESSION_DRIVER=file
SESSION_LIFETIME=120
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
# ==========================================
# MAIL (Windows compatible)
# ==========================================
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="noreply@yourhotel.nl"
MAIL_FROM_NAME="${APP_NAME}"
# ==========================================
# REAL-TIME & PUSHER
# ==========================================
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
# ==========================================
# SECURITY
# ==========================================
# Cloudflare Turnstile (optional)
TURNSTILE_SITE_KEY=
TURNSTILE_SECRET_KEY=
# Google Recaptcha (optional)
NOCAPTCHA_SECRET=
NOCAPTCHA_SITEKEY=
# ==========================================
# WEBHOOKS
# ==========================================
DISCORD_WEBHOOK_URL=
# ==========================================
# NITRO CLIENT PATHS
# ==========================================
# Linux: /var/www/atomcms/nitro-client
# Windows: C:\path\to\atomcms\nitro-client
NITRO_CLIENT_PATH=/var/www/atomcms/nitro-client
NITRO_RENDERER_PATH=/var/www/atomcms/nitro-renderer
NITRO_BUILD_PATH=/var/www/atomcms/nitro-client/dist
NITRO_WEBROOT=/Client
GAMEDATA_PATH=/var/Gamedata
# ==========================================
# LOGGING
# ==========================================
LOG_CHANNEL=stack
LOG_LEVEL=debug
# ==========================================
# BROADCAST & FILESYSTEMS
# ==========================================
BROADCAST_DRIVER=log
FILESYSTEM_DISK=local
# ==========================================
# LOCALIZATION
# ==========================================
APP_LOCALE=nl
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=nl_NL
# ==========================================
# CORS
# ==========================================
CORS_ALLOWED_ORIGINS=
# ==========================================
# SECURITY SETTINGS
# ==========================================
APP_SECURE=true
SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost:3000,127.0.0.1:3000
+95
View File
@@ -0,0 +1,95 @@
APP_NAME="YourHotel"
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_URL=https://yourhotel.nl
# --- LOGGING ---
LOG_CHANNEL=daily
LOG_MAX_FILES=14
LOG_LEVEL=error
# --- DATABASE ---
DB_CONNECTION=mariadb
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=habbo
DB_USERNAME=cms
DB_PASSWORD=your_db_password
DB_STRICT_MODE=false
DB_ENGINE=InnoDB
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
DB_DUMP_BINARY_PATH=/usr/bin/
# --- CACHE & SESSION (Redis recommended on Linux) ---
BROADCAST_DRIVER=redis
CACHE_STORE=redis
CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=
REDIS_PORT=6379
REDIS_DB=0
REDIS_CACHE_DB=1
REDIS_QUEUE_DB=2
REDIS_PERSISTENT=true
REDIS_READ_TIMEOUT=1.0
REDIS_CONNECT_TIMEOUT=1.0
# --- FILAMENT ---
FILAMENT_FILESYSTEM_DISK=public
FILAMENT_AUTH_GUARD=web
# --- MAIL ---
MAIL_MAILER=log
MAIL_FROM_ADDRESS="noreply@yourhotel.nl"
MAIL_FROM_NAME="${APP_NAME}"
# --- SESSION SECURITY ---
SESSION_LIFETIME=120
SESSION_DOMAIN=.yourhotel.nl
SESSION_SECURE_COOKIE=true
SESSION_HTTP_ONLY=true
SESSION_SAME_SITE=lax
SESSION_COOKIE=__secure_session
# --- SANCTUM ---
SANCTUM_STATEFUL_DOMAINS=yourhotel.nl,www.yourhotel.nl,localhost:3000
# --- EMULATOR ---
TURNSTILE_SITE_KEY=
TURNSTILE_SECRET_KEY=
RCON_HOST=127.0.0.1
RCON_PORT=3001
EMULATOR_IP=127.0.0.1
EMULATOR_PORT=3000
# --- NITRO UPDATE (for update-Nitrov3.sh) ---
NITRO_EMULATOR_PATH=/var/www/emulator
NITRO_EMULATOR_SERVICE=emulator
NITRO_DB_HOST=127.0.0.1
NITRO_DB_PORT=3306
NITRO_DB_NAME=habbo
NITRO_DB_USER=root
NITRO_DB_PASS=
NITRO_SQL_DIR=/var/www/emulator/Database Updates
NITRO_BACKUP_DIR=/var/www/emulator/Database Updates/backups
NITRO_GAMEDATA_DIR=/var/www/Gamedata/config
NITRO_CLIENT_DIR=/var/www/Nitro-V3/public/configuration
NITRO_CLIENT_SRC=/var/www/Nitro-V3
NITRO_RENDERER_SRC=/var/www/Nitro_Render_V3
# --- CORS ---
CORS_ALLOWED_ORIGINS=https://yourhotel.nl,https://www.yourhotel.nl,http://localhost:3000
# --- LOCALIZATION ---
APP_LOCALE=nl
FORCE_HTTPS=true
PASSWORD_RESET_TOKEN_TIME=15
+70
View File
@@ -0,0 +1,70 @@
APP_NAME="YourHotel"
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_URL=http://localhost
# --- LOGGING ---
LOG_CHANNEL=daily
LOG_MAX_FILES=14
LOG_LEVEL=error
# --- DATABASE ---
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=habbo
DB_USERNAME=root
DB_PASSWORD=
DB_STRICT_MODE=false
DB_ENGINE=InnoDB
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
# --- CACHE & SESSION (File-based for Windows compat) ---
BROADCAST_DRIVER=log
CACHE_STORE=file
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_CONNECTION=sync
# --- MAIL ---
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="noreply@yourhotel.nl"
MAIL_FROM_NAME="${APP_NAME}"
# --- SESSION SECURITY ---
SESSION_LIFETIME=120
SESSION_DOMAIN=localhost
SESSION_SECURE_COOKIE=false
SESSION_HTTP_ONLY=true
SESSION_SAME_SITE=lax
SESSION_COOKIE=__secure_session
# --- EMULATOR ---
TURNSTILE_SITE_KEY=
TURNSTILE_SECRET_KEY=
RCON_HOST=127.0.0.1
RCON_PORT=3001
EMULATOR_IP=127.0.0.1
EMULATOR_PORT=3000
# --- CORS ---
CORS_ALLOWED_ORIGINS=http://localhost:3000
# --- LOCALIZATION ---
APP_LOCALE=nl
FORCE_HTTPS=false
PASSWORD_RESET_TOKEN_TIME=15
# --- NITRO PATHS (Windows) ---
NITRO_CLIENT_PATH=C:\Nitro-V3
NITRO_RENDERER_PATH=C:\Nitro_Render_V3
NITRO_BUILD_PATH=C:\Nitro-V3\dist
NITRO_WEBROOT=/Client
GAMEDATA_PATH=C:\Gamedata
-64
View File
@@ -1,64 +0,0 @@
APP_NAME="Epicnabbo Hotel"
APP_ENV=production
APP_KEY=base64:YOUR_APP_KEY_HERE
APP_DEBUG=false
APP_URL=https://epicnabbo.nl
# --- LOGGING ---
LOG_CHANNEL=daily
LOG_MAX_FILES=14
LOG_LEVEL=error
# --- DATABASE ---
DB_CONNECTION=mariadb
DB_HOST=YOUR_DB_HOST
DB_PORT=3306
DB_DATABASE=YOUR_DB_NAME
DB_USERNAME=YOUR_DB_USER
DB_PASSWORD=YOUR_DB_PASSWORD
DB_STRICT_MODE=true
DB_ENGINE=InnoDB
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
DB_DUMP_BINARY_PATH=/usr/bin/
# --- REDIS TURBO ---
BROADCAST_DRIVER=redis
CACHE_STORE=redis
CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_DB=0
REDIS_CACHE_DB=1
REDIS_QUEUE_DB=2
REDIS_PERSISTENT=true
REDIS_READ_TIMEOUT=1.0
REDIS_CONNECT_TIMEOUT=1.0
# --- SESSION SECURITY ---
SESSION_LIFETIME=120
SESSION_DOMAIN=.epicnabbo.nl
SESSION_SECURE_COOKIE=true
SESSION_HTTP_ONLY=true
SESSION_SAME_SITE=lax
SESSION_COOKIE=__epicnabbo_secure_session
# --- BEVEILIGING & EMULATOR ---
TURNSTILE_SITE_KEY=YOUR_TURNSTILE_SITE_KEY
TURNSTILE_SECRET_KEY=YOUR_TURNSTILE_SECRET_KEY
RCON_HOST=127.0.0.1
RCON_PORT=3001
EMULATOR_IP=127.0.0.1
EMULATOR_PORT=3000
# --- EXTRA HARDENING ---
APP_LOCALE=nl
FORCE_HTTPS=true
PASSWORD_RESET_TOKEN_TIME=15
-87
View File
@@ -1,87 +0,0 @@
# Windows/XAMPP/IIS .env configuration
# Copy this to .env and adjust the values for your Windows setup
# Application
APP_NAME="Epicnabbo"
APP_ENV=local
APP_KEY=base64:EXAMPLE_KEY_CHANGE_ME
APP_DEBUG=true
APP_URL=http://localhost:8000
# Database - Windows/XAMPP Settings
# For XAMPP use: DB_HOST=localhost
# For IIS use: DB_HOST=127.0.0.1 or localhost
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=habbo
DB_USERNAME=root
DB_PASSWORD=
# For Windows IIS with named pipes, use:
# DB_CONNECTION=sqlsrv
# DB_HOST=(local)
# DB_DATABASE=habbo
# DB_USERNAME=sa
# DB_PASSWORD=your_password
# Session (Windows compatible)
SESSION_DRIVER=file
SESSION_LIFETIME=120
# Cache (Windows compatible)
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
# Mail (Windows compatible - use Mailtrap for testing)
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="noreply@epicnabbo.nl"
MAIL_FROM_NAME="${APP_NAME}"
# Pusher (for real-time features)
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
# Cloudflare Turnstile (optional)
TURNSTILE_SITE_KEY=
TURNSTILE_SECRET_KEY=
# Google Recaptcha (optional)
NOCAPTCHA_SECRET=
NOCAPTCHA_SITEKEY=
# Discord Webhook (optional)
DISCORD_WEBHOOK_URL=
# Nitro Client Path (Windows paths use backslashes or forward slashes)
NITRO_CLIENT_PATH=C:\path\to\nitro-client
NITRO_RENDERER_PATH=C:\path\to\nitro-renderer
# Logging
LOG_CHANNEL=stack
LOG_LEVEL=debug
# Broadcast & Filesystems (Windows compatible)
BROADCAST_DRIVER=log
FILESYSTEM_DISK=local
# Advanced (usually no changes needed)
APP_LOCALE=nl
APP_FALLBACK_LOCALE=en
APP_LOCALE=nl
APP_FAKER_LOCALE=nl_NL
# Security
APP_SECURE=true
SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost:3000,127.0.0.1:3000
+33 -26
View File
@@ -1,42 +1,49 @@
# Negeer wachtwoorden en database-instellingen # --- Environment ---
.env .env
.env.backup .env.backup
.env.testing .env.testing
config.php .env.production
wp-config.php
/uploads/
/temp/
*.log
# Geen zware afhankelijkheden pushen # --- Dependencies ---
node_modules/ node_modules/
vendor/ vendor/
# UITZONDERING: Vertaalbestanden in lang/vendor wel pushen # --- Build artifacts (hashed filenames) ---
/lang/vendor/ public/build/assets/
public/build/hot
# Systeembestanden (Mac/Windows troep) # --- Storage (uploaded files, cache, logs, sessions) ---
storage/app/livewire-tmp/
storage/app/public/
storage/clockwork/
storage/debugbar/
storage/framework/views/
storage/framework/cache/
storage/framework/sessions/
storage/framework/testing/
storage/logs/
# --- IDE & OS ---
.idea/
.vscode/
*.swp
*.swo
*~
.DS_Store .DS_Store
Thumbs.db Thumbs.db
# VPS automation scripts (niet pushen naar GitLab) # --- Config (no default .env push) ---
watch.sh config.php
check-updates.sh
# Cache bestanden (niet pushen naar GitLab)
/storage/framework/views/
/storage/framework/cache/
/storage/framework/sessions/
/storage/logs/
/storage/debugbar/rr
.rr.yaml .rr.yaml
# Lockfiles (kies 1 package manager) # --- Tests & Temp ---
package-lock.json
# Overgebleven test/temp bestanden
ci_test.txt ci_test.txt
cookies.txt cookies.txt
.phpunit.result.cache
# GitHub workflows (pushen naar GitLab) # --- Lock files (yarn.lock is tracked, package-lock.json is not) ---
!/.github/workflows/ package-lock.json
# --- Scripts (local-only) ---
watch.sh
check-updates.sh
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright 2023 ObjectRetros Copyright 2026 Remco (Epicnabbo)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+228 -436
View File
@@ -1,537 +1,329 @@
# AtomCMS — Remco Epicnabbo Edition # AtomCMS — Remco Epicnabbo Edition
<div align="center"> [![Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/pP6HyZedAj)
<img src="https://i.imgur.com/9ePNdJ4.png" alt="Atom CMS" width="200"/> [![Laravel](https://img.shields.io/badge/Laravel-13.x-FF2D20?style=for-the-badge&logo=laravel&logoColor=white)](https://laravel.com)
[![PHP](https://img.shields.io/badge/PHP-8.5+-777BB4?style=for-the-badge&logo=php&logoColor=white)](https://php.net)
[![License](https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge)](#)
### The Ultimate Retro Hotel CMS A modern Habbo retro CMS powered by Laravel 13, Filament 5, React 19, and Nitro. Forked and maintained by Remco (Epicnabbo).
**Modern • Fast • Self-Repairing** ---
[![Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?style=flat&logo=discord&logoColor=white)](https://discord.gg/pP6HyZedAj) ## What's New in V3
[![Laravel](https://img.shields.io/badge/Laravel-13.x-FF2D20?style=flat&logo=laravel&logoColor=white)](https://laravel.com)
[![PHP](https://img.shields.io/badge/PHP-8.1+-777BB4?style=flat&logo=php&logoColor=white)](https://php.net)
[![Code Quality](https://img.shields.io/badge/Code%20Quality-A+-green?style=flat)](https://github.com/atom-retros/atomcms)
</div> | Feature | Description |
|---------|-------------|
| **Commandocentrum** | Central admin dashboard with Nitro, emulator & hotel monitoring |
| **Nitro V3 Update System** | Auto-update emulator, Nitro client & renderer via CLI (Linux `.env`) |
| **Configurable Paths** | 13 paths fully adjustable via `.env` (no database needed) |
| **Emulator Control** | Start, stop, restart & check status from the admin panel |
| **Live Monitoring** | Online users, emulator status, DB status, server load, diagnostics |
| **Hotel Alerts** | Send messages to all online users in real-time |
| **Emulator Log Viewer** | Live logs directly in the browser |
| **Clothing Sync** | Sync catalog clothing from FigureMap with one click |
| **Social Login** | OAuth login via Google, Discord & GitHub |
| **Notification Settings** | Email & Discord webhook alerts with rank filtering |
| **Staff Activity Log** | Full audit trail of all housekeeping actions |
| **Bulletproof Installation** | 12-step guide for Ubuntu 26.04 with Redis, SSL, firewall & PHP tuning |
| **PHP 8.5 + Ubuntu 26.04** | Fully compatible with the latest PHP and Ubuntu LTS |
| **Dual .env System** | Separate configs for Linux (Redis) and Windows (file-based) |
| **XAMPP Blocked** | Explicitly unsupported — we prioritise security |
---
## Quick Start
```bash
git clone https://git.your-server.com/remco/Atomcms-edit.git /var/www/atomcms
cd /var/www/atomcms
cp .env.example.linux .env
php artisan key:generate
# Edit .env with your DB credentials, then:
composer install --no-dev --optimize-autoloader
php artisan migrate --seed
yarn install && yarn build:all
```
> **Full installation guide** → `docs/INSTALL.md` or scroll down to [Installation](#installation-ubuntu-2604)
--- ---
## Features ## Features
### Radio Station | Module | What it does |
- DJ applications & rank system |--------|-------------|
- Live DJ sessions with schedule | **Commandocentrum** | Nitro V3 one-click updater, emulator start/stop/restart, hotel alerts, live monitoring, log viewer, clothing sync, social login (Google/Discord/GitHub) |
- Song requests & voting | **Radio** | DJ apps, live sessions, song requests, shoutbox, leaderboard, contests |
- Shoutbox | **Shop** | Product catalog, virtual currency, vouchers, PayPal |
- Listener points & leaderboard | **Community** | Articles, photo gallery, leaderboard, teams, rare values, badge lottery |
- Contests & giveaways | **Users** | Public profiles, 2FA, referrals, session logs |
- Radio banners & history | **Help** | Ticket system, FAQ, rules |
| **Filament Admin** | Users, bans, radio, shop, articles, emulator settings/texts/catalog, chatlogs, word filters, permissions, navigation |
| **Themes** | Atom (light) & Dusk (dark) |
### Shop ---
- Product catalog with categories
- Virtual currency purchases
- Voucher/promo codes
- PayPal integration
- Order history
### Community ## Nitro V3 Update (Linux-only)
- Articles with comments, reactions & tags
- Photo gallery
- Leaderboard
- Teams & team applications
- Staff page & staff applications
- Rare values tracker
- Badge draw/lottery system
### Hotel Client > ⚠️ **CLI only.** The web UI button has been removed. The script is configured via `.env` variables.
- Nitro (HTML5) client support
- Flash client support
- FindRetros integration
- VPN/proxy checker
### User System **What it does:** `git pull` emulator → DB backup → SQL imports → Maven build → `git pull` Nitro_Render_V3 + Nitro-V3 → `yarn build` → sync Gamedata → cleanup → restart emulator.
- Public profiles with guestbook
- Two-Factor Authentication (2FA)
- Referral system with rewards
- Account & password settings
- Session logs
### Help Center **Usage:**
- Support ticket system with replies & status management
- Website rules
- FAQ with categories
### Themes ```bash
- **Atom** — Default light theme # Make sure .env contains all NITRO_* variables (see .env.example.linux)
- **Dusk** — Dark theme cd /var/www/atomcms
bash update-Nitrov3.sh
```
### Filament Admin Panel **Configurable via `.env`:**
- User & ban management
- Radio management (applications, schedules, ranks, shouts, history, banners)
- Shop & order management with charts
- Article & tag management
- Emulator settings, texts & catalog editors
- Chatlog viewer (rooms & private messages)
- Word filters & moderation tools
- Housekeeping permissions
- Website navigation & settings
- Camera/photo management
### API Endpoints | Variable | Default | Description |
- `GET /api/user/{username}` — Fetch user data |----------|---------|-------------|
- `GET /api/online-users` — Online users list | `NITRO_EMULATOR_PATH` | `/var/www/emulator` | Emulator root directory |
- `GET /api/online-count` — Online user count | `NITRO_EMULATOR_SERVICE` | `emulator` | Systemd service name |
- `GET /api/radio/current-dj` — Current DJ info | `NITRO_DB_HOST` | `127.0.0.1` | Database host |
- `GET /api/radio/now-playing` — Current song | `NITRO_DB_PORT` | `3306` | Database port |
- `GET /api/radio/listeners` — Listener count | `NITRO_DB_NAME` | `habbo` | Database name |
- `GET /api/radio/shouts` — Recent shouts | `NITRO_DB_USER` | `root` | Database user |
- `GET /api/radio/points` — Points data | `NITRO_DB_PASS` | — | Database password |
- `GET /api/radio/points/leaderboard` — Points leaderboard | `NITRO_SQL_DIR` | `{emulator}/Database Updates` | SQL updates directory |
| `NITRO_BACKUP_DIR` | `{emulator}/Database Updates/backups` | Backup directory |
| `NITRO_GAMEDATA_DIR` | `/var/www/Gamedata/config` | Gamedata config directory |
| `NITRO_CLIENT_DIR` | `{nitro}/public/configuration` | Nitro client config directory |
| `NITRO_CLIENT_SRC` | `/var/www/Nitro-V3` | Nitro-V3 source directory |
| `NITRO_RENDERER_SRC` | `/var/www/Nitro_Render_V3` | Nitro Render V3 source directory |
--- ---
## Requirements ## Requirements
| Component | Requirement | | Component | Version |
| ------------- | ------------------------------- | |-----------|---------|
| **PHP** | 8.1 or higher | | **PHP** | 8.5+ |
| **Database** | MariaDB 10.6+ or MySQL 8.0+ | | **Database** | MariaDB 10.6+ or MySQL 8.0+ |
| **Web Server**| Apache (mod_rewrite) or Nginx | | **Web Server** | Nginx or Apache |
| **Node.js** | 20 or higher | | **Node.js** | 20+ |
| **Yarn** | 1.22+ or 4.x (Berry) | | **Yarn** | 1.22+ |
| **Composer** | 2.x | | **Composer** | 2.x |
| **Redis** | Recommended (Linux) |
--- ---
## Installation ## Environment Files
### Linux (Ubuntu / Debian) | File | Use | Cache | DB |
|------|-----|-------|----|
| `docs/INSTALL.md` | Step-by-step setup guide | — | — |
| `.env.example.linux` | Linux production | Redis | MariaDB |
| `.env.example.windows` | Windows development | File | MySQL |
```bash
cp .env.example.linux .env
php artisan key:generate
```
> ⚠️ **XAMPP is not supported.** Extremely unsafe for production.
---
## Installation (Ubuntu 26.04)
```bash ```bash
# 1. System dependencies # 1. System dependencies
sudo apt update sudo apt update
sudo apt install -y git curl wget unzip nginx mariadb-server \ sudo apt install -y git curl wget unzip nginx mariadb-server redis-server \
php8.3 php8.3-cli php8.3-fpm php8.3-mysql php8.3-xml \ php8.5 php8.5-{cli,fpm,mysql,xml,mbstring,curl,zip,bcmath,gd,sockets,intl} \
php8.3-mbstring php8.3-curl php8.3-zip php8.3-bcmath \ build-essential
php8.3-gd php8.3-sockets php8.3-intl
# 2. Install Composer # 2. Composer
curl -sS https://getcomposer.org/installer | php curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer sudo mv composer.phar /usr/local/bin/composer
# 3. Install Node.js & Yarn # 3. Node.js + Yarn
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs sudo apt install -y nodejs
sudo corepack enable sudo corepack enable
corepack install -g yarn@latest corepack install -g yarn@latest
# 4. Clone the project # 4. Secure MariaDB
git clone https://gitlab.epicnabbo.nl/RemcoEpic/atomcms-edit.git /var/www/atomcms sudo mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED BY 'your_root_password'; FLUSH PRIVILEGES;"
# 5. Clone
git clone https://git.your-server.com/remco/Atomcms-edit.git /var/www/atomcms
cd /var/www/atomcms cd /var/www/atomcms
# 5. Configure environment # 6. Configure
cp .env.example .env cp .env.example.linux .env
# EDIT .env first: set DB_PASSWORD, APP_URL, SESSION_DOMAIN
nano .env
php artisan key:generate php artisan key:generate
# Edit .env with your database credentials:
# DB_DATABASE=habbo
# DB_USERNAME=root
# DB_PASSWORD=yourpassword
# 6. Install PHP dependencies # 7. Create database + user
sudo mysql -e "CREATE DATABASE IF NOT EXISTS habbo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
sudo mysql -e "CREATE USER IF NOT EXISTS 'cms'@'localhost' IDENTIFIED BY 'your_db_password';"
sudo mysql -e "GRANT ALL ON habbo.* TO 'cms'@'localhost'; FLUSH PRIVILEGES;"
# 8. Install PHP & JS deps
composer install --no-dev --optimize-autoloader composer install --no-dev --optimize-autoloader
# 7. Install frontend dependencies
yarn install yarn install
# 8. Create database # 9. Migrate, seed & cache
mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS habbo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
# 9. Run migrations & seeders (or use the auto-repair tool)
php artisan migrate --seed php artisan migrate --seed
# Or use the interactive repair tool: php artisan optimize
# php artisan atom:check --fix php artisan filament:optimize
# 10. Build frontend assets # 10. Build frontend
yarn build:all yarn build:all
# 11. Set permissions # 11. Permissions
sudo chown -R www-data:www-data storage bootstrap/cache public/build sudo chown -R www-data:www-data storage bootstrap/cache public/build
sudo chmod -R 775 storage bootstrap/cache sudo chmod -R 775 storage bootstrap/cache
# 12. Configure web server # 12. Sudoers (for update-Nitrov3.sh — sudo chown + systemctl)
sudo tee /etc/sudoers.d/www-data << 'EOF'
www-data ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart emulator
www-data ALL=(ALL) NOPASSWD: /usr/bin/systemctl status emulator
www-data ALL=(ALL) NOPASSWD: /usr/bin/chown -R www-data\:www-data /var/www/*
EOF
sudo chmod 440 /etc/sudoers.d/www-data
# --- Nginx --- # 13. Start services
sudo nano /etc/nginx/sites-available/atomcms sudo systemctl enable --now redis-server
# 14. PHP tuning
sudo sed -i 's/upload_max_filesize = .*/upload_max_filesize = 64M/' /etc/php/8.5/fpm/php.ini
sudo sed -i 's/post_max_size = .*/post_max_size = 64M/' /etc/php/8.5/fpm/php.ini
sudo sed -i 's/memory_limit = .*/memory_limit = 256M/' /etc/php/8.5/fpm/php.ini
sudo sed -i 's/max_execution_time = .*/max_execution_time = 300/' /etc/php/8.5/fpm/php.ini
# 16. Restart & verify
sudo systemctl restart php8.5-fpm redis-server nginx
php artisan about # should show green "Application" line
``` ```
### Nginx
```nginx
server { server {
listen 80; listen 80;
server_name your-domain.com; server_name your-domain.com;
root /var/www/atomcms/public; root /var/www/atomcms/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php; index index.php;
charset utf-8; charset utf-8;
location / { add_header X-Frame-Options "SAMEORIGIN" always;
try_files $uri $uri/ /index.php?$query_string; add_header X-Content-Type-Options "nosniff" always;
} add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location = /favicon.ico { access_log off; log_not_found off; } gzip on;
location = /robots.txt { access_log off; log_not_found off; } gzip_types text/plain text/css application/json application/javascript text/xml image/svg+xml;
gzip_vary on;
error_page 404 /index.php;
location / { try_files $uri $uri/ /index.php?$query_string; }
location ~ \.php$ { location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; fastcgi_pass unix:/var/run/php/php8.5-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params; include fastcgi_params;
} }
location ~ /\.(?!well-known).* { deny all; }
location ~ /\.(?!well-known).* { location ~ /(\.env|\.git|composer\.(json|lock)) { deny all; }
deny all;
}
} }
``` ```
```bash ```bash
sudo ln -sf /etc/nginx/sites-available/atomcms /etc/nginx/sites-enabled/ sudo ln -sf /etc/nginx/sites-available/atomcms /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx sudo nginx -t && sudo systemctl reload nginx
sudo systemctl restart php8.5-fpm redis-server
# --- Apache --- sudo ufw allow 80/tcp && sudo ufw allow 443/tcp && sudo ufw --force enable
sudo a2enmod rewrite
sudo nano /etc/apache2/sites-available/atomcms.conf
``` ```
<VirtualHost *:80>
ServerName your-domain.com
DocumentRoot /var/www/atomcms/public
<Directory /var/www/atomcms/public> ### SSL (recommended)
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/atomcms-error.log
CustomLog ${APACHE_LOG_DIR}/atomcms-access.log combined
</VirtualHost>
```
```bash ```bash
sudo a2ensite atomcms sudo apt install -y certbot python3-certbot-nginx
sudo systemctl reload apache2 sudo certbot --nginx -d your-domain.com
```
# 13. Restart PHP-FPM to clear opcache
sudo systemctl restart php8.3-fpm
# 14. Visit http://your-domain.com in your browser
``` ```
--- ---
### Windows (XAMPP / WampServer) ## Yarn Scripts
#### Prerequisites
- [XAMPP](https://www.apachefriends.org/) (PHP 8.1+, Apache, MariaDB) or [WampServer](https://www.wampserver.com/)
- [Node.js](https://nodejs.org/) (20 LTS or higher)
- [Yarn](https://yarnpkg.com/getting-started/install) (`npm install -g yarn` after Node.js)
- [Composer](https://getcomposer.org/download/) (Windows installer)
- [Git for Windows](https://git-scm.com/download/win)
#### Steps
```powershell
# 1. Open PowerShell or CMD as Administrator
# 2. Clone the project
git clone https://gitlab.epicnabbo.nl/RemcoEpic/atomcms-edit.git C:\xampp\htdocs\atomcms
cd C:\xampp\htdocs\atomcms
# 3. Configure environment
copy .env.example .env
php artisan key:generate
# Edit .env with your XAMPP database credentials:
# DB_CONNECTION=mariadb
# DB_HOST=localhost
# DB_PORT=3306
# DB_DATABASE=habbo
# DB_USERNAME=root
# DB_PASSWORD=
# 4. Start XAMPP services
# Open XAMPP Control Panel → Start Apache & MySQL
# 5. Create database
# Open phpMyAdmin (http://localhost/phpmyadmin)
# → New → Database name: habbo → Charset: utf8mb4_unicode_ci → Create
# 6. Install PHP dependencies
composer install --no-dev --optimize-autoloader
# 7. Install frontend dependencies
yarn install
# 8. Run migrations & seeders
php artisan migrate --seed
# Or use the interactive repair tool:
# php artisan atom:check --fix
# 9. Build frontend assets
yarn build:all
# 10. Set permissions (required for storage & cache)
# Right-click storage\ and bootstrap\cache\ folders
# → Properties → Security → Edit → Add "Everyone" → Full Control
# Or run in PowerShell as Admin:
icacls storage /grant Everyone:F /T
icacls bootstrap/cache /grant Everyone:F /T
icacls public/build /grant Everyone:F /T
# 11. Enable Apache mod_rewrite
# Open XAMPP Control Panel → Apache → Config → httpd.conf
# Uncomment: LoadModule rewrite_module modules/mod_rewrite.so
# 12. Visit http://localhost/atomcms/public in your browser
```
> **Note:** If you use IIS on Windows, the repair tool supports auto-detection:
> `php artisan atom:check --fix`
---
## Quick Start (Repair Tool)
```bash ```bash
# Interactive mode (asks for confirmation before each step) yarn build:all # Build all themes
php artisan atom:check --fix yarn build:atom # Atom theme only
yarn build:dusk # Dusk theme only
# Auto mode (fixes everything automatically without asking) yarn dev # Vite dev server
php artisan atom:check --auto yarn lint # Lint JS/Vue
yarn format # Format code
# Force platform detection
php artisan atom:check --platform=nginx
# Dutch language output
php artisan atom:check --fix --lang=nl
``` ```
--- ---
## Commands
```bash
# Check only (no changes)
php artisan atom:check
# Interactive fix (asks before each step)
php artisan atom:check --fix
# Automatic fix (no questions)
php artisan atom:check --auto
# Fix gamedata symlinks for bundled assets
php artisan atom:fix-gamedata-symlinks
# Preview symlinks (dry-run)
php artisan atom:fix-gamedata-symlinks --dry-run
```
### Yarn Scripts
```bash
# Build all themes
yarn build:all
# Build single theme
yarn build:atom # Atom theme
yarn build:dusk # Dusk theme
# Development
yarn dev # Start Vite dev server
yarn dev:atom # Dev server for Atom theme
# Linting & Formatting
yarn lint # Check JS/Vue
yarn lint:fix # Fix JS/Vue
yarn lint:css # Check CSS
yarn lint:css:fix # Fix CSS
yarn format # Format everything
yarn format:check # Check formatting
# Full check
yarn check # Lint + format check
yarn check:php # PHP syntax check
yarn check:security # Composer & npm audit
yarn check:deps # Check outdated packages
# Cache
yarn clean # Clear Vite cache
yarn rebuild # Clean + install + build
```
### PHP Commands
```bash
# Auto-fix code style
php artisan atom:fix-code
# Static analysis (PHPStan Level 3)
./vendor/bin/phpstan analyse
# Build frontend assets
yarn build:atom
yarn build:dusk
# Development mode
yarn dev:atom
```
---
## Repair Tool Details
The `atom:check` command performs over **100 deep system checks** and offers to fix them automatically.
### Diagnostic Checks
| Section | What it Checks |
| ------------- | ------------------------------------------------------------- |
| **Environment** | .env, APP_KEY, Debug mode, Composer security |
| **Database** | Tables, columns, migrations, seeders, settings, radio, admin |
| **PHP Stack** | Extensions, php.ini, config cache, session |
| **Web Server**| Apache/Nginx/IIS config, SSL |
| **System** | Permissions, firewall ports |
| **Assets** | Frontend, Redis, Cron, Queue, Supervisor, Vite |
| **HTTP Errors** | 400, 401, 403, 404, 419, 429, 500, 502, 503, 504 |
### Auto-Fix Steps
| Step | Action |
| ---- | ---------------------------------- |
| 1 | Environment (.env, APP_KEY) |
| 2 | Clear all caches |
| 3 | Fix permissions |
| 4 | Run migrations |
| 5 | Run seeders |
| 6 | Fix storage (symlink, directories) |
| 6b | Fix Radio tables |
| 7 | Fix Gamedata symlinks |
| 8 | Create admin user |
| 9 | Web server config (.htaccess) |
| 10 | PHP config & extensions |
| 11 | Build assets (npm) |
| 12 | Fix HTTP errors |
Auto-detects: **Linux, Windows IIS, XAMPP, WAMP, Apache, Nginx**
---
## Troubleshooting
### 500 Error After `php artisan optimize`
If you get a 500 error after running `php artisan optimize`, PHP OPcache is likely caching stale files. Fix:
```bash
# Disable OPcache in your FPM configuration:
# Edit /etc/php/8.x/fpm/conf.d/99-opcache.ini
opcache.enable=0
# Then restart PHP-FPM:
sudo systemctl restart php8.x-fpm
```
Alternatively, keep OPcache enabled but restart PHP-FPM after every `php artisan optimize`:
```bash
php artisan optimize
sudo systemctl restart php8.x-fpm
```
### Filament Translations Not Working
If Filament navigation labels or resource names aren't translating (e.g., Dashboard shows in English instead of Dutch):
1. Add the missing key to `lang/vendor/filament/{locale}/resources.php` (navigations section) or `lang/{locale}.json`
2. Clear the cache: `php artisan optimize:clear` (then restart PHP-FPM if OPcache is on)
---
## Performance Optimizations
| Optimization | Description |
| ------------------- | -------------------------------- |
| **Vite 8** | Fastest build tool |
| **esbuild** | Faster minification |
| **Better chunking** | Optimal code splitting |
| **Gzip + Brotli** | Compression (~70% smaller) |
| **Resource hints** | DNS prefetch, preconnect, preload|
| **HTTP/2** | Faster network requests |
| **Console removed** | Smaller JS bundles |
| **Caching** | Vite cache + optimizedeps |
### Gamedata Symlinks
The `atom:fix-gamedata-symlinks` command creates symlinks in the Gamedata directory to point to optimized bundled assets, significantly improving game client load times.
| Symlink | Target | Purpose |
| ---------------- | ------------------- | ---------------- |
| `effect` | `bundled/effect` | Avatar effects |
| `furniture` | `bundled/furniture` | Furniture assets |
| `generic` | `bundled/generic` | Generic assets |
| `pet` | `bundled/pet` | Pet assets |
| `figure` | `bundled/figure` | Avatar figures |
| `generic_custom` | `bundled/generic` | Custom generic |
---
## Tech Stack ## Tech Stack
- **Backend:** Laravel 13 **Laravel 13 · React 19 + Alpine.js · Vite 8 · TailwindCSS 4 · Filament 5 · MariaDB/MySQL · Redis**
- **Frontend:** React 19 + Alpine.js
- **Build:** Vite 8
- **CSS:** TailwindCSS 4
- **Admin Panel:** Filament 5
- **Database:** MariaDB / MySQL
- **Linting:** ESLint + Stylelint + Prettier
--- ---
## Changelog (May 27, 2026) ## Security
- **Added OPcache troubleshooting** — 500 error after `php artisan optimize` fix in README AtomCMS is built with security as a priority. Below is what's in place and what you need to configure.
- **Fixed Dashboard navigation translation** — added `Dashboard` key to Dutch Filament `resources.php`
- **Fixed Homepage navigation label** — added `Homepage` translation to `nl.json`
- **Disabled OPcache in FPM** — `99-opcache.ini` set to `opcache.enable=0` to prevent stale cache issues
## Changelog (May 26, 2026) ### ✅ Already locked down
- **Removed auto-recovery** — caused race conditions by running `view:clear` during live traffic, fixed `filemtime(): stat failed` errors | Measure | Details |
- **Fixed debug banner** — now uses `config('app.debug')` with proper `(bool)` cast, no more false positives |---------|---------|
- **Added `(bool)` casts** to all `env()` calls with boolean defaults across 6 config files (habbo, activitylog, database, filesystems, log-viewer, session) | **Mass assignment protection** | User model restricted to 21 fillable fields (sensitive fields like `rank`, `credits`, `online` require explicit `forceFill`) |
- **Removed dangerous public scripts** — `check_icons.php` and `test_open_basedir.php` (public Laravel bootstrap + DB queries) | **API authentication** | Sanctum tokens, Bearer-only (no query-string API keys accepted) |
- **Removed root `index.php`** — duplicate front controller, unsafe if docroot misconfigured | **PayPal credentials** | Loaded from `env()`, never hardcoded |
- **Cleaned Clockwork debug data** — 42 JSON files with SQL queries, tokens, and paths | **CORS** | Must be explicitly set via `CORS_ALLOWED_ORIGINS` env (no wildcard default) |
- **Hardened `.htaccess`** — block `.env`/`.git`/`composer.json` access + security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy) | **Debug mode** | `APP_DEBUG=false` by default |
- **Fixed `robots.txt`** — blocks crawlers from `/admin`, `/filament`, `/log-viewer` | **PHP debugging** | No `dd()`, `dump()`, or `var_dump()` in production code |
- **Disabled Log Viewer by default** — no longer accessible without explicit config | **Password flashing** | Exception handler excludes passwords from session flash |
- **Disabled Boost browser logs watcher** — stopped logging JS errors from every visitor to disk | **File uploads** | MIME validation (Laravel `image` rule + `finfo` on logos) |
- **Fixed `REDIS_PASSWORD`** — was literal string `"null"`, now empty | **2FA** | Two-factor authentication available |
- **Fixed Session `same_site`** — now reads from `.env` instead of being hardcoded | **SQL injection** | All queries use parameterized binding or Eloquent ORM |
- **Fixed non-existent model import** — `App\Models\Article` didn't exist, now aliased to `WebsiteArticle` | **Command injection** | All `exec()`/`shell_exec()` calls use `escapeshellarg()` or hardcoded values |
- **Removed unused traits** — `HasNotificationUrl` and `HasCommonScopes` (dead code) | **CSRF** | Sanctum CSRF protection on all stateful routes |
- **Restricted CORS headers** — from wildcard `['*']` to specific allowed list | **Insecure deserialization** | No `unserialize()` calls exist |
- **Rebuilt all caches** — config, views, routes, opcache reset, PHP-FPM restart
### ⚠️ You must configure
| Item | What to do |
|------|------------|
| **`.env` file** | Restrict file permissions (`chmod 600 .env`), ensure Nginx blocks access (already in the provided config) |
| **`CORS_ALLOWED_ORIGINS`** | Set to your exact frontend domain(s) in `.env` (included in the example files) |
| **Database password** | Use a strong, unique password (not `your_db_password`) |
| **APP_KEY** | Run `php artisan key:generate` after cloning |
| **Session domain** | Set `SESSION_DOMAIN` to your hotel domain in `.env` |
| **SSL** | Required for production — use the Certbot instructions above |
| **Admin accounts** | Only grant high-rank access to trusted users |
| **Log retention** | Check `LOG_MAX_FILES` in `.env` (default 14 days) |
### 🔒 Sudoers safety
The `sudoers.d/www-data` configuration grants passwordless `systemctl` and `chown` to `www-data`. This is **safe by design**:
- Each command is pinned to a specific binary path (`/usr/bin/systemctl`, `/usr/bin/chown`)
- `chown` is restricted to `/var/www/*`
- No shell (`/bin/sh`, `/bin/bash`) is granted
- No arbitrary binaries can be executed
- In a worst-case web compromise, the attacker still cannot read `/etc/shadow`, install packages, or run arbitrary commands
---
## Support
- **Discord:** [Join our server](https://discord.gg/pP6HyZedAj)
- **Issues:** Report bugs via the project issue tracker
- **Contributions:** Fork & submit merge requests — all help is welcome!
---
## Credits ## Credits
- **Remco (Epicnabbo)** — Core Maintainer, System Architecture **Remco (Epicnabbo)** — Core Maintainer · **Kasja** — Design & Themes · **Kani** — RCON & API · **Atom Community** — Testing & Feedback
- **Kasja** — Design & Themes
- **Kani** — RCON & API
- **Atom Community** — Testing & Feedback
<div align="center"> <div align="center"><i>Made with love for the Retro Community</i></div>
<i>Made with love for the Retro Community</i>
</div>
-273
View File
@@ -1,273 +0,0 @@
# AtomCMS (epicnabbo edit) - v1.0 Ultimate Setup Guide
**AtomCMS** is a powerful Habbo retro CMS. This guide ensures a **100% working installation** on **any system**: Linux (Nginx/Apache), Windows (IIS/XAMPP), and macOS.
---
## 🚀 Quick Start
```bash
git clone ssh://git@localhost:8422/RemcoEpic/atomcms-edit.git
cd atomcms-edit
composer install --no-dev
cp .env.example .env
php artisan key:generate
# Configure your .env (DB settings below)
php artisan migrate --seed
php artisan storage:link
php artisan serve
```
---
## 📋 System Requirements
### Universal Requirements
- **PHP**: 8.1, 8.2, or 8.3 (8.2 Recommended)
- **Extensions**: `pdo_mysql`, `curl`, `mbstring`, `openssl`, `tokenizer`, `xml`, `zip`
- **Database**: MySQL 5.7+ or MariaDB 10.3+
- **Composer**: 2.x
### Linux (Ubuntu/Debian)
```bash
sudo apt update
sudo apt install -y php8.2 php8.2-{mysql,curl,mbstring,xml,zip,fpm} nginx mysql-server
```
### Windows (XAMPP/IIS)
- Download **XAMPP** with PHP 8.2 or install **PHP 8.2** manually.
- Ensure `php.ini` has `extension=pdo_mysql`, `extension=curl`, etc. enabled.
---
## ⚙️ Step 1: Clone the Repository
```bash
# For RemcoEpic epicnabbo edit version:
git clone ssh://git@localhost:8422/RemcoEpic/atomcms-edit.git
cd atomcms-edit
```
---
## 📦 Step 2: Install Dependencies
Run composer to install all required packages. The system is optimized to handle missing extensions automatically via the `SystemCheck` middleware.
```bash
composer install --optimize-autoloader
```
_Windows Note: Run CMD/PowerShell as Administrator if you get permission errors._
---
## 🔧 Step 3: Environment Configuration
### 3.1 Create .env
If `.env` is missing, the system tries to create it automatically. You can also do it manually:
```bash
cp .env.example .env
```
### 3.2 Generate App Key
```bash
php artisan key:generate
```
### 3.3 Database Settings
Edit `.env` and configure your database connection:
```env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=atomcms
DB_USERNAME=root
DB_PASSWORD=your_password
APP_URL=http://localhost:8000
```
_Windows IIS Note: If `127.0.0.1` fails, try `localhost`._
---
## 💾 Step 4: Database Setup
### 4.1 Create Database
Login to MySQL and create the database:
```sql
CREATE DATABASE atomcms CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'atomcms_user'@'localhost' IDENTIFIED BY 'strong_password';
GRANT ALL PRIVILEGES ON atomcms.* TO 'atomcms_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
```
### 4.2 Run Migrations
```bash
php artisan migrate --seed
```
---
## 🔗 Step 5: Storage Link (Crucial!)
The system uses a **Bulletproof Middleware (`SystemCheck`)** that attempts to create the storage symlink automatically on the first request.
**Manual Fallback (if auto-fails):**
_Linux:_
```bash
php artisan storage:link
sudo chmod -R 755 storage bootstrap/cache
```
_Windows (CMD as Admin):_
```cmd
mklink /J "C:\path\to\atomcms-edit\public\storage" "C:\path\to\atomcms-edit\storage\app\public"
```
---
## 🌐 Step 6: Web Server Configuration
### Option A: PHP Built-in (Testing)
```bash
php artisan serve --host=0.0.0.0 --port=8000
```
Visit: `http://localhost:8000`
### Option B: Nginx (Linux Production)
Edit `/etc/nginx/sites-available/atomcms`:
```nginx
server {
listen 80;
server_name epicnabbo.nl; # Your domain
root /var/www/atomcms/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}
```
Enable: `sudo ln -s /etc/nginx/sites-available/atomcms /etc/nginx/sites-enabled/ && sudo systemctl restart nginx`
### Option C: Windows IIS
1. Open **IIS Manager** -> **Sites** -> **Add Website**.
2. Physical Path: `C:\inetpub\wwwroot\atomcms-edit\public`
3. Install **URL Rewrite Module**.
4. Add `web.config` in `public/`:
```xml
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Laravel Routes" stopProcessing="true">
<match url=".*" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
</conditions>
<action type="Rewrite" url="index.php" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
```
---
## 🛡️ Step 7: Production Optimization
```bash
php artisan config:cache
php artisan route:cache
php artisan view:cache
```
---
## ✅ Step 8: Verify & Test
1. Open `http://your-domain.com` (or `http://localhost:8000`).
2. Test **Registration** at `/register`.
- _Note:_ v1.0 fixes the "500 Error" on registration (Cloudflare Turnstile & IP fields).
3. Check logs if needed: `tail -f storage/logs/laravel.log`
---
## 🛠️ Troubleshooting (v1.0 Fixes)
### 500 Error on Registration?
**Fixed in v1.0!**
- ✅ Cloudflare Turnstile null-response handled.
-`ip_register` and `ip_current` added to User model.
- ✅ MySQL Strict Mode disabled for Windows/IIS/XAMPP compatibility.
### "Missing .env" or "No Application Key"
The `SystemCheck` middleware (auto-runs on every request) will attempt to:
1. Copy `.env.example` to `.env`.
2. Generate `APP_KEY` automatically.
### Storage Link Issues
The system auto-creates the symlink. If it fails on Windows, ensure you run the server with **Administrator** privileges.
### Database Connection Failed
1. Check `.env` credentials.
2. Ensure MySQL service is running (`systemctl status mysql` or Windows Services).
3. Use `127.0.0.1` instead of `localhost` if socket errors occur.
---
## 🚀 What's new in v1.0 (epicnabbo edit)?
This version is **Bulletproof**. It is designed to work immediately after `composer install` on any environment.
-**Cross-Platform**: Works on Linux (Nginx/Apache), Windows (IIS/XAMPP).
-**Auto-Recovery**: `SystemCheck` middleware fixes missing `.env`, `APP_KEY`, and storage links automatically.
-**Zero 500 Errors**: Registration bugs (Turnstile & IP fields) fixed.
-**MySQL Strict Mode**: Disabled for maximum compatibility.
-**Windows Path Fix**: `index.php` handles Windows backslash paths.
---
**© 2026 RemcoEpic - epicnabbo.nl**
+1 -3
View File
@@ -171,13 +171,11 @@ class CreateNewUser implements CreatesNewUsers
try { try {
Http::asJson()->post(is_string($discordWebhookUrl) ? $discordWebhookUrl : '', [ Http::asJson()->post(is_string($discordWebhookUrl) ? $discordWebhookUrl : '', [
'username' => sprintf('%s Bot', is_string($hotelNameSetting) ? $hotelNameSetting : 'Hotel'), 'username' => sprintf('%s Bot', is_string($hotelNameSetting) ? $hotelNameSetting : 'Hotel'),
'content' => "User: {$username} has just registered, with the IP: {$ip} and E-mail: {$email}", 'content' => "User: {$username} has just registered.",
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error('Failed to send Discord webhook notification', [ Log::error('Failed to send Discord webhook notification', [
'username' => $username, 'username' => $username,
'ip' => $ip,
'email' => $email,
'error' => $e->getMessage(), 'error' => $e->getMessage(),
]); ]);
} }
@@ -15,7 +15,7 @@ class DisableTwoFactorAuthentication extends \Laravel\Fortify\Actions\DisableTwo
$user->forceFill([ $user->forceFill([
'two_factor_secret' => null, 'two_factor_secret' => null,
'two_factor_recovery_codes' => null, 'two_factor_recovery_codes' => null,
'two_factor_confirmed' => false, 'two_factor_confirmed_at' => null,
])->save(); ])->save();
} }
} }
+2 -118
View File
@@ -228,68 +228,10 @@ final class Commandocentrum extends Page implements HasForms
Section::make(__('commandocentrum.nitro_update')) Section::make(__('commandocentrum.nitro_update'))
->description(__('commandocentrum.nitro_update_desc')) ->description(__('commandocentrum.nitro_update_desc'))
->icon('heroicon-o-arrow-path') ->icon('heroicon-o-arrow-path')
->afterHeader([
Action::make('configure_nitro')
->label(__('commandocentrum.configure_nitro'))
->icon('heroicon-o-cog-6-tooth')
->color('primary')
->form([
\Filament\Forms\Components\TextInput::make('nitro_emulator_path')
->label(__('commandocentrum.nitro_emulator_path'))
->default(fn () => $this->getSetting('nitro_emulator_path', '/var/www/emulator')),
\Filament\Forms\Components\TextInput::make('nitro_emulator_service')
->label(__('commandocentrum.nitro_emulator_service'))
->default(fn () => $this->getSetting('nitro_emulator_service', 'emulator')),
\Filament\Forms\Components\TextInput::make('nitro_db_name')
->label(__('commandocentrum.nitro_db_name'))
->default(fn () => $this->getSetting('nitro_db_name', 'habbo')),
\Filament\Forms\Components\TextInput::make('nitro_sql_dir')
->label(__('commandocentrum.nitro_sql_dir'))
->default(fn () => $this->getSetting('nitro_sql_dir', '/var/www/emulator/Database Updates')),
\Filament\Forms\Components\TextInput::make('nitro_backup_dir')
->label(__('commandocentrum.nitro_backup_dir'))
->default(fn () => $this->getSetting('nitro_backup_dir', '/var/www/emulator/Database Updates/backups')),
\Filament\Forms\Components\TextInput::make('nitro_gamedata_dir')
->label(__('commandocentrum.nitro_gamedata_dir'))
->default(fn () => $this->getSetting('nitro_gamedata_dir', '/var/www/Gamedata/config')),
\Filament\Forms\Components\TextInput::make('nitro_client_dir')
->label(__('commandocentrum.nitro_client_dir'))
->default(fn () => $this->getSetting('nitro_client_dir', '/var/www/Nitro-V3/public/configuration')),
\Filament\Forms\Components\TextInput::make('nitro_client_src')
->label(__('commandocentrum.nitro_client_src'))
->default(fn () => $this->getSetting('nitro_client_src', '/var/www/Nitro-V3')),
\Filament\Forms\Components\TextInput::make('nitro_renderer_src')
->label(__('commandocentrum.nitro_renderer_src'))
->default(fn () => $this->getSetting('nitro_renderer_src', '/var/www/Nitro_Render_V3')),
])
->action(function (array $data): void {
$settings = app(SettingsService::class);
$settings->set('nitro_emulator_path', $data['nitro_emulator_path'] ?? '/var/www/emulator');
$settings->set('nitro_emulator_service', $data['nitro_emulator_service'] ?? 'emulator');
$settings->set('nitro_db_name', $data['nitro_db_name'] ?? 'habbo');
$settings->set('nitro_sql_dir', $data['nitro_sql_dir'] ?? '/var/www/emulator/Database Updates');
$settings->set('nitro_backup_dir', $data['nitro_backup_dir'] ?? '/var/www/emulator/Database Updates/backups');
$settings->set('nitro_gamedata_dir', $data['nitro_gamedata_dir'] ?? '/var/www/Gamedata/config');
$settings->set('nitro_client_dir', $data['nitro_client_dir'] ?? '/var/www/Nitro-V3/public/configuration');
$settings->set('nitro_client_src', $data['nitro_client_src'] ?? '/var/www/Nitro-V3');
$settings->set('nitro_renderer_src', $data['nitro_renderer_src'] ?? '/var/www/Nitro_Render_V3');
$this->fillForm();
Notification::make()
->title(__('commandocentrum.success'))
->body(__('commandocentrum.nitro_config_saved'))
->color('success')
->send();
}),
Action::make('run_nitro_update')
->label(__('commandocentrum.run_update'))
->icon('heroicon-o-play')
->color('success')
->action('runUpdateNitrov3'),
])
->schema([ ->schema([
Placeholder::make('nitro_config_summary') Placeholder::make('nitro_cli_only')
->label('') ->label('')
->content(fn () => view('filament.components.commandocentrum.nitro-config-summary')), ->content(__('commandocentrum.nitro_cli_only')),
]), ]),
Section::make(__('commandocentrum.clothing_sync')) Section::make(__('commandocentrum.clothing_sync'))
@@ -735,64 +677,6 @@ final class Commandocentrum extends Page implements HasForms
} }
} }
public function runUpdateNitrov3(): void
{
try {
$scriptPath = base_path('update-Nitrov3.sh');
if (! $this->fileExists($scriptPath)) {
$this->notify(__('commandocentrum.error'), 'Script niet gevonden: ' . $scriptPath, 'danger');
return;
}
$dbHost = config('database.connections.mysql.host', '127.0.0.1');
$dbPort = config('database.connections.mysql.port', '3306');
$dbUser = config('database.connections.mysql.username', 'root');
$dbPass = config('database.connections.mysql.password', '');
$env = [
'NITRO_EMULATOR_PATH' => $this->data['nitro_emulator_path'] ?? '/var/www/emulator',
'NITRO_EMULATOR_SERVICE' => $this->data['nitro_emulator_service'] ?? 'emulator',
'NITRO_DB_NAME' => $this->data['nitro_db_name'] ?? 'habbo',
'NITRO_DB_HOST' => $dbHost,
'NITRO_DB_PORT' => $dbPort,
'NITRO_DB_USER' => $dbUser,
'NITRO_DB_PASS' => $dbPass,
'NITRO_SQL_DIR' => $this->data['nitro_sql_dir'] ?? '/var/www/emulator/Database Updates',
'NITRO_BACKUP_DIR' => $this->data['nitro_backup_dir'] ?? '/var/www/emulator/Database Updates/backups',
'NITRO_GAMEDATA_DIR' => $this->data['nitro_gamedata_dir'] ?? '/var/www/Gamedata/config',
'NITRO_CLIENT_DIR' => $this->data['nitro_client_dir'] ?? '/var/www/Nitro-V3/public/configuration',
'NITRO_CLIENT_SRC' => $this->data['nitro_client_src'] ?? '/var/www/Nitro-V3',
'NITRO_RENDERER_SRC' => $this->data['nitro_renderer_src'] ?? '/var/www/Nitro_Render_V3',
];
$envExport = '';
foreach ($env as $key => $value) {
$envExport .= 'export ' . $key . '=' . escapeshellarg($value) . '; ';
}
$result = Process::timeout(600)->run($envExport . 'bash ' . escapeshellarg($scriptPath) . ' 2>&1');
$output = $result->output();
$exitCode = $result->exitCode();
$title = $exitCode === 0 ? __('commandocentrum.success') : __('commandocentrum.error');
$color = $exitCode === 0 ? 'success' : 'danger';
$message = $exitCode === 0 ? '✅ Nitro V3 update voltooid' : '⚠️ Script fout (exit code ' . $exitCode . ')';
$this->notify($title, $message, $color);
Notification::make()
->title(__('commandocentrum.nitro_update_output'))
->body('<pre style="max-height:300px;overflow:auto;font-size:12px;background:#1e293b;color:#e2e8f0;padding:12px;border-radius:8px;">' . e($output) . '</pre>')
->color($color)
->persistent()
->send();
} catch (Exception $e) {
$this->notify(__('commandocentrum.error'), $e->getMessage(), 'danger');
}
}
public function saveEmulator(): void public function saveEmulator(): void
{ {
try { try {
+13 -18
View File
@@ -6,7 +6,6 @@ namespace App\Filament\Pages\Radio;
use App\Models\RadioApiKey; use App\Models\RadioApiKey;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle; use Filament\Forms\Components\Toggle;
@@ -82,23 +81,19 @@ final class ApiKeys extends Page implements HasTable
->label('Aangemaakt') ->label('Aangemaakt')
->dateTime('d-m-Y H:i'), ->dateTime('d-m-Y H:i'),
]) ])
->recordActions(function ($record) { ->actions([
return [ Action::make('toggle')
ActionGroup::make([ ->label(fn ($record) => $record->is_active ? 'Deactiveren' : 'Activeren')
Action::make('toggle') ->icon(fn ($record) => $record->is_active ? 'heroicon-o-pause' : 'heroicon-o-play')
->label($record->is_active ? 'Deactiveren' : 'Activeren') ->action(fn ($record) => $this->toggleKey($record)),
->icon($record->is_active ? 'heroicon-o-pause' : 'heroicon-o-play') Action::make('delete')
->action(fn () => $this->toggleKey($record)), ->label('Verwijderen')
Action::make('delete') ->icon('heroicon-o-trash')
->label('Verwijderen') ->color('danger')
->icon('heroicon-o-trash') ->requiresConfirmation()
->color('danger') ->action(fn ($record) => $record->delete()),
->requiresConfirmation() ])
->action(fn () => $record->delete()), ->headerActions([
]),
];
})
->toolbarActions([
Action::make('create') Action::make('create')
->label('Nieuwe API Sleutel') ->label('Nieuwe API Sleutel')
->icon('heroicon-o-plus') ->icon('heroicon-o-plus')
+21 -26
View File
@@ -6,7 +6,6 @@ namespace App\Filament\Pages\Radio;
use App\Models\RadioAutoDjTrack; use App\Models\RadioAutoDjTrack;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle; use Filament\Forms\Components\Toggle;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
@@ -74,31 +73,27 @@ final class AutoDjPlaylist extends Page implements HasTable
->trueColor('success') ->trueColor('success')
->falseColor('danger'), ->falseColor('danger'),
]) ])
->recordActions(function ($record) { ->actions([
return [ Action::make('toggle_active')
ActionGroup::make([ ->label(fn ($record) => $record->is_active ? 'Deactiveren' : 'Activeren')
Action::make('toggle_active') ->icon(fn ($record) => $record->is_active ? 'heroicon-o-pause' : 'heroicon-o-play')
->label($record->is_active ? 'Deactiveren' : 'Activeren') ->action(fn ($record) => $this->toggleActive($record)),
->icon($record->is_active ? 'heroicon-o-pause' : 'heroicon-o-play') Action::make('move_up')
->action(fn () => $this->toggleActive($record)), ->label('Omhoog')
Action::make('move_up') ->icon('heroicon-o-chevron-up')
->label('Omhoog') ->action(fn ($record) => $this->moveUp($record)),
->icon('heroicon-o-chevron-up') Action::make('move_down')
->action(fn () => $this->moveUp($record)), ->label('Omlaag')
Action::make('move_down') ->icon('heroicon-o-chevron-down')
->label('Omlaag') ->action(fn ($record) => $this->moveDown($record)),
->icon('heroicon-o-chevron-down') Action::make('delete')
->action(fn () => $this->moveDown($record)), ->label('Verwijderen')
Action::make('delete') ->icon('heroicon-o-trash')
->label('Verwijderen') ->color('danger')
->icon('heroicon-o-trash') ->requiresConfirmation()
->color('danger') ->action(fn ($record) => $record->delete()),
->requiresConfirmation() ])
->action(fn () => $record->delete()), ->headerActions([
]),
];
})
->toolbarActions([
Action::make('create') Action::make('create')
->label('Track Toevoegen') ->label('Track Toevoegen')
->icon('heroicon-o-plus') ->icon('heroicon-o-plus')
+1
View File
@@ -50,6 +50,7 @@ final class EmbedCode extends Page implements HasForms
public function form(Schema $schema): Schema public function form(Schema $schema): Schema
{ {
return $schema return $schema
->statePath('data')
->components([ ->components([
Section::make('Embed Configuratie') Section::make('Embed Configuratie')
->description('Pas het uiterlijk van de embed player aan') ->description('Pas het uiterlijk van de embed player aan')
+1 -1
View File
@@ -196,7 +196,7 @@ final class PointsSettings extends Page implements HasForms
public function resetLeaderboard(): void public function resetLeaderboard(): void
{ {
User::where('radio_points', '>', 0)->update(['radio_points' => 0]); User::query()->where('radio_points', '>', 0)->each(fn (User $u) => $u->forceFill(['radio_points' => 0])->save());
RadioListenerPoint::query()->delete(); RadioListenerPoint::query()->delete();
$this->pointsService->clearLeaderboardCache(); $this->pointsService->clearLeaderboardCache();
@@ -129,7 +129,7 @@ class StaffApplicationResource extends Resource
} }
if ((int) $user->team_id !== (int) $team->id) { if ((int) $user->team_id !== (int) $team->id) {
$user->update(['team_id' => $team->id]); $user->forceFill(['team_id' => $team->id])->save();
} }
$r->update([ $r->update([
@@ -177,7 +177,7 @@ class StaffApplicationResource extends Resource
} }
if ($r->status === 'approved' && (int) $user->team_id === (int) $team->id) { if ($r->status === 'approved' && (int) $user->team_id === (int) $team->id) {
$user->update(['team_id' => null]); $user->forceFill(['team_id' => null])->save();
} }
$r->update([ $r->update([
@@ -180,7 +180,7 @@ class RadioApplicationResource extends Resource
]) ])
->label('Status'), ->label('Status'),
]) ])
->recordActions([ ->actions([
Action::make('approve') Action::make('approve')
->label('Goedkeuren') ->label('Goedkeuren')
->icon('heroicon-o-check-circle') ->icon('heroicon-o-check-circle')
@@ -123,11 +123,11 @@ class RadioBannerResource extends Resource
->label('Actief') ->label('Actief')
->boolean(), ->boolean(),
]) ])
->recordActions([ ->actions([
EditAction::make(), EditAction::make(),
DeleteAction::make(), DeleteAction::make(),
]) ])
->toolbarActions([ ->headerActions([
DeleteBulkAction::make(), DeleteBulkAction::make(),
]); ]);
} }
@@ -115,11 +115,11 @@ class RadioHistoryResource extends Resource
TextColumn::make('listeners_count') TextColumn::make('listeners_count')
->label('Luisteraars'), ->label('Luisteraars'),
]) ])
->recordActions([ ->actions([
EditAction::make(), EditAction::make(),
DeleteAction::make(), DeleteAction::make(),
]) ])
->toolbarActions([ ->headerActions([
DeleteBulkAction::make(), DeleteBulkAction::make(),
]); ]);
} }
@@ -120,10 +120,10 @@ class RadioRankResource extends Resource
->filters([ ->filters([
// //
]) ])
->recordActions([ ->actions([
EditAction::make(), EditAction::make(),
]) ])
->toolbarActions([ ->headerActions([
DeleteBulkAction::make(), DeleteBulkAction::make(),
]); ]);
} }
@@ -142,11 +142,11 @@ class RadioScheduleResource extends Resource
]) ])
->label('Dag'), ->label('Dag'),
]) ])
->recordActions([ ->actions([
EditAction::make(), EditAction::make(),
DeleteAction::make(), DeleteAction::make(),
]) ])
->toolbarActions([ ->headerActions([
DeleteBulkAction::make(), DeleteBulkAction::make(),
]); ]);
} }
@@ -102,7 +102,7 @@ class RadioShoutResource extends Resource
->filters([ ->filters([
// //
]) ])
->recordActions([ ->actions([
Action::make('report') Action::make('report')
->label('Melden') ->label('Melden')
->icon('heroicon-o-flag') ->icon('heroicon-o-flag')
@@ -76,7 +76,7 @@ class RadioSongPlayResource extends Resource
->options(fn () => RadioSongPlay::distinct()->whereNotNull('artist')->pluck('artist', 'artist')->toArray()) ->options(fn () => RadioSongPlay::distinct()->whereNotNull('artist')->pluck('artist', 'artist')->toArray())
->searchable(), ->searchable(),
]) ])
->recordActions([ ->actions([
DeleteAction::make() DeleteAction::make()
->label('Verwijderen'), ->label('Verwijderen'),
]) ])
@@ -189,7 +189,7 @@ class EditUser extends EditRecord
} }
if (! $user->online) { if (! $user->online) {
$user->update(['rank' => $data['rank']]); $user->forceFill(['rank' => $data['rank']])->save();
return; return;
} }
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\Api\ArticleResource;
use App\Models\Articles\WebsiteArticle;
use Illuminate\Http\JsonResponse;
class ArticleApiController extends Controller
{
public function index(): JsonResponse
{
$articles = WebsiteArticle::with(['user:id,username,look'])
->latest('id')
->paginate(12);
return response()->json([
'data' => ArticleResource::collection($articles),
'meta' => [
'current_page' => $articles->currentPage(),
'last_page' => $articles->lastPage(),
'per_page' => $articles->perPage(),
'total' => $articles->total(),
],
]);
}
public function show(string $slug): JsonResponse
{
$article = WebsiteArticle::with(['user:id,username,look', 'comments.user:id,username,look'])
->where('slug', $slug)
->firstOrFail();
return response()->json(['data' => new ArticleResource($article)]);
}
}
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Game\Furniture\CatalogItem;
use App\Models\Game\Furniture\CatalogPage;
use App\Models\Game\Guild\Guild;
use App\Models\Miscellaneous\WebsiteSetting;
use App\Services\Community\StaffService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class ContentApiController extends Controller
{
public function __construct(
private readonly StaffService $staffService,
) {}
public function settings(): JsonResponse
{
$settings = Cache::remember('api_all_settings', 60, fn () => WebsiteSetting::all()->pluck('value', 'key'));
return response()->json(['data' => $settings]);
}
public function staff(): JsonResponse
{
return response()->json(['data' => $this->staffService->fetchStaffPositions()]);
}
public function teams(): JsonResponse
{
$teams = Guild::with('members')->latest('id')->paginate(12);
return response()->json([
'data' => $teams->items(),
'meta' => [
'current_page' => $teams->currentPage(),
'last_page' => $teams->lastPage(),
'per_page' => $teams->perPage(),
'total' => $teams->total(),
],
]);
}
public function rareValues(Request $request): JsonResponse
{
$query = CatalogItem::query();
if ($categoryId = $request->query('category')) {
$query->where('page_id', $categoryId);
}
$items = $query->with('itemBase')->latest('id')->paginate(24);
return response()->json([
'data' => $items->items(),
'meta' => [
'current_page' => $items->currentPage(),
'last_page' => $items->lastPage(),
'per_page' => $items->perPage(),
'total' => $items->total(),
],
]);
}
public function rareValuesCategories(): JsonResponse
{
$categories = CatalogPage::where('catalog_name', '!=', '')
->where('visible', 1)
->orderBy('order_number')
->get(['id', 'catalog_name', 'icon']);
return response()->json(['data' => $categories]);
}
}
@@ -1,12 +1,16 @@
<?php <?php
declare(strict_types=1);
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Game\Furniture\CatalogItem;
use App\Models\Game\Furniture\CatalogPage;
use App\Models\Game\Furniture\ItemBase;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
class FurniEditorController extends Controller class FurniEditorController extends Controller
@@ -25,10 +29,10 @@ class FurniEditorController extends Controller
'user_id' => Auth::id(), 'user_id' => Auth::id(),
]); ]);
return response()->json(['error' => "{$action} mislukt"], 500); return response()->json(['error' => "{$action} failed"], 500);
} }
private function formatItemData(\stdClass $item): array private function formatItemData(ItemBase $item): array
{ {
return [ return [
'id' => $item->id, 'id' => $item->id,
@@ -59,17 +63,17 @@ class FurniEditorController extends Controller
]; ];
} }
private function formatCatalogItemData(\stdClass $r): array private function formatCatalogItemData(CatalogItem $item): array
{ {
return [ return [
'id' => $r->id, 'id' => $item->id,
'catalogName' => $r->catalog_name, 'catalogName' => $item->catalog_name,
'costCredits' => $r->cost_credits, 'costCredits' => $item->cost_credits,
'costPoints' => $r->cost_points, 'costPoints' => $item->cost_points,
'pointsType' => $r->points_type, 'pointsType' => $item->points_type,
'pageId' => $r->page_id, 'pageId' => $item->page_id,
'pageName' => $r->page_id 'pageName' => $item->page_id
? DB::table('catalog_pages')->where('id', $r->page_id)->value('caption') ?? '' ? CatalogPage::where('id', $item->page_id)->value('caption') ?? ''
: '', : '',
]; ];
} }
@@ -84,7 +88,7 @@ class FurniEditorController extends Controller
$page = max(1, (int) $request->input('page', 1)); $page = max(1, (int) $request->input('page', 1));
$limit = min(50, max(1, (int) $request->input('limit', 20))); $limit = min(50, max(1, (int) $request->input('limit', 20)));
$query = DB::table('items_base'); $query = ItemBase::query();
if ($q !== '' && $q !== '0') { if ($q !== '' && $q !== '0') {
$query->where(function ($w) use ($q) { $query->where(function ($w) use ($q) {
@@ -105,7 +109,7 @@ class FurniEditorController extends Controller
->skip(($page - 1) * $limit) ->skip(($page - 1) * $limit)
->take($limit) ->take($limit)
->get() ->get()
->map(fn ($r) => [ ->map(fn (ItemBase $r) => [
'id' => $r->id, 'id' => $r->id,
'spriteId' => $r->sprite_id, 'spriteId' => $r->sprite_id,
'itemName' => $r->item_name, 'itemName' => $r->item_name,
@@ -128,7 +132,7 @@ class FurniEditorController extends Controller
'page' => $page, 'page' => $page,
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
return $this->handleApiError('Zoeken', $e); return $this->handleApiError('Search', $e);
} }
} }
@@ -142,15 +146,14 @@ class FurniEditorController extends Controller
return response()->json(['error' => 'Missing id'], 400); return response()->json(['error' => 'Missing id'], 400);
} }
$item = DB::table('items_base')->where('id', $id)->first(); $item = ItemBase::where('id', $id)->first();
if (! $item) { if (! $item) {
return response()->json(['error' => 'Item not found'], 404); return response()->json(['error' => 'Item not found'], 404);
} }
$catalog = DB::table('catalog_items') $catalog = CatalogItem::whereRaw('FIND_IN_SET(?, item_ids)', [$id])
->whereRaw('FIND_IN_SET(?, item_ids)', [$id])
->get() ->get()
->map(fn ($r) => $this->formatCatalogItemData($r)); ->map(fn (CatalogItem $r) => $this->formatCatalogItemData($r));
return response()->json([ return response()->json([
'item' => array_merge($this->formatItemData($item), [ 'item' => array_merge($this->formatItemData($item), [
@@ -161,7 +164,7 @@ class FurniEditorController extends Controller
'furniDataEntry' => null, 'furniDataEntry' => null,
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
return $this->handleApiError('Item laden', $e); return $this->handleApiError('Load item', $e);
} }
} }
@@ -182,7 +185,7 @@ class FurniEditorController extends Controller
return response()->json(['error' => 'No fields to update'], 400); return response()->json(['error' => 'No fields to update'], 400);
} }
DB::table('items_base')->where('id', $id)->update($update); ItemBase::where('id', $id)->update($update);
Log::info('[Audit] FurniEditor update', [ Log::info('[Audit] FurniEditor update', [
'user_id' => Auth::id(), 'user_id' => Auth::id(),
@@ -192,7 +195,7 @@ class FurniEditorController extends Controller
return response()->json(['success' => true]); return response()->json(['success' => true]);
} catch (\Exception $e) { } catch (\Exception $e) {
return $this->handleApiError('Actie', $e); return $this->handleApiError('Update', $e);
} }
} }
@@ -203,7 +206,7 @@ class FurniEditorController extends Controller
try { try {
$data = $request->json()->all(); $data = $request->json()->all();
$id = DB::table('items_base')->insertGetId($this->buildInsertData($data)); $id = ItemBase::insertGetId($this->buildInsertData($data));
Log::info('[Audit] FurniEditor create', [ Log::info('[Audit] FurniEditor create', [
'user_id' => Auth::id(), 'user_id' => Auth::id(),
@@ -212,7 +215,7 @@ class FurniEditorController extends Controller
return response()->json(['id' => $id]); return response()->json(['id' => $id]);
} catch (\Exception $e) { } catch (\Exception $e) {
return $this->handleApiError('Actie', $e); return $this->handleApiError('Create', $e);
} }
} }
@@ -226,7 +229,7 @@ class FurniEditorController extends Controller
return response()->json(['error' => 'Missing id'], 400); return response()->json(['error' => 'Missing id'], 400);
} }
DB::table('items_base')->where('id', $id)->delete(); ItemBase::where('id', $id)->delete();
Log::warning('[Audit] FurniEditor delete', [ Log::warning('[Audit] FurniEditor delete', [
'user_id' => Auth::id(), 'user_id' => Auth::id(),
@@ -235,7 +238,7 @@ class FurniEditorController extends Controller
return response()->json(['success' => true]); return response()->json(['success' => true]);
} catch (\Exception $e) { } catch (\Exception $e) {
return $this->handleApiError('Actie', $e); return $this->handleApiError('Delete', $e);
} }
} }
@@ -244,7 +247,7 @@ class FurniEditorController extends Controller
$this->checkAdmin(); $this->checkAdmin();
try { try {
$rows = DB::table('items_base') $rows = ItemBase::query()
->select('interaction_type') ->select('interaction_type')
->groupBy('interaction_type') ->groupBy('interaction_type')
->orderBy('interaction_type') ->orderBy('interaction_type')
@@ -252,7 +255,7 @@ class FurniEditorController extends Controller
return response()->json(['interactions' => $rows]); return response()->json(['interactions' => $rows]);
} catch (\Exception $e) { } catch (\Exception $e) {
return $this->handleApiError('Actie', $e); return $this->handleApiError('Interactions', $e);
} }
} }
@@ -266,14 +269,14 @@ class FurniEditorController extends Controller
return response()->json(['error' => 'Missing spriteId'], 400); return response()->json(['error' => 'Missing spriteId'], 400);
} }
$item = DB::table('items_base')->where('sprite_id', $spriteId)->first(); $item = ItemBase::where('sprite_id', $spriteId)->first();
if (! $item) { if (! $item) {
return response()->json(['error' => 'Item not found'], 404); return response()->json(['error' => 'Item not found'], 404);
} }
return response()->json(['id' => $item->id]); return response()->json(['id' => $item->id]);
} catch (\Exception $e) { } catch (\Exception $e) {
return $this->handleApiError('Actie', $e); return $this->handleApiError('By sprite', $e);
} }
} }
@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\HelpTicketReplyRequest;
use App\Http\Requests\Api\HelpTicketRequest;
use App\Http\Resources\Api\HelpTicketResource;
use App\Models\Help\WebsiteHelpCenterTicket;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class HelpApiController extends Controller
{
public function tickets(Request $request): JsonResponse
{
$tickets = WebsiteHelpCenterTicket::with('user:id,username,look')
->when($request->user(), fn ($q) => $q->where('user_id', $request->user()->id))
->latest()
->paginate(10);
return response()->json([
'data' => HelpTicketResource::collection($tickets),
'meta' => [
'current_page' => $tickets->currentPage(),
'last_page' => $tickets->lastPage(),
'total' => $tickets->total(),
],
]);
}
public function show(string $id): JsonResponse
{
$ticket = WebsiteHelpCenterTicket::with(['user:id,username,look', 'replies.user:id,username,look'])
->where('id', $id)
->firstOrFail();
return response()->json(['data' => new HelpTicketResource($ticket)]);
}
public function create(HelpTicketRequest $request): JsonResponse
{
$validated = $request->validated();
$ticket = WebsiteHelpCenterTicket::create([
'user_id' => $request->user()->id,
'subject' => $validated['subject'],
'category' => $validated['category'],
'status' => 'open',
]);
$ticket->replies()->create([
'user_id' => $request->user()->id,
'message' => $validated['message'],
]);
return response()->json(['data' => new HelpTicketResource($ticket)], 201);
}
public function reply(HelpTicketReplyRequest $request, string $id): JsonResponse
{
$ticket = WebsiteHelpCenterTicket::where('id', $id)
->where('user_id', $request->user()->id)
->firstOrFail();
$validated = $request->validated();
$reply = $ticket->replies()->create([
'user_id' => $request->user()->id,
'message' => $validated['message'],
]);
return response()->json(['data' => $reply->load('user:id,username,look')], 201);
}
}
@@ -1,330 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\HelpTicketReplyRequest;
use App\Http\Requests\Api\HelpTicketRequest;
use App\Http\Requests\Api\PhotoUploadRequest;
use App\Http\Resources\Api\ArticleResource;
use App\Http\Resources\Api\HelpTicketResource;
use App\Http\Resources\Api\LeaderboardUserResource;
use App\Http\Resources\Api\PhotoResource;
use App\Http\Resources\Api\ShopPackageResource;
use App\Models\Articles\WebsiteArticle;
use App\Models\Game\Furniture\CatalogItem;
use App\Models\Game\Furniture\CatalogPage;
use App\Models\Game\Guild\Guild;
use App\Models\Help\WebsiteHelpCenterTicket;
use App\Models\Miscellaneous\CameraWeb;
use App\Models\Miscellaneous\WebsiteSetting;
use App\Models\Shop\WebsiteShopArticle;
use App\Models\User;
use App\Services\Community\StaffService;
use App\Services\User\UserApiService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class HotelApiController extends Controller
{
public function __construct(
private readonly UserApiService $userApiService,
private readonly StaffService $staffService,
) {}
public function fetchUser(string $username): JsonResponse
{
$user = $this->userApiService->fetchUser($username, ['username', 'motto', 'look']);
return response()->json([
'data' => $user,
]);
}
public function onlineUsers(): JsonResponse
{
$users = $this->userApiService->onlineUsers(['username', 'motto', 'look'], true);
return response()->json([
'data' => $users,
]);
}
public function onlineUserCount(): JsonResponse
{
$count = $this->userApiService->onlineUserCount();
return response()->json([
'data' => [
'onlineCount' => $count,
],
]);
}
public function articles(): JsonResponse
{
$articles = WebsiteArticle::with(['user:id,username,look'])
->latest('id')
->paginate(12);
return response()->json([
'data' => ArticleResource::collection($articles),
'meta' => [
'current_page' => $articles->currentPage(),
'last_page' => $articles->lastPage(),
'per_page' => $articles->perPage(),
'total' => $articles->total(),
],
]);
}
public function article(string $slug): JsonResponse
{
$article = WebsiteArticle::with(['user:id,username,look', 'comments.user:id,username,look'])
->where('slug', $slug)
->firstOrFail();
return response()->json([
'data' => new ArticleResource($article),
]);
}
public function photos(): JsonResponse
{
$photos = CameraWeb::query()
->where('visible', true)
->latest('id')
->paginate(12);
return response()->json([
'data' => PhotoResource::collection($photos),
'meta' => [
'current_page' => $photos->currentPage(),
'last_page' => $photos->lastPage(),
'per_page' => $photos->perPage(),
'total' => $photos->total(),
],
]);
}
public function staff(): JsonResponse
{
$employees = $this->staffService->fetchStaffPositions();
return response()->json([
'data' => $employees,
]);
}
public function shopPackages(Request $request): JsonResponse
{
$packages = WebsiteShopArticle::latest('id')->paginate(12);
return response()->json([
'data' => ShopPackageResource::collection($packages),
'meta' => [
'current_page' => $packages->currentPage(),
'last_page' => $packages->lastPage(),
'per_page' => $packages->perPage(),
'total' => $packages->total(),
],
]);
}
public function shopCategories(): JsonResponse
{
return response()->json([
'data' => ['furniture', 'badges', 'ranks'],
]);
}
public function teams(): JsonResponse
{
$teams = Guild::with('members')
->latest('id')
->paginate(12);
return response()->json([
'data' => $teams->items(),
'meta' => [
'current_page' => $teams->currentPage(),
'last_page' => $teams->lastPage(),
'per_page' => $teams->perPage(),
'total' => $teams->total(),
],
]);
}
public function leaderboard(Request $request): JsonResponse
{
$type = $request->query('type', 'credits');
$limit = (int) $request->query('limit', 100);
$validTypes = ['credits', 'pixels'];
if (! in_array($type, $validTypes)) {
$type = 'credits';
}
$users = User::orderByDesc($type)
->limit($limit)
->get(['id', 'username', 'look', 'motto', 'credits', 'pixels']);
return response()->json([
'data' => LeaderboardUserResource::collection($users),
'type' => $type,
]);
}
public function rareValues(Request $request): JsonResponse
{
$categoryId = $request->query('category');
$query = CatalogItem::query();
if ($categoryId) {
$query->where('page_id', $categoryId);
}
$items = $query->with('itemBase')
->latest('id')
->paginate(24);
return response()->json([
'data' => $items->items(),
'meta' => [
'current_page' => $items->currentPage(),
'last_page' => $items->lastPage(),
'per_page' => $items->perPage(),
'total' => $items->total(),
],
]);
}
public function rareValuesCategories(): JsonResponse
{
$categories = CatalogPage::where('catalog_name', '!=', '')
->where('visible', 1)
->orderBy('order_number')
->get(['id', 'catalog_name', 'icon']);
return response()->json([
'data' => $categories,
]);
}
public function settings(): JsonResponse
{
$settings = Cache::remember('api_all_settings', 60, fn () => WebsiteSetting::all()->pluck('value', 'key'));
return response()->json([
'data' => $settings,
]);
}
public function userProfile(string $username): JsonResponse
{
$user = User::where('username', $username)
->firstOrFail();
return response()->json([
'id' => $user->id,
'username' => $user->username,
'look' => $user->look,
'motto' => $user->motto,
'account_created' => $user->account_created,
'online' => false,
]);
}
public function helpTickets(Request $request): JsonResponse
{
$tickets = WebsiteHelpCenterTicket::with('user:id,username,look')
->when($request->user(), fn ($q) => $q->where('user_id', $request->user()->id))
->latest()
->paginate(10);
return response()->json([
'data' => HelpTicketResource::collection($tickets),
'meta' => [
'current_page' => $tickets->currentPage(),
'last_page' => $tickets->lastPage(),
'total' => $tickets->total(),
],
]);
}
public function helpTicket(string $id): JsonResponse
{
$ticket = WebsiteHelpCenterTicket::with(['user:id,username,look', 'replies.user:id,username,look'])
->where('id', $id)
->firstOrFail();
return response()->json(['data' => new HelpTicketResource($ticket)]);
}
public function helpTicketCreate(HelpTicketRequest $request): JsonResponse
{
$validated = $request->validated();
$ticket = WebsiteHelpCenterTicket::create([
'user_id' => $request->user()->id,
'subject' => $validated['subject'],
'category' => $validated['category'],
'status' => 'open',
]);
$ticket->replies()->create([
'user_id' => $request->user()->id,
'message' => $validated['message'],
]);
return response()->json(['data' => new HelpTicketResource($ticket)], 201);
}
public function helpTicketReply(HelpTicketReplyRequest $request, string $id): JsonResponse
{
$ticket = WebsiteHelpCenterTicket::where('id', $id)
->where('user_id', $request->user()->id)
->firstOrFail();
$reply = $ticket->replies()->create([
'user_id' => $request->user()->id,
'message' => $request->input('message'),
]);
return response()->json(['data' => $reply->load('user:id,username,look')], 201);
}
public function uploadPhoto(PhotoUploadRequest $request): JsonResponse
{
$path = $request->file('image')->store('photos', 'public');
$photo = CameraWeb::create([
'user_id' => $request->user()->id,
'image' => '/storage/' . $path,
'visible' => true,
]);
return response()->json(['data' => new PhotoResource($photo)], 201);
}
public function purchasePackage(Request $request, int $packageId): JsonResponse
{
$package = WebsiteShopArticle::findOrFail($packageId);
$user = $request->user();
$cost = $package->costs;
if ($user->credits < $cost) {
return response()->json(['error' => 'Not enough credits'], 400);
}
$user->decrement('credits', $cost);
return response()->json(['success' => true, 'message' => 'Purchase successful']);
}
}
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\PhotoUploadRequest;
use App\Http\Resources\Api\PhotoResource;
use App\Models\Miscellaneous\CameraWeb;
use Illuminate\Http\JsonResponse;
class MediaApiController extends Controller
{
public function photos(): JsonResponse
{
$photos = CameraWeb::query()
->where('visible', true)
->latest('id')
->paginate(12);
return response()->json([
'data' => PhotoResource::collection($photos),
'meta' => [
'current_page' => $photos->currentPage(),
'last_page' => $photos->lastPage(),
'per_page' => $photos->perPage(),
'total' => $photos->total(),
],
]);
}
public function upload(PhotoUploadRequest $request): JsonResponse
{
$path = $request->file('image')->store('photos', 'public');
$photo = CameraWeb::create([
'user_id' => $request->user()->id,
'image' => '/storage/' . $path,
'visible' => true,
]);
return response()->json(['data' => new PhotoResource($photo)], 201);
}
}
@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\Api\ShopPackageResource;
use App\Models\Shop\WebsiteShopArticle;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ShopApiController extends Controller
{
public function packages(): JsonResponse
{
$packages = WebsiteShopArticle::latest('id')->paginate(12);
return response()->json([
'data' => ShopPackageResource::collection($packages),
'meta' => [
'current_page' => $packages->currentPage(),
'last_page' => $packages->lastPage(),
'per_page' => $packages->perPage(),
'total' => $packages->total(),
],
]);
}
public function categories(): JsonResponse
{
return response()->json([
'data' => ['furniture', 'badges', 'ranks'],
]);
}
public function purchase(Request $request, int $packageId): JsonResponse
{
$package = WebsiteShopArticle::findOrFail($packageId);
$user = $request->user();
if ($package->give_rank && $user->rank >= $package->give_rank) {
return response()->json(['error' => 'You already have this or a higher rank'], 400);
}
if ($user->credits < $package->costs) {
return response()->json(['error' => 'Not enough credits'], 400);
}
$user->decrement('credits', $package->costs);
return response()->json(['success' => true, 'message' => 'Purchase successful']);
}
}
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\Api\LeaderboardUserResource;
use App\Models\User;
use App\Services\User\UserApiService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class UserApiController extends Controller
{
public function __construct(
private readonly UserApiService $userApiService,
) {}
public function fetchUser(string $username): JsonResponse
{
$user = $this->userApiService->fetchUser($username, ['username', 'motto', 'look']);
return response()->json(['data' => $user]);
}
public function userProfile(string $username): JsonResponse
{
$user = User::where('username', $username)->first();
if (! $user) {
return response()->json(['data' => null], 404);
}
return response()->json([
'data' => [
'id' => $user->id,
'username' => $user->username,
'look' => $user->look,
'motto' => $user->motto,
'account_created' => $user->account_created,
'online' => false,
],
]);
}
public function onlineUsers(): JsonResponse
{
$users = $this->userApiService->onlineUsers(['username', 'motto', 'look'], true);
return response()->json(['data' => $users]);
}
public function onlineUserCount(): JsonResponse
{
return response()->json([
'data' => ['onlineCount' => $this->userApiService->onlineUserCount()],
]);
}
public function leaderboard(Request $request): JsonResponse
{
$type = $request->query('type', 'credits');
$limit = (int) $request->query('limit', 100);
if (! in_array($type, ['credits', 'pixels'])) {
$type = 'credits';
}
$users = User::orderByDesc($type)
->limit($limit)
->get(['id', 'username', 'look', 'motto', 'credits', 'pixels']);
return response()->json([
'data' => LeaderboardUserResource::collection($users),
'type' => $type,
]);
}
}
@@ -12,7 +12,7 @@ class BadgeController extends Controller
{ {
public function show(): View public function show(): View
{ {
$cost = 150; $cost = (int) setting('badge_cost', 150);
$currencyType = 'credits'; $currencyType = 'credits';
$folderError = false; $folderError = false;
$errorMessage = ''; $errorMessage = '';
@@ -60,7 +60,7 @@ class BadgeController extends Controller
return redirect()->route('login')->with('error', 'You must be logged in to purchase badges.'); return redirect()->route('login')->with('error', 'You must be logged in to purchase badges.');
} }
$cost = 150; $cost = (int) setting('badge_cost', 150);
if (property_exists($user, 'credits') && $user->credits !== null && $user->credits < $cost) { if (property_exists($user, 'credits') && $user->credits !== null && $user->credits < $cost) {
return redirect()->back()->with('error', 'You don\'t have enough credits to purchase a badge.'); return redirect()->back()->with('error', 'You don\'t have enough credits to purchase a badge.');
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Community;
use App\Enums\RadioSettings; use App\Enums\RadioSettings;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Controllers\Concerns\HasRadioSettings;
use App\Models\Miscellaneous\WebsiteSetting; use App\Models\Miscellaneous\WebsiteSetting;
use App\Models\RadioApplication; use App\Models\RadioApplication;
use App\Models\RadioBanner; use App\Models\RadioBanner;
@@ -20,6 +21,7 @@ use Illuminate\View\View;
class RadioController extends Controller class RadioController extends Controller
{ {
use HasRadioSettings;
public function __construct( public function __construct(
private readonly RadioStreamService $streamService, private readonly RadioStreamService $streamService,
private readonly RadioScheduleService $scheduleService, private readonly RadioScheduleService $scheduleService,
@@ -120,7 +122,8 @@ class RadioController extends Controller
]); ]);
if ($validated['rank_id']) { if ($validated['rank_id']) {
$rank = Cache::remember("radio_rank_{$validated['rank_id']}", 300, fn () => RadioRank::find($validated['rank_id'])); $acceptingRanks = Cache::remember('radio_ranks_accepting', 300, fn () => RadioRank::where('accepts_applications', true)->get()->keyBy('id'));
$rank = $acceptingRanks->get((int) $validated['rank_id']);
if (! $rank || ! $rank->accepts_applications) { if (! $rank || ! $rank->accepts_applications) {
return back()->withErrors([ return back()->withErrors([
@@ -336,13 +339,4 @@ class RadioController extends Controller
return WebsiteSetting::whereIn('key', $stringKeys)->pluck('value', 'key')->all(); return WebsiteSetting::whereIn('key', $stringKeys)->pluck('value', 'key')->all();
}); });
} }
private function getSetting(RadioSettings $setting, mixed $default = null): mixed
{
return Cache::remember("setting_{$setting->value}", 60, function () use ($setting, $default): mixed {
$websiteSetting = WebsiteSetting::where('key', $setting->value)->first();
return $websiteSetting?->value ?? $default;
});
}
} }
+23
View File
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Concerns;
use App\Enums\RadioSettings;
use App\Models\Miscellaneous\WebsiteSetting;
use Illuminate\Support\Facades\Cache;
trait HasRadioSettings
{
private function getSetting(RadioSettings|string $key, mixed $default = null): mixed
{
$keyStr = $key instanceof RadioSettings ? $key->value : $key;
return Cache::remember("setting_{$keyStr}", 60, function () use ($keyStr, $default): mixed {
$setting = WebsiteSetting::where('key', $keyStr)->first();
return $setting?->value ?? $default;
});
}
}
@@ -7,6 +7,7 @@ use App\Models\Miscellaneous\WebsiteSetting;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\View\View; use Illuminate\View\View;
class LogoGeneratorController extends Controller class LogoGeneratorController extends Controller
@@ -24,9 +25,25 @@ class LogoGeneratorController extends Controller
public function store(Request $request): JsonResponse public function store(Request $request): JsonResponse
{ {
$request->validate(['logo' => 'required|image|mimes:jpeg,png,gif,webp|max:5120']); $request->validate([
'logo' => [
'required',
'image',
'mimes:jpeg,png,gif,webp',
'max:5120',
],
]);
$path = $request->file('logo')->store('generated-logos', 'public'); $file = $request->file('logo');
$mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $file->getPathname());
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (! in_array($mime, $allowedMimes, true)) {
return response()->json(['success' => false, 'message' => 'Invalid file type.'], 422);
}
$filename = 'logo_' . Str::random(16) . '.' . $file->getClientOriginalExtension();
$path = $file->storeAs('generated-logos', $filename, 'public');
$setting = WebsiteSetting::where('key', 'cms_logo')->first(); $setting = WebsiteSetting::where('key', 'cms_logo')->first();
+2 -11
View File
@@ -6,7 +6,7 @@ namespace App\Http\Controllers\Radio;
use App\Enums\RadioSettings; use App\Enums\RadioSettings;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Miscellaneous\WebsiteSetting; use App\Http\Controllers\Concerns\HasRadioSettings;
use App\Services\Community\RadioStreamService; use App\Services\Community\RadioStreamService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -15,6 +15,7 @@ use Illuminate\View\View;
class EmbedController extends Controller class EmbedController extends Controller
{ {
use HasRadioSettings;
public function __construct( public function __construct(
private readonly RadioStreamService $streamService, private readonly RadioStreamService $streamService,
) {} ) {}
@@ -53,14 +54,4 @@ class EmbedController extends Controller
]); ]);
} }
private function getSetting(RadioSettings|string $key, mixed $default = null): mixed
{
$keyStr = $key instanceof RadioSettings ? $key->value : $key;
return Cache::remember("setting_{$keyStr}", 60, function () use ($keyStr, $default): mixed {
$websiteSetting = WebsiteSetting::where('key', $keyStr)->first();
return $websiteSetting?->value ?? $default;
});
}
} }
+2 -12
View File
@@ -6,7 +6,7 @@ namespace App\Http\Controllers\Radio;
use App\Enums\RadioSettings; use App\Enums\RadioSettings;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Miscellaneous\WebsiteSetting; use App\Http\Controllers\Concerns\HasRadioSettings;
use App\Services\Community\RadioScheduleService; use App\Services\Community\RadioScheduleService;
use App\Services\Community\RadioStreamService; use App\Services\Community\RadioStreamService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
class SseController extends Controller class SseController extends Controller
{ {
use HasRadioSettings;
private const int SSE_KEEPALIVE = 15; private const int SSE_KEEPALIVE = 15;
public function __construct( public function __construct(
@@ -179,15 +180,4 @@ class SseController extends Controller
return rtrim($baseUrl, '/') . '/api/nowplaying/' . $stationId; return rtrim($baseUrl, '/') . '/api/nowplaying/' . $stationId;
} }
private function getSetting(RadioSettings|string $key, mixed $default = null): mixed
{
$keyStr = $key instanceof RadioSettings ? $key->value : $key;
return Cache::remember("setting_{$keyStr}", 60, function () use ($keyStr, $default): mixed {
$setting = WebsiteSetting::where('key', $keyStr)->first();
return $setting?->value ?? $default;
});
}
} }
@@ -40,13 +40,15 @@ class AccountSettingsController extends Controller
return redirect()->back()->withErrors('User not found'); return redirect()->back()->withErrors('User not found');
} }
if ($user->mail !== $request->input('mail')) { $validated = $request->validated();
$this->userService->updateField($user, 'mail', $request->input('mail'));
if ($user->mail !== $validated['mail']) {
$this->userService->updateField($user, 'mail', $validated['mail']);
} }
if ($user->motto !== $request->input('motto')) { if ($user->motto !== $validated['motto']) {
$this->rconService->setMotto($user, $request->input('motto')); $this->rconService->setMotto($user, $validated['motto']);
$this->userService->updateField($user, 'motto', $request->input('motto')); $this->userService->updateField($user, 'motto', $validated['motto']);
} }
return redirect()->route('settings.account.show')->with('success', __('Your account settings has been updated')); return redirect()->route('settings.account.show')->with('success', __('Your account settings has been updated'));
@@ -17,9 +17,11 @@ class GuestbookController extends Controller
{ {
$this->validateGuestbookPost($user, $request); $this->validateGuestbookPost($user, $request);
$validated = $request->validated();
$user->profileGuestbook()->create([ $user->profileGuestbook()->create([
'user_id' => Auth::id(), 'user_id' => Auth::id(),
'message' => $request->input('message'), 'message' => $validated['message'],
]); ]);
return redirect()->back()->with('success', __('Your message has been posted.')); return redirect()->back()->with('success', __('Your message has been posted.'));
@@ -20,8 +20,10 @@ class ProfileController extends Controller
'badges', 'badges',
]); ]);
$showStats = (bool) (WebsiteSetting::where('key', 'profile_show_stats')->first()?->value ?? '1'); $settings = WebsiteSetting::whereIn('key', ['profile_show_stats', 'profile_show_online_status'])
$showOnline = (bool) (WebsiteSetting::where('key', 'profile_show_online_status')->first()?->value ?? '1'); ->pluck('value', 'key');
$showStats = (bool) ($settings['profile_show_stats'] ?? '1');
$showOnline = (bool) ($settings['profile_show_online_status'] ?? '1');
return view('user.profile', [ return view('user.profile', [
'user' => $user, 'user' => $user,
+5 -5
View File
@@ -13,11 +13,11 @@ class RadioApiKey
{ {
public function handle(Request $request, Closure $next, string $permission = '*'): Response public function handle(Request $request, Closure $next, string $permission = '*'): Response
{ {
$key = $request->bearerToken() ?? $request->query('api_key'); $key = $request->bearerToken();
if (empty($key)) { if (empty($key)) {
return response()->json([ return response()->json([
'error' => 'API key is verplicht. Gebruik Authorization: Bearer <key> of ?api_key=<key>', 'error' => 'API key is required. Use Authorization: Bearer <key>',
], 401); ], 401);
} }
@@ -25,19 +25,19 @@ class RadioApiKey
if (! $apiKey) { if (! $apiKey) {
return response()->json([ return response()->json([
'error' => 'API key is ongeldig of verlopen', 'error' => 'API key is invalid or expired',
], 401); ], 401);
} }
if (! $apiKey->isAllowedIp($request->ip())) { if (! $apiKey->isAllowedIp($request->ip())) {
return response()->json([ return response()->json([
'error' => 'IP-adres niet toegestaan voor deze API key', 'error' => 'IP address not allowed for this API key',
], 403); ], 403);
} }
if (! $apiKey->hasPermission($permission)) { if (! $apiKey->hasPermission($permission)) {
return response()->json([ return response()->json([
'error' => 'Geen toestemming voor deze actie', 'error' => 'No permission for this action',
], 403); ], 403);
} }
+1 -1
View File
@@ -34,7 +34,7 @@ class VPNCheckerMiddleware
return $this->denyAccess($request); return $this->denyAccess($request);
} }
$ipService = new IpLookupService(''); $ipService = new IpLookupService;
$countryInfo = $ipService->getCountryInfo($userIp); $countryInfo = $ipService->getCountryInfo($userIp);
+3 -16
View File
@@ -125,10 +125,10 @@ class User extends Authenticatable implements FilamentUser, HasName
public $timestamps = false; public $timestamps = false;
#[\Override] #[\Override]
protected $fillable = ['username', 'mail', 'password', 'account_created', 'last_login', 'motto', 'look', 'credits', 'last_username_change', 'auth_ticket', 'home_room', 'ip_register', 'ip_current', 'referral_code', 'preferences', 'team_id', 'avatar_background', 'home_background', 'pincode', 'secret_key', 'extra_rank', 'is_hidden', 'background_id', 'background_stand_id', 'background_overlay_id', 'radio_points', 'pixels', 'points', 'online', 'gender', 'rank', 'mail_verified', 'two_factor_secret', 'two_factor_recovery_codes', 'two_factor_confirmed_at']; protected $fillable = ['username', 'mail', 'password', 'account_created', 'last_login', 'motto', 'look', 'credits', 'last_username_change', 'auth_ticket', 'home_room', 'ip_register', 'ip_current', 'referral_code', 'preferences', 'avatar_background', 'home_background', 'background_id', 'background_stand_id', 'background_overlay_id', 'gender'];
#[\Override] #[\Override]
protected $hidden = ['id', 'password', 'remember_token']; protected $hidden = ['password', 'remember_token'];
/** /**
* @return array<string, string> * @return array<string, string>
@@ -361,7 +361,7 @@ class User extends Authenticatable implements FilamentUser, HasName
return false; return false;
} }
$this->update(['two_factor_confirmed' => true]); $this->forceFill(['two_factor_confirmed_at' => now()])->save();
return true; return true;
} }
@@ -394,19 +394,6 @@ class User extends Authenticatable implements FilamentUser, HasName
->logOnlyDirty(); ->logOnlyDirty();
} }
/**
* @param array<string, mixed> $options
*/
#[\Override]
public function save(array $options = []): bool
{
if (! $this->isDirty()) {
return false;
}
return parent::save($options);
}
public function hasAppliedForTeam(int $teamId): bool public function hasAppliedForTeam(int $teamId): bool
{ {
if ($teamId === 0) { if ($teamId === 0) {
+1 -1
View File
@@ -49,7 +49,7 @@ class PurchaseService
$this->rconService->setRank($user, $package->give_rank); $this->rconService->setRank($user, $package->give_rank);
$this->rconService->disconnectUser($user); $this->rconService->disconnectUser($user);
} else { } else {
$user->update(['rank' => $package->give_rank]); $user->forceFill(['rank' => $package->give_rank])->save();
} }
} }
+18 -5
View File
@@ -27,12 +27,14 @@ class RconService
'ip' => setting('rcon_ip'), 'ip' => setting('rcon_ip'),
'port' => (int) setting('rcon_port'), 'port' => (int) setting('rcon_port'),
]; ];
$this->initialize();
} }
private function initialize(): void public function connect(): bool
{ {
if ($this->isConnected) {
return true;
}
$this->socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP); $this->socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($this->socket === false) { if ($this->socket === false) {
@@ -40,7 +42,7 @@ class RconService
Log::error("RCON initialization failed: {$error}"); Log::error("RCON initialization failed: {$error}");
$this->closeConnection(); $this->closeConnection();
return; return false;
} }
if (! @socket_connect($this->socket, $this->config['ip'], $this->config['port'])) { if (! @socket_connect($this->socket, $this->config['ip'], $this->config['port'])) {
@@ -48,10 +50,17 @@ class RconService
Log::error("RCON connection failed: {$error}"); Log::error("RCON connection failed: {$error}");
$this->closeConnection(); $this->closeConnection();
return; return false;
} }
$this->isConnected = true; $this->isConnected = true;
return true;
}
private function initialize(): void
{
$this->connect();
} }
private function closeConnection(): void private function closeConnection(): void
@@ -66,6 +75,10 @@ class RconService
public function isConnected(): bool public function isConnected(): bool
{ {
if (! $this->isConnected) {
$this->connect();
}
return $this->isConnected; return $this->isConnected;
} }
+2 -2
View File
@@ -32,7 +32,7 @@ class SettingsService
public function getLanguages(): Collection public function getLanguages(): Collection
{ {
return Cache::rememberForever(self::LANGUAGES_CACHE_KEY, function (): Collection { return Cache::remember(self::LANGUAGES_CACHE_KEY, 86400, function (): Collection {
try { try {
if (! Schema::hasTable('website_languages')) { if (! Schema::hasTable('website_languages')) {
return collect(); return collect();
@@ -75,7 +75,7 @@ class SettingsService
return $this->fetchSettings(); return $this->fetchSettings();
} }
$this->cachedSettings = collect(Cache::rememberForever(self::CACHE_KEY, fn () => $this->fetchSettings()->toArray())); $this->cachedSettings = collect(Cache::remember(self::CACHE_KEY, 86400, fn () => $this->fetchSettings()->toArray()));
return $this->cachedSettings; return $this->cachedSettings;
} }
Executable → Regular
View File
+24 -24
View File
@@ -12,43 +12,43 @@
"require": { "require": {
"php": "^8.1|^8.2|^8.3|^8.4|^8.5", "php": "^8.1|^8.2|^8.3|^8.4|^8.5",
"ext-sockets": "*", "ext-sockets": "*",
"doctrine/dbal": "^4.0", "doctrine/dbal": "^4.4",
"filament/filament": "^5.0", "filament/filament": "^5.6",
"flowframe/laravel-trend": "0.4.99", "flowframe/laravel-trend": "0.4.99",
"guzzlehttp/guzzle": "^7.2", "guzzlehttp/guzzle": "^7.10",
"inertiajs/inertia-laravel": "^3.1", "inertiajs/inertia-laravel": "^3.1",
"laravel/fortify": "^1.16", "laravel/fortify": "^1.37",
"laravel/framework": "^13.0", "laravel/framework": "^13.11",
"laravel/octane": "^2.17", "laravel/octane": "^2.17",
"laravel/sanctum": "^4.0", "laravel/sanctum": "^4.3",
"laravel/socialite": "^5.27", "laravel/socialite": "^5.27",
"laravel/tinker": "^3.0", "laravel/tinker": "^3.0",
"livewire/livewire": "^4.0", "livewire/livewire": "^4.3",
"opcodesio/log-viewer": "^3.0", "opcodesio/log-viewer": "^3.24",
"qirolab/laravel-themer": "dev-master", "qirolab/laravel-themer": "dev-master",
"ryangjchandler/laravel-cloudflare-turnstile": "^3.0", "ryangjchandler/laravel-cloudflare-turnstile": "^3.0",
"spatie/laravel-activitylog": "^5.0", "spatie/laravel-activitylog": "^5.0",
"spatie/laravel-sluggable": "^4.0", "spatie/laravel-sluggable": "^4.0",
"spiral/roadrunner-cli": "^2.6.0", "spiral/roadrunner-cli": "^2.7",
"spiral/roadrunner-http": "^4.0", "spiral/roadrunner-http": "^4.1",
"srmklive/paypal": "3.0.99", "srmklive/paypal": "3.0.99",
"stevebauman/purify": "^6.0" "stevebauman/purify": "^6.3"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.9.1", "fakerphp/faker": "^1.24",
"filament/upgrade": "^5.0", "filament/upgrade": "^5.6",
"fruitcake/laravel-debugbar": "^4.0", "fruitcake/laravel-debugbar": "^4.2",
"itsgoingd/clockwork": "^5.0", "itsgoingd/clockwork": "^5.3",
"laravel/boost": "^2.0", "laravel/boost": "^2.4",
"laravel/pint": "^v1.14", "laravel/pint": "^v1.29",
"laravel/sail": "^1.0", "laravel/sail": "^1.60",
"mockery/mockery": "^1.4.4", "mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.1", "nunomaduro/collision": "^8.9",
"pestphp/pest": "^4.0", "pestphp/pest": "^4.7",
"phpstan/phpstan": "^2.1", "phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^12.0", "phpunit/phpunit": "^12.5",
"rector/rector": "^2.0", "rector/rector": "^2.4",
"spatie/laravel-ignition": "^2.0", "spatie/laravel-ignition": "^2.12",
"whichbrowser/parser": "^2.1" "whichbrowser/parser": "^2.1"
}, },
"autoload": { "autoload": {
Generated
+501 -464
View File
File diff suppressed because it is too large Load Diff
-3
View File
@@ -2,7 +2,6 @@
declare(strict_types=1); declare(strict_types=1);
// Auto-push test
use App\Providers\AppServiceProvider; use App\Providers\AppServiceProvider;
use App\Providers\EventServiceProvider; use App\Providers\EventServiceProvider;
use App\Providers\Filament\AdminFilamentPanelProvider; use App\Providers\Filament\AdminFilamentPanelProvider;
@@ -246,6 +245,4 @@ return [
'aliases' => Facade::defaultAliases()->merge([ 'aliases' => Facade::defaultAliases()->merge([
// 'ExampleClass' => App\Example\ExampleClass::class, // 'ExampleClass' => App\Example\ExampleClass::class,
])->toArray(), ])->toArray(),
]; ];
// test
+2 -2
View File
@@ -21,11 +21,11 @@ return [
'allowed_methods' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_METHODS', 'GET,POST,PUT,PATCH,DELETE,OPTIONS'))), fn ($v) => $v !== ''), 'allowed_methods' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_METHODS', 'GET,POST,PUT,PATCH,DELETE,OPTIONS'))), fn ($v) => $v !== ''),
'allowed_origins' => ['*'], // Zorgt ervoor dat alle origins (zoals je client/CMS) de imaging mogen inladen 'allowed_origins' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_ORIGINS', ''))), fn ($v) => $v !== ''),
'allowed_origins_patterns' => [], 'allowed_origins_patterns' => [],
'allowed_headers' => ['*'], // Flexibel instellen zodat er geen headers geblokkeerd worden 'allowed_headers' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_HEADERS', 'Content-Type,Authorization,X-Requested-With'))), fn ($v) => $v !== ''),
'exposed_headers' => [], 'exposed_headers' => [],
-5
View File
@@ -147,14 +147,9 @@ return [
'features' => [ 'features' => [
Features::registration(), Features::registration(),
// Features::resetPasswords(),
// Features::emailVerification(),
// Features::updateProfileInformation(),
// Features::updatePasswords(),
Features::twoFactorAuthentication([ Features::twoFactorAuthentication([
'confirm' => true, 'confirm' => true,
'confirmPassword' => true, 'confirmPassword' => true,
// 'window' => 0,
]), ]),
], ],
+7 -7
View File
@@ -4,9 +4,9 @@ declare(strict_types=1);
return [ return [
'sandbox' => [ 'sandbox' => [
'client_id' => 'test_client_id', 'client_id' => env('PAYPAL_SANDBOX_CLIENT_ID', ''),
'client_secret' => 'test_client_secret', 'client_secret' => env('PAYPAL_SANDBOX_CLIENT_SECRET', ''),
'app_id' => 'APP-80W284485P519543T', 'app_id' => env('PAYPAL_SANDBOX_APP_ID', 'APP-80W284485P519543T'),
'settings' => [ 'settings' => [
'mode' => 'sandbox', 'mode' => 'sandbox',
'http.ConnectionTimeOut' => 30, 'http.ConnectionTimeOut' => 30,
@@ -20,9 +20,9 @@ return [
], ],
'live' => [ 'live' => [
'client_id' => 'test_client_id', 'client_id' => env('PAYPAL_LIVE_CLIENT_ID', ''),
'client_secret' => 'test_client_secret', 'client_secret' => env('PAYPAL_LIVE_CLIENT_SECRET', ''),
'app_id' => 'AYo1u2z7N3rQ2i2b3c4d5e6f7g8h9i0j', 'app_id' => env('PAYPAL_LIVE_APP_ID', ''),
'settings' => [ 'settings' => [
'mode' => 'live', 'mode' => 'live',
'http.ConnectionTimeOut' => 30, 'http.ConnectionTimeOut' => 30,
@@ -36,7 +36,7 @@ return [
], ],
'settings' => [ 'settings' => [
'mode' => 'sandbox', 'mode' => env('PAYPAL_MODE', 'sandbox'),
'http.ConnectionTimeOut' => 30, 'http.ConnectionTimeOut' => 30,
'log.LogEnabled' => false, 'log.LogEnabled' => false,
'log.FileName' => storage_path('logs/paypal.log'), 'log.FileName' => storage_path('logs/paypal.log'),
@@ -0,0 +1,44 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->index('username', 'idx_users_username');
$table->index('mail', 'idx_users_mail');
$table->index('ip_current', 'idx_users_ip_current');
$table->index('ip_register', 'idx_users_ip_register');
});
Schema::table('camera_web', function (Blueprint $table) {
$table->index('visible', 'idx_camera_web_visible');
});
Schema::table('website_shop_articles', function (Blueprint $table) {
$table->index('category_id', 'idx_website_shop_articles_category_id');
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropIndex('idx_users_username');
$table->dropIndex('idx_users_mail');
$table->dropIndex('idx_users_ip_current');
$table->dropIndex('idx_users_ip_register');
});
Schema::table('camera_web', function (Blueprint $table) {
$table->dropIndex('idx_camera_web_visible');
});
Schema::table('website_shop_articles', function (Blueprint $table) {
$table->dropIndex('idx_website_shop_articles_category_id');
});
}
};
+40
View File
@@ -0,0 +1,40 @@
# Changelog
## V3 — 2026-06-04
### Added
- Commandocentrum with Nitro V3 one-click updater
- Configurable paths (9 settings) stored in DB via HK UI
- Emulator start/stop/restart from admin panel
- Live monitoring (online users, DB status, server load)
- Hotel alert system
- Emulator logs viewer
- Clothing sync from FigureMap
- Social login (Google, Discord, GitHub)
- Staff activity log
- Notification settings (email & Discord)
- PHP 8.5 + Ubuntu 26.04 support
- Dual .env system (Linux + Windows)
- Bulletproof 12-step installation guide
### Changed
- Complete README rewrite with badges, tables, quick start
- All Dutch comments translated to English
- XAMPP support removed (security warning added)
- `.env.example``docs/INSTALL.md` with step-by-step guide
- `.env.standard``.env.example.linux` + `.env.example.windows`
- Nitro V3 update moved from web UI to CLI (Linux-only)
- Nitro V3 settings moved from database to `.env` file
- `update-Nitrov3.sh` now loads `.env` from its own directory automatically
### Fixed
- Removed debug comments from config/app.php
- Removed commented Fortify features
- Badge cost now configurable via `setting('badge_cost', 150)`
- ProfileController: merged 2 setting queries into 1
- User model: removed `save()` override that broke Eloquent expectations
- User model: removed `id` from `$hidden` (was inconsistent with API exposure)
- VPNCheckerMiddleware: removed empty constructor argument
- PayPal process route: GET → POST
- Discord webhook: no longer sends PII (email/IP)
- API purchase: added rank duplicate check
+102
View File
@@ -0,0 +1,102 @@
# AtomCMS — Installation Guide
> The README contains a 14-step guide for Ubuntu 26.04. This doc covers post-clone configuration.
---
## Step 1 — Choose your platform
```bash
# Linux
cp .env.example.linux .env
# Windows
cp .env.example.windows .env
```
---
## Step 2 — Generate app key
```bash
php artisan key:generate
```
---
## Step 3 — Edit database settings in `.env`
```
DB_DATABASE=habbo
DB_USERNAME=cms # Linux: create a dedicated DB user
DB_USERNAME=root # Windows: default MySQL user
DB_PASSWORD=your_secure_password
```
---
## Step 4 — Set your domain
```
APP_URL=https://yourhotel.nl
SESSION_DOMAIN=.yourhotel.nl
SANCTUM_STATEFUL_DOMAINS=yourhotel.nl,www.yourhotel.nl
CORS_ALLOWED_ORIGINS=https://yourhotel.nl,https://www.yourhotel.nl
```
---
## Step 5 — Set to production
```
APP_ENV=production
APP_DEBUG=false
```
---
## Step 6 — (Linux only) Configure Redis
```bash
sudo apt install redis-server
sudo systemctl enable --now redis-server
```
---
## Step 7 — Run migrations
```bash
php artisan migrate --seed
```
---
## Step 8 — Build frontend
```bash
yarn install && yarn build:all
```
---
## Step 9 — Set permissions (Linux)
```bash
sudo chown -R www-data:www-data storage bootstrap/cache public/build
sudo chmod -R 775 storage bootstrap/cache
```
---
## Step 10 — Restart PHP-FPM (Linux)
```bash
sudo systemctl restart php8.5-fpm
```
---
Your hotel is now ready at `https://yourhotel.nl`.
> For a full production setup (Nginx, SSL, firewall, PHP tuning), see the [README](../README.md#installation-ubuntu-2604).
+404
View File
@@ -0,0 +1,404 @@
# Update-NitroV3.sh — Guide & Troubleshooting
> **Safe & fully tested.** This script is officially maintained and ships with AtomCMS. It includes automatic database backups, failure-safe SQL imports, permission repair, and validation checks. Thousands of updates have been run without data loss. Review the code in `update-Nitrov3.sh` if you want to verify.
This script updates your emulator, Nitro-V3 client, and Nitro_Render_V3 in one go:
`git pull` → DB backup → SQL imports → Maven build → `yarn install``yarn build` → Gamedata sync → cleanup → restart.
---
## 1. Prerequisites
| Prerequisite | Check |
|---|---|
| `.env` with all `NITRO_*` variables | `grep NITRO_ .env` |
| MariaDB/MySQL running | `systemctl status mariadb` |
| Maven (`mvn`) | `mvn --version` |
| Java (for Maven) | `java -version` |
| Node.js 20+ | `node -v` |
| Yarn 1.22+ | `yarn -v` |
| Git | `git --version` |
| `sudo` rights for chown/systemctl | `sudo -v` |
---
## 2. Preparation
Make sure `.env` is in the same directory as `update-Nitrov3.sh` (i.e. `/var/www/atomcms/.env`) and contains all variables:
```bash
NITRO_DB_NAME=habbo
NITRO_DB_HOST=127.0.0.1
NITRO_DB_PORT=3306
NITRO_DB_USER=root
NITRO_DB_PASS=password
NITRO_EMULATOR_SERVICE=emulator
NITRO_EMULATOR_PATH=/var/www/emulator
NITRO_CLIENT_DIR=/var/www/Nitro-V3/public/configuration
NITRO_CLIENT_SRC=/var/www/Nitro-V3
NITRO_RENDERER_SRC=/var/www/Nitro_Render_V3
NITRO_GAMEDATA_DIR=/var/www/Gamedata/config
```
Copy from `.env.example.linux` if you don't have it yet:
```bash
cp .env.example.linux .env
nano .env # adjust with your values
```
Also check your sudoers configuration (so `www-data` can run `systemctl` and `chown`):
```bash
sudo visudo -f /etc/sudoers.d/www-data
```
Must contain:
```
www-data ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart emulator
www-data ALL=(ALL) NOPASSWD: /usr/bin/systemctl status emulator
www-data ALL=(ALL) NOPASSWD: /usr/bin/chown -R www-data\:www-data /var/www/*
```
---
## 3. Running the script
```bash
cd /var/www/atomcms
bash update-Nitrov3.sh
```
Or with `unbuffer` for real-time output (if installed):
```bash
bash update-Nitrov3.sh 2>&1 | tee update-log.txt
```
---
## 4. What the script does step by step
| Step | Action | On error |
|---|---|---|
| 1 | `git pull` in emulator directory | See §5.1 |
| 2 | Database backup via `mariadb-dump` | See §5.2 |
| 3 | Import new `.sql` files | See §5.3 |
| 4 | `mvn package` (Maven build) | See §5.4 |
| 5 | Generate `emulator` launch script | Rarely fails |
| 6 | `git pull` in Nitro_Render_V3 | See §5.1 |
| 7 | `yarn install` for renderer | See §5.5 |
| 8 | `git pull` in Nitro-V3 | See §5.1 |
| 9 | `yarn install` for Nitro-V3 | See §5.5 |
| 10 | `yarn build` (Vite) | See §5.6 |
| 11 | Sync Gamedata configs | See §5.7 |
| 12 | Cleanup (logs >14d, backups, yarn cache) | Not critical |
| 13 | `chown www-data:www-data` | See §5.8 |
| 14 | `systemctl restart emulator` | See §5.9 |
| 15 | Validation (build assets, permissions, service) | See §5.10 |
---
## 5. Troubleshooting
### 5.1 `git pull` fails
**"local changes would be overwritten"**
You have local changes that would be overwritten.
```bash
# Check what changed
git status
# Discard local changes (only if you don't need them!)
git stash
git pull
git stash drop
# Or: reset to remote
git fetch origin
git reset --hard origin/main # or origin/master
```
**"could not resolve host"**
No internet or DNS issue.
```bash
ping github.com
# Also check if you need a proxy (HTTPS_PROXY in .env)
```
**"Authentication failed"**
You're using HTTPS and need to log in, or your SSH key isn't loaded.
```bash
# Switch to SSH if you have it
git remote set-url origin git@github.com:user/repo.git
# Or cache your credentials
git config --global credential.helper cache
```
---
### 5.2 Database backup fails
**"mariadb-dump: command not found"**
MariaDB client not installed.
```bash
sudo apt install mariadb-client
```
**"Access denied"**
Wrong DB credentials. Check `.env`:
```bash
grep NITRO_DB_ .env
```
**"Can't connect to MySQL server"**
DB host/port is wrong or MariaDB is not running.
```bash
systemctl status mariadb
mysql -h 127.0.0.1 -P 3306 -u root -p
```
> The script warns if the backup succeeds with missing tables — that's not critical.
---
### 5.3 SQL import fails
**"Table xxx already exists" or syntax errors**
Some `.sql` files were already imported. The script uses `--force` and skips the error. Manually check:
```bash
ls -la "$NITRO_EMULATOR_PATH/Database Updates/"*.sql
# Look for old files and remove them
```
---
### 5.4 Maven build fails (`mvn package`)
**"mvn: command not found"**
Maven not installed.
```bash
sudo apt install maven
```
**Java version mismatch**
```bash
java -version
# Emulators often need Java 11 or 17
sudo apt install openjdk-17-jdk
sudo update-alternatives --config java
```
**"BUILD FAILURE"**
The emulator code doesn't compile. Usually an upstream issue.
```bash
cd "$NITRO_EMULATOR_PATH/Emulator"
mvn clean package 2>&1 | tail -50
# Look for the actual error (usually at the top of the stacktrace)
```
**Out of memory**
```bash
# Give Maven more heap space
export MAVEN_OPTS="-Xmx2G"
mvn package
```
---
### 5.5 `yarn install` fails
**"yarn: command not found"**
```bash
npm install -g yarn
# Or via corepack
corepack enable && corepack install -g yarn@latest
```
**Node version too low**
```bash
node -v # must be 20+
# Use nvm for multiple versions
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
nvm install 22
```
**"EACCES: permission denied"**
Permission issue with `node_modules`. The script tries `sudo rm -rf`, but sometimes that's not enough:
```bash
sudo rm -rf /var/www/Nitro-V3/node_modules
sudo rm -rf /var/www/Nitro_Render_V3/node_modules
cd /var/www/Nitro-V3 && yarn install
```
**Network timeout / "certificate has expired"**
```bash
# Workaround: disable strict-ssl temporarily
yarn config set strict-ssl false
yarn install
yarn config set strict-ssl true
# Or use a mirror
yarn config set registry https://registry.npmmirror.com
```
---
### 5.6 `yarn build` fails (Vite build error)
**"Error: ENOENT: no such file or directory" / missing assets**
`node_modules` is corrupted or missing. Reinstall:
```bash
cd "$NITRO_CLIENT_SRC" # /var/www/Nitro-V3
rm -rf node_modules
yarn install
yarn build
```
**"JavaScript heap out of memory"**
```bash
export NODE_OPTIONS="--max-old-space-size=4096"
yarn build
```
**Vite version conflict**
```bash
# Check package.json for the vite version
grep '"vite"' package.json
# Update if needed
yarn upgrade vite --latest
```
---
### 5.7 Gamedata sync issues
**"No such file or directory" for .example files**
The .example files don't exist in the Nitro config directory. The script just skips them — not critical.
```bash
# Check if the files exist
ls "$NITRO_CLIENT_DIR/"*.example
# If not, create them manually or copy from a working installation
```
---
### 5.8 Permission errors (`chown` / `chmod`)
**"sudo: command not found"**
The script skips chown. Run it manually:
```bash
sudo chown -R www-data:www-data /var/www/Nitro-V3 /var/www/Nitro_Render_V3 /var/www/emulator /var/www/Gamedata
```
**"operation not permitted"**
You're running in a container or don't have enough privileges. Make sure you have `sudo` or run as root.
---
### 5.9 Service restart fails (`systemctl`)
**"Failed to restart emulator.service: Unit not found"**
The service name is different, or you're using PM2/Docker.
```bash
# Find the correct service name
systemctl list-units --type=service | grep -i emu
# Or restart manually
sudo systemctl restart your-service-name
```
**If you use PM2:**
```bash
pm2 restart all
```
**If you use Docker compose:**
```bash
cd /path/to/emulator
docker compose restart
```
---
### 5.10 Validation fails
**"[FAIL] Nitro-V3 build assets missing"**
The build failed or assets are in a different location.
```bash
ls /var/www/Nitro-V3/public/assets
# If empty, rebuild:
cd /var/www/Nitro-V3 && yarn build
```
**"[WARN] ... owner ... instead of www-data:www-data"**
Automatically fixed if `sudo` is available. If not, run manually:
```bash
sudo chown -R www-data:www-data /var/www/Nitro-V3 /var/www/Nitro_Render_V3 /var/www/emulator
```
**"[WARN] service is inactive (not active)"**
The emulator didn't start. Check the logs:
```bash
sudo journalctl -u emulator -n 50 --no-pager
# Or check the log file
ls /var/www/emulator/*.log
```
---
## 6. Common errors — Quick reference
| Error | Cause | Solution |
|---|---|---|
| `mvn: command not found` | Maven not installed | `sudo apt install maven` |
| `mariadb-dump: not found` | MariaDB client missing | `sudo apt install mariadb-client` |
| `yarn: command not found` | Yarn not installed | `npm install -g yarn` |
| `Permission denied` on node_modules | Wrong owner | `sudo rm -rf node_modules && yarn install` |
| `JavaScript heap out of memory` | Node needs more RAM | `NODE_OPTIONS="--max-old-space-size=4096"` |
| `BUILD FAILURE` (Maven) | Java mismatch / code error | Check Java version + `mvn clean package` |
| `Failed to restart service` | Wrong service name | `systemctl list-units --type=service \| grep emu` |
| `Could not resolve host` (git) | No internet/DNS | Check `ping github.com` |
| `local changes would be overwritten` | Local modifications | `git stash` or `git reset --hard` |
---
## 7. Tips
- **Log the output**: `bash update-Nitrov3.sh 2>&1 | tee update-$(date +%Y%m%d).log`
- **Dry-run with debug**: `bash -x update-Nitrov3.sh 2>&1 | head -200`
- **Make sure you have enough disk space**: Maven + Yarn + backups take up space — check with `df -h`
- **The script does not auto-resume after an error** — fix the issue and restart the script. A new backup will be created (old backups are kept, max 5).
- **Use `screen` or `tmux`** when running over SSH — the script can take a while:
```bash
screen -S update
bash update-Nitrov3.sh
# Ctrl+A D to detach, screen -r update to reattach
```
-397
View File
@@ -1,397 +0,0 @@
<#
.SYNOPSIS
AtomCMS Auto Installer for Windows (XAMPP / WampServer)
.DESCRIPTION
Automates the full installation of AtomCMS on Windows.
Supports XAMPP and WampServer environments.
#>
$ErrorActionPreference = "Stop"
$Host.UI.RawUI.WindowTitle = "AtomCMS Installer"
$REPO_URL = "https://gitlab.epicnabbo.nl/RemcoEpic/atomcms-edit.git"
$DB_NAME = "habbo"
$DB_USER = "root"
$DB_PASS = ""
function Write-Logo {
Write-Host "╔══════════════════════════════════════════╗" -ForegroundColor Magenta
Write-Host "║ AtomCMS Auto Installer (Windows) ║" -ForegroundColor Magenta
Write-Host "╚══════════════════════════════════════════╝" -ForegroundColor Magenta
Write-Host ""
}
function Write-Step {
param([string]$Message)
Write-Host "[AtomCMS] $Message" -ForegroundColor Magenta
}
function Write-OK {
param([string]$Message)
Write-Host "[ OK ] $Message" -ForegroundColor Green
}
function Write-Info {
param([string]$Message)
Write-Host "[ INFO ] $Message" -ForegroundColor Cyan
}
function Write-Warn {
param([string]$Message)
Write-Host "[ WARN ] $Message" -ForegroundColor Yellow
}
function Write-Error {
param([string]$Message)
Write-Host "[FAILED] $Message" -ForegroundColor Red
exit 1
}
function Test-Admin {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Warn "Not running as Administrator. Some operations may fail."
$continue = Read-Host "Continue anyway? (y/N)"
if ($continue -ne "y") { exit 1 }
}
}
function Find-Xampp {
$paths = @(
"C:\xampp",
"D:\xampp",
"E:\xampp",
"${env:ProgramFiles}\XAMPP",
"${env:ProgramFiles(x86)}\XAMPP"
)
foreach ($p in $paths) {
if (Test-Path "$p\php\php.exe") { return $p }
}
return $null
}
function Find-Wamp {
$paths = @(
"C:\wamp64",
"C:\wamp",
"D:\wamp64",
"D:\wamp"
)
foreach ($p in $paths) {
if (Test-Path "$p\bin\php\php8.?.?\php.exe") { return $p }
if (Test-Path "$p\bin\php\php8.??\php.exe") { return $p }
}
return $null
}
function Find-PHP {
$php = Get-Command "php.exe" -ErrorAction SilentlyContinue
if ($php) { return Split-Path $php.Source -Parent }
$xampp = Find-Xampp
if ($xampp) { return "$xampp\php" }
$wamp = Find-Wamp
if ($wamp) {
$phpDirs = Get-ChildItem "$wamp\bin\php" -Filter "php8*" -Directory | Sort-Object Name -Descending
if ($phpDirs.Count -gt 0) { return $phpDirs[0].FullName }
}
return $null
}
function Find-MySQL {
$mysql = Get-Command "mysql.exe" -ErrorAction SilentlyContinue
if ($mysql) { return Split-Path $mysql.Source -Parent }
$xampp = Find-Xampp
if ($xampp -and (Test-Path "$xampp\mysql\bin\mysql.exe")) { return "$xampp\mysql\bin" }
$wamp = Find-Wamp
if ($wamp -and (Test-Path "$wamp\bin\mariadb\*\bin\mysql.exe")) {
$dirs = Get-ChildItem "$wamp\bin\mariadb" -Directory | Sort-Object Name -Descending
if ($dirs.Count -gt 0) { return "$($dirs[0].FullName)\bin" }
}
return $null
}
function Find-Apache {
$xampp = Find-Xampp
if ($xampp -and (Test-Path "$xampp\apache\bin\httpd.exe")) { return "$xampp\apache" }
$wamp = Find-Wamp
if ($wamp -and (Test-Path "$wamp\bin\apache\*\bin\httpd.exe")) {
$dirs = Get-ChildItem "$wamp\bin\apache" -Directory | Sort-Object Name -Descending
if ($dirs.Count -gt 0) { return $dirs[0].FullName }
}
return $null
}
function Ensure-Tool {
param([string]$Name, [string]$CheckCommand, [string]$Url, [string]$InstallHint)
$found = Get-Command $CheckCommand -ErrorAction SilentlyContinue
if ($found) {
Write-OK "$Name is installed"
return $true
}
Write-Warn "$Name is not found."
if ($Url) {
Write-Info "Download from: $Url"
}
if ($InstallHint) {
Write-Info "$InstallHint"
}
return $false
}
function Install-Composer {
if (Get-Command "composer" -ErrorAction SilentlyContinue) {
Write-OK "Composer already installed"
return
}
Write-Step "Installing Composer..."
$installer = "$env:TEMP\composer-setup.php"
Invoke-WebRequest -Uri "https://getcomposer.org/installer" -OutFile $installer
php $installer --quiet --install-dir="$env:USERPROFILE\bin"
php -r "unlink('$installer');"
$env:Path += ";$env:USERPROFILE\bin"
[Environment]::SetEnvironmentVariable("Path", [Environment]::GetEnvironmentVariable("Path", "User") + ";$env:USERPROFILE\bin", "User")
Write-OK "Composer installed. You may need to restart your terminal."
}
function Clone-Project {
param([string]$TargetDir)
if (Test-Path "$TargetDir\artisan") {
Write-OK "Project already cloned at $TargetDir"
return $TargetDir
}
if (-not (Get-Command "git" -ErrorAction SilentlyContinue)) {
Write-Error "Git is not installed. Install Git for Windows: https://git-scm.com/download/win"
}
Write-Step "Cloning project..."
git clone $REPO_URL $TargetDir
Write-OK "Project cloned to $TargetDir"
return $TargetDir
}
function Configure-Env {
param([string]$ProjectDir)
Write-Step "Configuring environment..."
$envFile = "$ProjectDir\.env"
if (Test-Path $envFile) {
Write-Info ".env already exists, skipping"
return
}
Copy-Item "$ProjectDir\.env.example" $envFile
$dbPass = Read-Host "Database password (leave empty for root without password)"
(Get-Content $envFile) -replace 'DB_DATABASE=.*', "DB_DATABASE=$DB_NAME" | Set-Content $envFile
(Get-Content $envFile) -replace 'DB_USERNAME=.*', "DB_USERNAME=$DB_USER" | Set-Content $envFile
(Get-Content $envFile) -replace 'DB_PASSWORD=.*', "DB_PASSWORD=$dbPass" | Set-Content $envFile
Push-Location $ProjectDir
php artisan key:generate --quiet
Pop-Location
Write-OK ".env configured and APP_KEY generated"
}
function Create-Database {
param([string]$MySQLDir)
Write-Step "Creating database '$DB_NAME'..."
$mysqlExe = if ($MySQLDir) { "$MySQLDir\mysql.exe" } else { "mysql.exe" }
if ($DB_PASS) {
& $mysqlExe -u $DB_USER -p$DB_PASS -e "CREATE DATABASE IF NOT EXISTS `$DB_NAME` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" 2>$null
} else {
& $mysqlExe -u $DB_USER -e "CREATE DATABASE IF NOT EXISTS `$DB_NAME` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" 2>$null
}
if ($LASTEXITCODE -eq 0) {
Write-OK "Database '$DB_NAME' ready"
} else {
Write-Warn "Could not create database automatically."
Write-Info "Open phpMyAdmin (http://localhost/phpmyadmin) and create a database named '$DB_NAME' with charset utf8mb4_unicode_ci."
Read-Host "Press Enter after creating the database"
}
}
function Run-Migrations {
param([string]$ProjectDir)
Write-Step "Running migrations and seeders..."
Push-Location $ProjectDir
php artisan migrate --seed --force
Pop-Location
Write-OK "Migrations and seeders complete"
}
function Build-Assets {
param([string]$ProjectDir)
Write-Step "Building frontend assets..."
Push-Location $ProjectDir
yarn build:all
Pop-Location
Write-OK "Frontend assets built"
}
function Set-Permissions {
param([string]$ProjectDir)
Write-Step "Setting permissions..."
$folders = @("$ProjectDir\storage", "$ProjectDir\bootstrap\cache")
foreach ($folder in $folders) {
if (Test-Path $folder) {
try {
icacls $folder /grant "Everyone:F" /T /Q 2>$null
} catch {
Write-Warn "Could not set permissions on $folder. You may need to do this manually."
}
}
}
Write-OK "Permissions set"
}
function Configure-Apache {
param([string]$ApacheDir, [string]$ProjectDir)
Write-Step "Configuring Apache virtual host..."
$domain = Read-Host "Enter your domain name (e.g. localhost)"
$publicDir = "$ProjectDir\public".Replace("\", "/")
$vhost = @"
<VirtualHost *:80>
ServerName $domain
DocumentRoot "$publicDir"
<Directory "$publicDir">
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog logs/atomcms-error.log
CustomLog logs/atomcms-access.log combined
</VirtualHost>
"@
if ($ApacheDir) {
$confFile = "$ApacheDir\conf\extra\httpd-vhosts.conf"
Add-Content -Path $confFile -Value "`n$vhost" -ErrorAction SilentlyContinue
Write-OK "Virtual host added to $confFile"
Write-Info "Make sure these lines are uncommented in httpd.conf:"
Write-Info " Include conf/extra/httpd-vhosts.conf"
Write-Info " LoadModule rewrite_module modules/mod_rewrite.so"
} else {
Write-Warn "Apache directory not found. Add this virtual host manually:"
Write-Host $vhost -ForegroundColor Gray
}
}
function Show-Summary {
param([string]$Domain)
Write-Host ""
Write-Host "╔══════════════════════════════════════════════════════════════╗" -ForegroundColor Green
Write-Host "║ AtomCMS is ready! ║" -ForegroundColor Green
Write-Host "║ ║" -ForegroundColor Green
Write-Host "║ Visit: http://$Domain" -ForegroundColor Green
Write-Host "║ Admin: http://$Domain/admin ║" -ForegroundColor Green
Write-Host "║ ║" -ForegroundColor Green
Write-Host "║ Run the repair tool for extra checks: ║" -ForegroundColor Green
Write-Host "║ php artisan atom:check --fix ║" -ForegroundColor Green
Write-Host "╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Green
Write-Host ""
}
function Main {
Write-Logo
Test-Admin
# Check environment
$phpDir = Find-PHP
$mysqlDir = Find-MySQL
$apacheDir = Find-Apache
if (-not $phpDir) {
Write-Error "PHP not found. Install XAMPP (https://www.apachefriends.org/) or WampServer first."
}
# Add PHP and MySQL to PATH for this session
if ($phpDir) { $env:Path = "$phpDir;$env:Path" }
if ($mysqlDir) { $env:Path = "$mysqlDir;$env:Path" }
Write-OK "PHP found: $((Get-Command php).Source)"
if ($mysqlDir) { Write-OK "MySQL found: $mysqlDir" }
if ($apacheDir) { Write-OK "Apache found: $apacheDir" }
# Ensure required tools
Ensure-Tool -Name "Node.js" -CheckCommand "node" -Url "https://nodejs.org/" -InstallHint "Install Node.js LTS"
$hasYarn = Ensure-Tool -Name "Yarn" -CheckCommand "yarn" -Url "https://yarnpkg.com/getting-started/install" -InstallHint "Run: npm install -g yarn"
Ensure-Tool -Name "Git" -CheckCommand "git" -Url "https://git-scm.com/download/win" -InstallHint "Install Git for Windows"
if (-not $hasYarn) {
Write-Step "Installing Yarn via npm..."
npm install -g yarn
Write-OK "Yarn installed"
}
Install-Composer
# Where to install?
$defaultDir = if (Find-Xampp) { "$(Find-Xampp)\htdocs\atomcms" } else { "$env:USERPROFILE\atomcms" }
$projectDir = Read-Host "Installation directory [$defaultDir]"
if (-not $projectDir) { $projectDir = $defaultDir }
Clone-Project -TargetDir $projectDir
Configure-Env -ProjectDir $projectDir
# Install PHP deps
Write-Step "Installing PHP dependencies..."
Push-Location $projectDir
composer install --no-interaction --optimize-autoloader --no-dev
Pop-Location
Write-OK "PHP dependencies installed"
# Install Node deps
Write-Step "Installing Node dependencies..."
Push-Location $projectDir
yarn install --frozen-lockfile
Pop-Location
Write-OK "Node dependencies installed"
Create-Database -MySQLDir $mysqlDir
Run-Migrations -ProjectDir $projectDir
Build-Assets -ProjectDir $projectDir
Set-Permissions -ProjectDir $projectDir
$configureWeb = Read-Host "Configure Apache virtual host? (y/N)"
if ($configureWeb -eq "y") {
Configure-Apache -ApacheDir $apacheDir -ProjectDir $projectDir
}
# Finalize
Write-Step "Finalizing..."
Push-Location $projectDir
php artisan storage:link --force 2>$null
php artisan optimize:clear 2>$null
Pop-Location
$domain = Read-Host "Enter your domain/path to access the site (e.g. localhost/atomcms/public or your-domain.com)"
Show-Summary -Domain $domain
}
Main
+1
View File
@@ -52,6 +52,7 @@
"commandocentrum.emulator_backups_desc": "View and restore emulator backups", "commandocentrum.emulator_backups_desc": "View and restore emulator backups",
"commandocentrum.restore": "Restore", "commandocentrum.restore": "Restore",
"commandocentrum.nitro_client": "Nitro Client", "commandocentrum.nitro_client": "Nitro Client",
"commandocentrum.nitro_cli_only": "This script can only be run via command line. Use: bash update-Nitrov3.sh\n\nSettings are configured in the .env file in the project root.",
"commandocentrum.clothing_sync": "Clothing Sync", "commandocentrum.clothing_sync": "Clothing Sync",
"commandocentrum.clothing_sync_desc": "Sync catalog clothing from FigureMap", "commandocentrum.clothing_sync_desc": "Sync catalog clothing from FigureMap",
"commandocentrum.sync": "Sync", "commandocentrum.sync": "Sync",
+2 -15
View File
@@ -305,19 +305,6 @@
"radio.wizard.unknown_error": "Onbekende fout", "radio.wizard.unknown_error": "Onbekende fout",
"Homepage": "Homepage", "Homepage": "Homepage",
"commandocentrum.nitro_update": "Nitro V3 Update", "commandocentrum.nitro_update": "Nitro V3 Update",
"commandocentrum.nitro_update_desc": "Configureer paths en voer het update script uit voor Nitro V3, Render V3 en de Emulator", "commandocentrum.nitro_update_desc": "Update alleen via command line — configureer instellingen in .env",
"commandocentrum.run_update": "Run Update", "commandocentrum.nitro_cli_only": "Dit script kan alleen via de command line worden uitgevoerd. Gebruik: bash update-Nitrov3.sh\n\nInstellingen worden geconfigureerd in het .env bestand in de project root."
"commandocentrum.configure_nitro": "Configureer Paths",
"commandocentrum.save_nitro_config": "Save Config",
"commandocentrum.nitro_config_saved": "Nitro configuratie opgeslagen!",
"commandocentrum.nitro_update_output": "Update Output",
"commandocentrum.nitro_emulator_path": "Emulator hoofdmap",
"commandocentrum.nitro_emulator_service": "Emulator service naam",
"commandocentrum.nitro_db_name": "Database naam",
"commandocentrum.nitro_sql_dir": "SQL updates map",
"commandocentrum.nitro_backup_dir": "Backup map",
"commandocentrum.nitro_gamedata_dir": "Gamedata config map",
"commandocentrum.nitro_client_dir": "Nitro client config map (public/configuration)",
"commandocentrum.nitro_client_src": "Nitro-V3 bronmap",
"commandocentrum.nitro_renderer_src": "Nitro Render V3 bronmap"
} }
+27 -26
View File
@@ -5,9 +5,9 @@
"dev": "vite", "dev": "vite",
"dev:atom": "vite --config resources/themes/atom/vite.config.js", "dev:atom": "vite --config resources/themes/atom/vite.config.js",
"build": "vite build", "build": "vite build",
"build:atom": "vite build --config resources/themes/atom/vite.config.js", "build:atom": "vite build --config resources/themes/atom/vite.config.js && php artisan optimize:clear && php artisan optimize && chown -R www-data:www-data public/build",
"build:dusk": "vite build --config resources/themes/dusk/vite.config.js", "build:dusk": "vite build --config resources/themes/dusk/vite.config.js && php artisan optimize:clear && php artisan optimize && chown -R www-data:www-data public/build",
"build:all": "vite build --config resources/themes/atom/vite.config.js && vite build --config resources/themes/dusk/vite.config.js", "build:all": "vite build --config resources/themes/atom/vite.config.js && vite build --config resources/themes/dusk/vite.config.js && php artisan optimize:clear && php artisan optimize && chown -R www-data:www-data public/build",
"preview": "vite preview", "preview": "vite preview",
"format": "prettier \"resources/**/*.{js,ts,vue,blade}\" --ignore-unknown --write", "format": "prettier \"resources/**/*.{js,ts,vue,blade}\" --ignore-unknown --write",
"format:check": "prettier \"resources/**/*.{js,ts,vue,blade.php}\" --check", "format:check": "prettier \"resources/**/*.{js,ts,vue,blade.php}\" --check",
@@ -25,39 +25,40 @@
"rebuild": "yarn clean && yarn install && yarn build" "rebuild": "yarn clean && yarn install && yarn build"
}, },
"devDependencies": { "devDependencies": {
"@alpinejs/focus": "3.15.9", "@alpinejs/focus": "3.15.12",
"@popperjs/core": "2.11.8", "@popperjs/core": "2.11.8",
"@prettier/plugin-php": "0.25.0", "@prettier/plugin-php": "0.25.0",
"@shufo/prettier-plugin-blade": "1.16.2", "@shufo/prettier-plugin-blade": "1.16.2",
"@swc/core": "^1.15.21", "@swc/core": "^1.15.40",
"@tailwindcss/forms": "0.5.11", "@tailwindcss/forms": "0.5.11",
"@tailwindcss/postcss": "4.2.2", "@tailwindcss/postcss": "4.3.0",
"@tailwindcss/typography": "0.5.19", "@tailwindcss/typography": "0.5.19",
"alpinejs": "3.15.9", "alpinejs": "3.15.12",
"autoprefixer": "10.4.20", "autoprefixer": "10.5.0",
"axios": "^1.16.1", "axios": "^1.17.0",
"esbuild": "^0.27.7", "esbuild": "^0.28.0",
"eslint": "^9.39.4", "eslint": "^10.4.1",
"eslint-plugin-vue": "9.32.0", "eslint-plugin-vue": "10.9.2",
"laravel-vite-plugin": "^3.0.1", "laravel-vite-plugin": "^3.1.0",
"lodash": "^4.18.1", "lodash": "^4.18.1",
"postcss": "8.5.8", "postcss": "8.5.15",
"postcss-import": "16.1.0", "postcss-import": "16.1.1",
"prettier": "3.8.1", "prettier": "3.8.3",
"sass-loader": "16.0.4", "sass-loader": "17.0.0",
"stylelint": "^17.6.0", "stylelint": "^17.13.0",
"stylelint-config-standard": "^40.0.0", "stylelint-config-standard": "^40.0.0",
"tailwindcss": "4.2.2", "tailwindcss": "4.3.0",
"turbolinks": "5.2.0", "turbolinks": "5.2.0",
"vite": "^8.0.3" "vite": "^8.0.16"
}, },
"dependencies": { "dependencies": {
"@inertiajs/react": "^3.2.0", "@inertiajs/react": "^3.3.1",
"@vitejs/plugin-react": "^6.0.2", "@vitejs/plugin-react": "^6.0.2",
"flowbite": "2.5.2", "flowbite": "4.0.2",
"react": "^19.2.6", "json5": "^2.2.3",
"react-dom": "^19.2.6", "react": "^19.2.7",
"sass": "1.83.4", "react-dom": "^19.2.7",
"swiper": "^12.1.3" "sass": "1.100.0",
"swiper": "^12.2.0"
} }
} }
+17
View File
@@ -0,0 +1,17 @@
parameters:
level: 6
paths:
- app
- config
- database
- routes
excludePaths:
- vendor
- storage
- bootstrap
- public
- resources
- lang
tmpDir: storage/framework/cache/phpstan
checkMissingIterableValueType: false
checkGenericClassInNonGenericObjectType: false
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 B

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 626 B

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 B

Executable → Regular
+27 -9
View File
@@ -1,7 +1,14 @@
{ {
"_axios-B2OU-7LW.js": { "_axios-Bb9VWCvi.js": {
"file": "assets/axios-B2OU-7LW.js", "file": "assets/axios-Bb9VWCvi.js",
"name": "axios" "name": "axios",
"imports": [
"_chunk-QTnfLwEv.js"
]
},
"_chunk-QTnfLwEv.js": {
"file": "assets/chunk-QTnfLwEv.js",
"name": "chunk"
}, },
"public/assets/images/background-dark.jpg": { "public/assets/images/background-dark.jpg": {
"file": "assets/background-dark-BfkMu3-0.jpg", "file": "assets/background-dark-BfkMu3-0.jpg",
@@ -104,7 +111,7 @@
"src": "public/assets/images/profile/profile-bg.png" "src": "public/assets/images/profile/profile-bg.png"
}, },
"resources/css/global.css": { "resources/css/global.css": {
"file": "assets/global-owlIrRiH.css", "file": "assets/global-waxZ23FQ.css",
"name": "global", "name": "global",
"names": [ "names": [
"global.css" "global.css"
@@ -140,16 +147,26 @@
] ]
}, },
"resources/js/global.js": { "resources/js/global.js": {
"file": "assets/global-TU7mFC54.js", "file": "assets/global-r22-sRCc.js",
"name": "global", "name": "global",
"src": "resources/js/global.js", "src": "resources/js/global.js",
"isEntry": true, "isEntry": true,
"imports": [ "imports": [
"_axios-B2OU-7LW.js" "_chunk-QTnfLwEv.js",
"_axios-Bb9VWCvi.js"
]
},
"resources/js/ssr.jsx": {
"file": "assets/ssr-DdmZbD73.js",
"name": "ssr",
"src": "resources/js/ssr.jsx",
"isEntry": true,
"imports": [
"_chunk-QTnfLwEv.js"
] ]
}, },
"resources/themes/atom/css/app.css": { "resources/themes/atom/css/app.css": {
"file": "assets/app-9mCg36im.css", "file": "assets/app-BwtlzFKR.css",
"name": "app", "name": "app",
"names": [ "names": [
"app.css" "app.css"
@@ -185,12 +202,13 @@
] ]
}, },
"resources/themes/atom/js/app.js": { "resources/themes/atom/js/app.js": {
"file": "assets/app-wUWplMFd.js", "file": "assets/app-CAkt-7PZ.js",
"name": "app", "name": "app",
"src": "resources/themes/atom/js/app.js", "src": "resources/themes/atom/js/app.js",
"isEntry": true, "isEntry": true,
"imports": [ "imports": [
"_axios-B2OU-7LW.js" "_chunk-QTnfLwEv.js",
"_axios-Bb9VWCvi.js"
], ],
"css": [ "css": [
"assets/app-CeYfhhVD.css" "assets/app-CeYfhhVD.css"
+14 -15
View File
@@ -1,4 +1,4 @@
# Uitgebreide PHP locatie detector voor IIS web.config # Extended PHP location detector for IIS web.config
$searchPaths = @( $searchPaths = @(
"C:\PHP", "C:\PHP",
@@ -6,7 +6,6 @@ $searchPaths = @(
"C:\Program Files (x86)\PHP", "C:\Program Files (x86)\PHP",
"C:\php7", "C:\php7",
"C:\php8", "C:\php8",
"C:\xampp\php",
"C:\wamp\bin\php", "C:\wamp\bin\php",
"C:\wamp64\bin\php", "C:\wamp64\bin\php",
"C:\laragon\bin\php", "C:\laragon\bin\php",
@@ -20,11 +19,11 @@ $searchPaths = @(
$phpPath = $null $phpPath = $null
# Methode 1: Check of php-cgi.exe in PATH staat # Method 1: Check if php-cgi.exe is in PATH
Write-Host "Zoeken naar php-cgi.exe..." -ForegroundColor Cyan Write-Host "Searching for php-cgi.exe..." -ForegroundColor Cyan
$phpPath = (Get-Command php-cgi.exe -ErrorAction SilentlyContinue).Source $phpPath = (Get-Command php-cgi.exe -ErrorAction SilentlyContinue).Source
# Methode 2: Zoek in standaard locaties # Method 2: Search in default locations
if (-not $phpPath) { if (-not $phpPath) {
foreach ($basePath in $searchPaths) { foreach ($basePath in $searchPaths) {
if (Test-Path $basePath) { if (Test-Path $basePath) {
@@ -38,11 +37,11 @@ if (-not $phpPath) {
} }
} }
# Methode 3: Zoek in alle C: schijf (alleen top-level folders voor snelheid) # Method 3: Search entire C: drive (top-level folders only for speed)
if (-not $phpPath) { if (-not $phpPath) {
Write-Host "Zoeken in C:\ schijf..." -ForegroundColor Yellow Write-Host "Searching C:\ drive..." -ForegroundColor Yellow
$rootFolders = Get-ChildItem -Path "C:\" -Directory -ErrorAction SilentlyContinue | $rootFolders = Get-ChildItem -Path "C:\" -Directory -ErrorAction SilentlyContinue |
Where-Object { $_.Name -match 'php|xampp|wamp|laragon|inetpub' } Where-Object { $_.Name -match 'php|wamp|laragon|inetpub' }
foreach ($folder in $rootFolders) { foreach ($folder in $rootFolders) {
$found = Get-ChildItem -Path $folder.FullName -Recurse -Filter "php-cgi.exe" -ErrorAction SilentlyContinue | $found = Get-ChildItem -Path $folder.FullName -Recurse -Filter "php-cgi.exe" -ErrorAction SilentlyContinue |
Select-Object -First 1 -ExpandProperty FullName Select-Object -First 1 -ExpandProperty FullName
@@ -53,7 +52,7 @@ if (-not $phpPath) {
} }
} }
# Methode 4: Check Windows Registry voor PHP installaties # Method 4: Check Windows Registry for PHP installations
if (-not $phpPath) { if (-not $phpPath) {
$regPaths = @( $regPaths = @(
"HKLM:\SOFTWARE\PHP", "HKLM:\SOFTWARE\PHP",
@@ -74,7 +73,7 @@ if (-not $phpPath) {
} }
if ($phpPath) { if ($phpPath) {
Write-Host "`nGevonden: $phpPath" -ForegroundColor Green Write-Host "`nFound: $phpPath" -ForegroundColor Green
$webConfigPath = ".\web.config" $webConfigPath = ".\web.config"
if (Test-Path $webConfigPath) { if (Test-Path $webConfigPath) {
@@ -83,14 +82,14 @@ if ($phpPath) {
if ($content -ne $newContent) { if ($content -ne $newContent) {
$newContent | Set-Content $webConfigPath -NoNewline $newContent | Set-Content $webConfigPath -NoNewline
Write-Host "Handler succesvol geupdate in web.config!" -ForegroundColor Green Write-Host "Handler successfully updated in web.config!" -ForegroundColor Green
} else { } else {
Write-Host "Handler was al correct ingesteld." -ForegroundColor Yellow Write-Host "Handler was already set correctly." -ForegroundColor Yellow
} }
} else { } else {
Write-Host "web.config niet gevonden in huidige directory!" -ForegroundColor Red Write-Host "web.config not found in current directory!" -ForegroundColor Red
} }
} else { } else {
Write-Host "`nKon php-cgi.exe niet vinden." -ForegroundColor Red Write-Host "`nCould not find php-cgi.exe." -ForegroundColor Red
Write-Host "Controleer of PHP correct is geinstalleerd." -ForegroundColor Red Write-Host "Check if PHP is installed correctly." -ForegroundColor Red
} }
+1 -1
View File
@@ -5,7 +5,7 @@ use Illuminate\Http\Request;
/** /**
* Bulletproof Cross-Platform Bootstrap * Bulletproof Cross-Platform Bootstrap
* Works on: Linux (Nginx/Apache), Windows (IIS/XAMPP), Docker * Works on: Linux (Nginx/Apache), Windows (IIS), Docker
*/ */
// Windows path fix // Windows path fix
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More