Of course! This is an ambitious and exciting project. Building a WHMCS alternative on a modern framework like Laravel is an excellent choice. Your initial planning is very detailed, which is fantastic.
The New "WHIMS" Directory Structure (Laravel 12.x)
/whims_project # Your Laravel project root
│
├── app/ # Core Application Logic (The "Heart" of WHIMS)
│ ├── Console/Commands/ # Custom artisan commands (e.g., cron jobs)
│ ├── Events/ # Your "Hooks" are defined here as Event classes
│ │ ├── InvoicePaid.php
│ │ └── UserRegistered.php
│ ├── Exceptions/
│ ├── Http/
│ │ ├── Controllers/
│ │ │ ├── Admin/ # Controllers for the Admin Area
│ │ │ │ ├── DashboardController.php
│ │ │ │ └── Settings/
│ │ │ │ ├── GeneralSettingsController.php
│ │ │ │ └── PaymentGatewayController.php
│ │ │ └── Client/ # Controllers for the Client Area
│ │ │ ├── DashboardController.php
│ │ │ └── InvoiceController.php
│ │ ├── Middleware/ # Security, Auth, Theme & Language Loading
│ │ └── Requests/ # Form validation
│ ├── Interfaces/ # Your common interfaces
│ │ └── ModuleInterface.php
│ ├── Listeners/ # Your "Hook Files" are Listeners that react to Events
│ │ ├── SendWelcomeEmail.php # Reacts to UserRegistered event
│ │ └── ProvisionNewService.php # Reacts to InvoicePaid event
│ ├── Models/ # Core Eloquent Models (User, Invoice, Product, etc.)
│ ├── Providers/ # Service Providers are key for your custom systems
│ │ ├── AppServiceProvider.php
│ │ ├── AuthServiceProvider.php
│ │ ├── EventServiceProvider.php # Maps Events to Listeners (Hooks)
│ │ ├── RouteServiceProvider.php
│ │ ├── ModuleServiceProvider.php # Custom: To discover and boot all modules
│ │ ├── ThemeServiceProvider.php # Custom: To load the active theme
│ │ └── TranslationServiceProvider.php # Custom: To handle language overrides
│ └── Rules/ # Custom validation rules
│
├── bootstrap/
├── config/ # All configuration files
│ ├── app.php
│ ├── database.php
│ └── whims.php # Custom config for WHIMS (e.g., active_theme, etc.)
│
├── database/ # Migrations, Seeders, Factories
│
├── lang/ # Default language files (Laravel's standard)
│ ├── en/
│ │ └── messages.php
│ └── es/
│ └── messages.php
│
├── Modules/ # <<< YOUR CENTRALIZED ADDON SYSTEM LIVES HERE
│ │ # This directory will be managed by a package like nwidart/laravel-modules
│ ├── Addons/
│ │ └── SampleAddon/
│ │ ├── app/ # Module-specific Laravel structure
│ │ │ ├── Http/Controllers/
│ │ │ ├── Models/
│ │ │ └── Providers/
│ │ ├── database/migrations/
│ │ ├── resources/
│ │ │ ├── lang/
│ │ │ └── views/
│ │ │ ├── admin/
│ │ │ └── client/
│ │ ├── routes/
│ │ │ ├── api.php
│ │ │ └── web.php
│ │ ├── composer.json # Module-specific dependencies
│ │ ├── module.json # Module definition file
│ │ └── SampleAddonProvider.php # The module's entry point
│ ├── Gateways/
│ │ └── PayU/
│ ├── Registrars/
│ │ └── SampleRegistrar/
│ ├── Reports/
│ │ └── SalesByMonth/
│ ├── Servers/
│ │ └── cPanel/
│ └── Widgets/
│ └── RecentActivity/
│
├── public/ # <<< WEB SERVER ROOT (for Nginx/Apache)
│ ├── storage/ # Symlink to storage/app/public
│ ├── themes/ # Public assets for themes (CSS, JS, Images)
│ │ ├── default/
│ │ └── custom_theme/
│ └── index.php # Laravel's entry point
│
├── resources/ # Frontend source files
│ ├── css/
│ ├── js/
│ ├── lang_overrides/ # <<< YOUR CUSTOM LANGUAGE OVERRIDE DIRECTORY
│ │ └── en/
│ │ └── messages.php # Contains only the strings you want to override
│ ├── themes/ # <<< YOUR THEME TEMPLATES
│ │ ├── default/ # Default Theme
│ │ │ ├── layouts/
│ │ │ │ └── app.blade.php
│ │ │ ├── client/
│ │ │ │ ├── dashboard.blade.php
│ │ │ │ └── invoices.blade.php
│ │ │ └── info.json # Theme metadata (name, author, etc.)
│ │ └── custom_theme/ # A second theme
│ └── views/ # Core system views (e.g., error pages)
│
├── routes/ # Route definitions
│ ├── web.php # Client area routes
│ ├── admin.php # Admin area routes (custom)
│ └── api.php
│
├── storage/ # App-generated files (logs, cache, file uploads)
│ ├── app/
│ │ ├── public/ # Files to be publicly accessible (e.g. company logo)
│ │ └── attachments/ # Secure files like ticket attachments
│ ├── framework/
│ └── logs/
│
├── tests/
├── vendor/ # Composer dependencies
├── .env # <<< YOUR configuration.php replacement
├── artisan
└── composer.json
Step-by-Step Guide to Start Development
Step 1: Initial Laravel Setup
# 1. Install Laravel via Composer
composer create-project laravel/laravel whims_project
# 2. Navigate into the project directory
cd whims_project
# 3. Set up your environment file. Copy the example file.
cp .env.example .env
# 4. Open .env and configure your database credentials (DB_DATABASE, DB_USERNAME, DB_PASSWORD)
# and set your APP_URL.
# Example:
# APP_NAME=WHIMS
# APP_URL=http://whims.test
# DB_CONNECTION=mysql
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=whims_db
# DB_USERNAME=root
# DB_PASSWORD=
# 5. Generate the application key
php artisan key:generate
# 6. Run initial migrations to create users table, etc.
php artisan migrate
Step 2: The Modular System (Your Addons, Gateways, etc.)
# 1. Install the package
composer require nwidart/laravel-modules
# 2. Publish the package's configuration
php artisan vendor:publish --provider="Nwidart\Modules\LaravelModulesServiceProvider"
# 3. Configure it. Open config/modules.php.
# - Inside the 'paths.modules' key, you can change the default `Modules`
# directory name if you wish.
# - Inside the 'paths.generator' array, you can define custom paths for
# different module types as you requested. This is powerful!
# For example, you can group them visually.
'paths' => [
'modules' => base_path('Modules'),
'assets' => public_path('modules'),
'migration' => base_path('database/migrations'),
'generator' => [
// This is an example to create different module "types"
'addon' => ['path' => 'Addons', 'generate' => true],
'gateway' => ['path' => 'Gateways', 'generate' => true],
'registrar' => ['path' => 'Registrars', 'generate' => true],
'server' => ['path' => 'Servers', 'generate' => true],
'widget' => ['path' => 'Widgets', 'generate' => true],
// ... and so on for all your types
],
],
# The 'server' part matches the generator key we just configured
php artisan module:make cPanel server
Step 3: The Theme System
Create a config/whims.php file: // config/whims.php <?php return [ 'theme' => [ 'active_theme' => env('WHIMS_ACTIVE_THEME', 'default'), ], ];Add WHIMS_ACTIVE_THEME=default to your .env file. Create a ThemeServiceProvider.php: php artisan make:provider ThemeServiceProviderEdit the new provider: // app/Providers/ThemeServiceProvider.php <?php namespace App\Providers; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\View; use Illuminate\Support\ServiceProvider; class ThemeServiceProvider extends ServiceProvider { public function boot(): void { $activeTheme = Config::get('whims.theme.active_theme', 'default'); $themePath = resource_path('themes/' . $activeTheme); // Add the theme's view folder to the list of locations Laravel checks // This allows you to use `view('client.dashboard')` and it will find // the file in your active theme folder. View::addLocation($themePath); } }Register the provider: Add \App\Providers\ThemeServiceProvider::class, to the providers array in config/app.php.
Step 4: The Language Override System
Create TranslationServiceProvider.php: php artisan make:provider TranslationServiceProviderEdit the new provider: // app/Providers/TranslationServiceProvider.php <?php namespace App\Providers; use Illuminate\Translation\TranslationServiceProvider as BaseTranslationServiceProvider; use Illuminate\Translation\FileLoader; class TranslationServiceProvider extends BaseTranslationServiceProvider { public function register() { // First, register the standard translator parent::register(); // Now, override the loader with our custom one that knows about overrides $this->app->singleton('translation.loader', function ($app) { // The default lang path $defaultPath = $app['path.lang']; // Our custom override path $overridePath = resource_path('lang_overrides'); // Our custom loader will check the override path first return new FileLoader($app['files'], $defaultPath, $overridePath); }); } }Note: A more robust version of this would extend FileLoader to merge arrays instead of just replacing files. But this demonstrates the concept. Register it: In config/app.php,replace Illuminate\Translation\TranslationServiceProvider::class with App\Providers\TranslationServiceProvider::class.
Step 5: Hooks as Events & Listeners
Define an Event (The "Hook"): php artisan make:event UserRegisteredEdit app/Events/UserRegistered.php to accept the user object. // app/Events/UserRegistered.php public function __construct(public \App\Models\User $user) {}Create a Listener (The "Hook File"): # This listener will react to the event php artisan make:listener SendWelcomeEmail --event=UserRegisteredWrite the Listener Logic: // app/Listeners/SendWelcomeEmail.php use App\Events\UserRegistered; use Illuminate\Support\Facades\Mail; public function handle(UserRegistered $event): void { // Access data from the event: $event->user // Mail::to($event->user->email)->send(new WelcomeEmail($event->user)); \Log::info('A new user has registered: ' . $event->user->name); }Trigger the Event: In your user registration controller, after the user is saved:// In your Auth/RegisterController or similar use App\Events\UserRegistered; $user = User::create([...]); // This "triggers the hook" for all listeners to react event(new UserRegistered($user));
Step 6: Widget System Explained
Create a Widget Module: php artisan module:make RecentActivity widgetWidget Logic: The widget's controller (Modules/Widgets/RecentActivity/app/Http/Controllers/RecentActivityController.php) would fetch the data (e.g., last 5 invoices, last 5 tickets).Widget View: It will have a simple Blade view (Modules/Widgets/RecentActivity/resources/views/widget.blade.php) to display the data.Rendering Widgets: On your main admin dashboard view (resources/themes/default/admin/dashboard.blade.php), you can render the active widgets:{{-- resources/themes/default/admin/dashboard.blade.php --}} <div class="dashboard-widgets"> @foreach ($activeWidgets as $widget) {{-- This assumes you have a service that loads widget info. The service would call the widget's controller/method to get its content. --}} @livewire($widget->livewire_component) {{-- OR --}} @include($widget->view_path, ['data' => $widget->getData()]) @endforeach </div>This gives you a powerful, dynamic dashboard where admins can enable/disable widgets from a settings page.
Step 7: Nginx & Apache Compatibility
server {
listen 80;
server_name whims.in www.whims.in;
root /path/to/your/whims_project/public; # <-- IMPORTANT
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
index index.php index.html index.htm;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock; # Adjust to your PHP-FPM version
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
<VirtualHost *:80>
ServerName whims.in
DocumentRoot "/path/to/your/whims_project/public" # <-- IMPORTANT
<Directory "/path/to/your/whims_project/public">
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Comments
Post a Comment