First Commit

This commit is contained in:
marco
2026-09-01 10:46:29 -03:00
commit bf96b77e9c
10000 changed files with 3508600 additions and 0 deletions
+692
View File
@@ -0,0 +1,692 @@
<?php
namespace Modules\Company\Controllers;
use App\Controllers\BaseAdminController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Modules\Company\Models\Company;
use App\Models\Module;
use App\Helpers\Log;
use App\Helpers\Crypto;
use Intervention\Image\ImageManagerStatic as Image;
use Kernel\License;
class CompanyController extends BaseAdminController
{
public function index(Request $request): Response
{
$user = $this->getCurrentUser();
// Allow access to SuperAdmin, Admin, or Member with company.view permission
$hasAccess = $user && (
$user->isSuperAdmin() ||
$user->isAdmin() ||
($user->isMember() && $user->hasPermission('company.view'))
);
if (!$hasAccess) {
return new RedirectResponse(url('admin/dashboard?error=unauthorized'));
}
if ($user->isSuperAdmin()) {
$companies = Company::all();
} else {
$companies = Company::where('bus_id', $user->users_bus_id)->get();
}
return $this->renderAdminView('company/views/admin/index.twig', [
'companies' => $companies,
'title' => 'Companies Management',
'can_create_company' => $user->isSuperAdmin() && License::canCreateCompany()
]);
}
public function settings(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user) {
return new RedirectResponse(url('login'));
}
if ($user->isSuperAdmin()) {
return new RedirectResponse(url('admin/companies'));
}
return $this->edit($request, $user->users_bus_id);
}
public function create(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new RedirectResponse(url('admin/dashboard?error=unauthorized'));
}
if (!License::canCreateCompany()) {
return new RedirectResponse(url('admin/companies?error=license_limit'));
}
$error = $request->query->get('error');
$errorMessage = $error ? $this->getErrorMessage($error) : null;
$modules = Module::active()->ordered()->get();
return $this->renderAdminView('company/views/admin/form.twig', [
'title' => 'Create Company',
'error' => $errorMessage,
'target_company' => null,
'modules' => $modules
]);
}
public function store(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new RedirectResponse(url('admin/dashboard?error=unauthorized'));
}
$currentUserId = $_SESSION['user_id'] ?? null;
$currentUserRole = $_SESSION['user_role'] ?? null;
$currentUserBus = $_SESSION['user_idbus'] ?? null;
$data = $request->request->all();
// Basic validation
if (empty($data['name']) || empty($data['email'])) {
return new RedirectResponse(url('admin/companies/create?error=missing_fields'));
}
// Email format validation
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
return new RedirectResponse(url('admin/companies/create?error=invalid_email'));
}
// Check if email exists
if (Company::where('bus_email', $data['email'])->exists()) {
return new RedirectResponse(url('admin/companies/create?error=email_exists'));
}
// Generate slug from name
$slug = strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $data['name'])));
// Check if slug exists
if (Company::where('bus_slug', $slug)->exists()) {
$slug .= '-' . time();
}
$company = new Company();
$company->bus_name = $data['name'];
$company->bus_slug = $slug;
$company->bus_description = $data['description'] ?? '';
$company->bus_email = $data['email'];
$company->bus_phone = $data['phone'] ?? '';
$company->bus_address = $data['address'] ?? '';
$company->bus_city = $data['city'] ?? '';
$company->bus_state = $data['state'] ?? '';
$company->bus_country = $data['country'] ?? '';
$company->bus_postal_code = $data['postal_code'] ?? '';
$company->bus_website = $data['website'] ?? '';
$company->bus_tax_id = $data['tax_id'] ?? '';
$company->bus_type = $data['bus_type'] ?? 'business';
$company->bus_status = $data['status'] ?? 'active';
$company->platform_name = $data['platform_name'] ?? '';
// Encrypt OpenRouter API Key if present
$newApiKey = $data['openrouter_api_key'] ?? '';
if (!empty($newApiKey) && strpos($newApiKey, '***') === false) {
$company->openrouter_api_key = Crypto::encrypt($newApiKey);
}
$company->ai_provider = $data['ai_provider'] ?? 'openrouter';
$company->ai_model = $data['ai_model'] ?? 'google/gemini-2.0-flash-001';
$company->ai_endpoint = $data['ai_endpoint'] ?? '';
// Settings are now handled via modules table
// $company->bus_settings = $data['bus_settings'] ?? [];
$company->save();
// Handle modules - ONLY Super Admin can change which modules a company has access to
if ($user->isSuperAdmin()) {
$modulesData = $request->request->all()['modules'] ?? [];
$syncData = [];
foreach ($modulesData as $moduleId => $settings) {
$syncData[$moduleId] = [
'bus_modules_is_enabled' => isset($settings['enabled']),
'bus_modules_enabled_by' => $currentUserId,
'bus_modules_enabled_at' => \Carbon\Carbon::now(),
'bus_modules_show_public_nav' => isset($settings['public']),
'bus_modules_show_dashboard_nav' => isset($settings['dashboard'])
];
}
if (!empty($syncData)) {
$company->modules()->sync($syncData);
} else {
$company->modules()->detach();
}
}
if ($request->files->has('logo')) {
$file = $request->files->get('logo');
if ($file && $file->isValid()) {
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (in_array($file->getMimeType(), $allowedMimes)) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/logos';
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$formatSetting = strtolower((string) get_env('IMG_FORMAT', 'webp'));
$format = $formatSetting === 'auto' ? 'webp' : $formatSetting;
$quality = env_int('IMG_QUALITY', 80);
$resolutionPercent = env_int('IMG_RESOLUTION', 100);
$image = Image::make($file->getPathname());
$width = $image->width();
$height = $image->height();
if ($resolutionPercent > 0 && $resolutionPercent < 100) {
$newW = (int) round($width * $resolutionPercent / 100);
$newH = (int) round($height * $resolutionPercent / 100);
$image->resize($newW, $newH, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
}
$ext = $format === 'jpeg' || $format === 'jpg' ? 'jpg' : ($format === 'png' ? 'png' : ($format === 'gif' ? 'gif' : 'webp'));
$filename = 'logo_' . $company->bus_id . '_' . time() . '.' . $ext;
if ($format === 'jpeg' || $format === 'jpg') {
$image->interlace(true);
$image->encode('jpeg', max(1, min(100, $quality)));
} elseif ($format === 'png') {
$pngCompression = max(0, min(9, (int) round((100 - max(1, min(100, $quality))) / 10)));
$image->encode('png', $pngCompression);
} elseif ($format === 'gif') {
$image->encode('gif');
} else {
$image->encode('webp', max(1, min(100, $quality)));
}
$image->save($uploadDir . '/' . $filename);
// Delete old logo if exists (check both old and new directories)
if ($company->bus_logo) {
$oldDirs = [
dirname(__DIR__, 4) . '/storage/uploads/logos',
dirname(__DIR__, 4) . '/storage/uploads/company/logos'
];
foreach ($oldDirs as $dir) {
$oldFile = $dir . '/' . $company->bus_logo;
if (file_exists($oldFile)) {
unlink($oldFile);
break; // Delete only once
}
}
}
$company->bus_logo = $filename;
}
}
}
// Handle business card upload
if ($request->files->has('business_card')) {
$file = $request->files->get('business_card');
if ($file && $file->isValid()) {
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (in_array($file->getMimeType(), $allowedMimes)) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/business-cards';
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$formatSetting = strtolower((string) get_env('IMG_FORMAT', 'webp'));
$format = $formatSetting === 'auto' ? 'webp' : $formatSetting;
$quality = env_int('IMG_QUALITY', 80);
$resolutionPercent = env_int('IMG_RESOLUTION', 100);
$image = Image::make($file->getPathname());
$width = $image->width();
$height = $image->height();
if ($resolutionPercent > 0 && $resolutionPercent < 100) {
$newW = (int) round($width * $resolutionPercent / 100);
$newH = (int) round($height * $resolutionPercent / 100);
$image->resize($newW, $newH, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
}
$ext = $format === 'jpeg' || $format === 'jpg' ? 'jpg' : ($format === 'png' ? 'png' : ($format === 'gif' ? 'gif' : 'webp'));
$filename = 'business_card_' . $company->bus_id . '_' . time() . '.' . $ext;
if ($format === 'jpeg' || $format === 'jpg') {
$image->interlace(true);
$image->encode('jpeg', max(1, min(100, $quality)));
} elseif ($format === 'png') {
$pngCompression = max(0, min(9, (int) round((100 - max(1, min(100, $quality))) / 10)));
$image->encode('png', $pngCompression);
} elseif ($format === 'gif') {
$image->encode('gif');
} else {
$image->encode('webp', max(1, min(100, $quality)));
}
$image->save($uploadDir . '/' . $filename);
// Delete old business card if exists (check both old and new directories)
if ($company->bus_business_card) {
$oldDirs = [
dirname(__DIR__, 4) . '/storage/uploads/business-cards',
dirname(__DIR__, 4) . '/storage/uploads/company/business-cards'
];
foreach ($oldDirs as $dir) {
$oldFile = $dir . '/' . $company->bus_business_card;
if (file_exists($oldFile)) {
unlink($oldFile);
break; // Delete only once
}
}
}
$company->bus_business_card = $filename;
}
}
}
// Handle document upload
if ($request->files->has('document')) {
$file = $request->files->get('document');
if ($file && $file->isValid()) {
$allowedMimes = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation'
];
if (in_array($file->getMimeType(), $allowedMimes)) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/documents';
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$originalName = $file->getClientOriginalName();
$filename = 'document_' . $company->bus_id . '_' . time() . '_' . $originalName;
$file->move($uploadDir, $filename);
// Delete old document if exists (check both old and new directories)
if ($company->bus_document) {
$oldDirs = [
dirname(__DIR__, 4) . '/storage/uploads/documents',
dirname(__DIR__, 4) . '/storage/uploads/company/documents'
];
foreach ($oldDirs as $dir) {
$oldFile = $dir . '/' . $company->bus_document;
if (file_exists($oldFile)) {
unlink($oldFile);
break; // Delete only once
}
}
}
$company->bus_document = $filename;
}
}
}
$company->save();
// Restore session to ensure super_admin remains logged
$_SESSION['user_id'] = $currentUserId;
$_SESSION['user_role'] = $currentUserRole;
$_SESSION['user_idbus'] = $currentUserBus;
return new RedirectResponse(url('admin/companies'));
}
public function show(Request $request, string $id): Response
{
$user = $this->getCurrentUser();
if (!$user || (!$user->isSuperAdmin() && $user->users_bus_id != $id)) {
return new RedirectResponse(url('admin/dashboard?error=unauthorized'));
}
$company = Company::find($id);
if (!$company) {
return new RedirectResponse(url('admin/companies'));
}
return $this->renderAdminView('company/views/admin/show.twig', [
'company' => $company,
'title' => 'Company Details'
]);
}
public function edit(Request $request, string $id): Response
{
$user = $this->getCurrentUser();
if (!$user || (!$user->isSuperAdmin() && $user->users_bus_id != $id)) {
return new RedirectResponse(url('admin/dashboard?error=unauthorized'));
}
$company = Company::find($id);
if (!$company) {
return new RedirectResponse(url('admin/companies'));
}
// Mask the API Key for the view
if ($company->openrouter_api_key) {
$company->openrouter_api_key = substr(Crypto::decrypt($company->openrouter_api_key), 0, 8) . '****************';
}
$error = $request->query->get('error');
$errorMessage = $error ? $this->getErrorMessage($error) : null;
$modules = Module::active()->ordered()->get();
return $this->renderAdminView('company/views/admin/form.twig', [
'target_company' => $company,
'title' => 'Edit Company',
'error' => $errorMessage,
'modules' => $modules
]);
}
public function update(Request $request, string $id): Response
{
$user = $this->getCurrentUser();
if (!$user || (!$user->isSuperAdmin() && $user->users_bus_id != $id)) {
return new RedirectResponse(url('admin/dashboard?error=unauthorized'));
}
$currentUserId = $_SESSION['user_id'] ?? null;
$currentUserRole = $_SESSION['user_role'] ?? null;
$currentUserBus = $_SESSION['user_idbus'] ?? null;
$company = Company::find($id);
if (!$company) {
return new RedirectResponse(url('admin/companies'));
}
$data = $request->request->all();
// Basic validation
if (empty($data['name']) || empty($data['email'])) {
return new RedirectResponse(url('admin/companies/' . $id . '/edit?error=missing_fields'));
}
// Email format validation
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
return new RedirectResponse(url('admin/companies/' . $id . '/edit?error=invalid_email'));
}
// Check if email exists (exclude current company)
if ($data['email'] !== $company->bus_email && Company::where('bus_email', $data['email'])->exists()) {
return new RedirectResponse(url('admin/companies/' . $id . '/edit?error=email_exists'));
}
$company->bus_name = $data['name'];
$company->bus_description = $data['description'] ?? '';
$company->bus_email = $data['email'];
$company->bus_phone = $data['phone'] ?? '';
$company->bus_address = $data['address'] ?? '';
$company->bus_city = $data['city'] ?? '';
$company->bus_state = $data['state'] ?? '';
$company->bus_country = $data['country'] ?? '';
$company->bus_postal_code = $data['postal_code'] ?? '';
$company->bus_website = $data['website'] ?? '';
$company->bus_tax_id = $data['tax_id'] ?? '';
$company->bus_type = $data['bus_type'] ?? 'business';
$company->bus_status = $data['status'] ?? 'active';
$company->platform_name = $data['platform_name'] ?? '';
// Encrypt OpenRouter API Key if present
$newApiKey = $data['openrouter_api_key'] ?? '';
if (!empty($newApiKey) && strpos($newApiKey, '***') === false) {
$company->openrouter_api_key = Crypto::encrypt($newApiKey);
}
$company->ai_provider = $data['ai_provider'] ?? 'openrouter';
$company->ai_model = $data['ai_model'] ?? 'google/gemini-2.0-flash-001';
$company->ai_endpoint = $data['ai_endpoint'] ?? '';
// Settings are now handled via modules table
// $company->bus_settings = $data['bus_settings'] ?? [];
// Update slug if name changed
if ($data['name'] !== $company->getOriginal('name')) {
$slug = strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $data['name'])));
// Check if slug exists (exclude current company)
if ($slug !== $company->bus_slug && Company::where('bus_slug', $slug)->exists()) {
$slug .= '-' . time();
}
$company->bus_slug = $slug;
}
$company->save();
// Handle modules - ONLY Super Admin can change which modules a company has access to
if ($user->isSuperAdmin()) {
$modulesData = $request->request->all()['modules'] ?? [];
$syncData = [];
foreach ($modulesData as $moduleId => $settings) {
$syncData[$moduleId] = [
'bus_modules_is_enabled' => isset($settings['enabled']),
'bus_modules_enabled_by' => $currentUserId,
'bus_modules_enabled_at' => \Carbon\Carbon::now(),
'bus_modules_show_public_nav' => isset($settings['public']),
'bus_modules_show_dashboard_nav' => isset($settings['dashboard'])
];
}
if (!empty($syncData)) {
$company->modules()->sync($syncData);
} else {
$company->modules()->detach();
}
}
if ($request->files->has('logo')) {
$file = $request->files->get('logo');
if ($file && $file->isValid()) {
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (in_array($file->getMimeType(), $allowedMimes)) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/logos';
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$formatSetting = strtolower((string) get_env('IMG_FORMAT', 'webp'));
$format = $formatSetting === 'auto' ? 'webp' : $formatSetting;
$quality = env_int('IMG_QUALITY', 80);
$resolutionPercent = env_int('IMG_RESOLUTION', 100);
$image = Image::make($file->getPathname());
$width = $image->width();
$height = $image->height();
if ($resolutionPercent > 0 && $resolutionPercent < 100) {
$newW = (int) round($width * $resolutionPercent / 100);
$newH = (int) round($height * $resolutionPercent / 100);
$image->resize($newW, $newH, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
}
$ext = $format === 'jpeg' || $format === 'jpg' ? 'jpg' : ($format === 'png' ? 'png' : ($format === 'gif' ? 'gif' : 'webp'));
$filename = 'logo_' . $company->bus_id . '_' . time() . '.' . $ext;
if ($format === 'jpeg' || $format === 'jpg') {
$image->interlace(true);
$image->encode('jpeg', max(1, min(100, $quality)));
} elseif ($format === 'png') {
$pngCompression = max(0, min(9, (int) round((100 - max(1, min(100, $quality))) / 10)));
$image->encode('png', $pngCompression);
} elseif ($format === 'gif') {
$image->encode('gif');
} else {
$image->encode('webp', max(1, min(100, $quality)));
}
$image->save($uploadDir . '/' . $filename);
if ($company->bus_logo) {
$oldDirs = [
dirname(__DIR__, 4) . '/storage/uploads/logos',
dirname(__DIR__, 4) . '/storage/uploads/company/logos'
];
foreach ($oldDirs as $dir) {
$oldFile = $dir . '/' . $company->bus_logo;
if (file_exists($oldFile)) {
unlink($oldFile);
break;
}
}
}
$company->bus_logo = $filename;
}
}
}
// Handle business card upload
if ($request->files->has('business_card')) {
$file = $request->files->get('business_card');
if ($file && $file->isValid()) {
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (in_array($file->getMimeType(), $allowedMimes)) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/business-cards';
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$formatSetting = strtolower((string) get_env('IMG_FORMAT', 'webp'));
$format = $formatSetting === 'auto' ? 'webp' : $formatSetting;
$quality = env_int('IMG_QUALITY', 80);
$resolutionPercent = env_int('IMG_RESOLUTION', 100);
$image = Image::make($file->getPathname());
$width = $image->width();
$height = $image->height();
if ($resolutionPercent > 0 && $resolutionPercent < 100) {
$newW = (int) round($width * $resolutionPercent / 100);
$newH = (int) round($height * $resolutionPercent / 100);
$image->resize($newW, $newH, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
}
$ext = $format === 'jpeg' || $format === 'jpg' ? 'jpg' : ($format === 'png' ? 'png' : ($format === 'gif' ? 'gif' : 'webp'));
$filename = 'business_card_' . $company->bus_id . '_' . time() . '.' . $ext;
if ($format === 'jpeg' || $format === 'jpg') {
$image->interlace(true);
$image->encode('jpeg', max(1, min(100, $quality)));
} elseif ($format === 'png') {
$pngCompression = max(0, min(9, (int) round((100 - max(1, min(100, $quality))) / 10)));
$image->encode('png', $pngCompression);
} elseif ($format === 'gif') {
$image->encode('gif');
} else {
$image->encode('webp', max(1, min(100, $quality)));
}
$image->save($uploadDir . '/' . $filename);
if ($company->bus_business_card) {
$oldDirs = [
dirname(__DIR__, 4) . '/storage/uploads/business-cards',
dirname(__DIR__, 4) . '/storage/uploads/company/business-cards'
];
foreach ($oldDirs as $dir) {
$oldFile = $dir . '/' . $company->bus_business_card;
if (file_exists($oldFile)) {
unlink($oldFile);
break;
}
}
}
$company->bus_business_card = $filename;
}
}
}
// Handle document upload
if ($request->files->has('document')) {
$file = $request->files->get('document');
if ($file && $file->isValid()) {
$allowedMimes = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation'
];
if (in_array($file->getMimeType(), $allowedMimes)) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/documents';
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$originalName = $file->getClientOriginalName();
$filename = 'document_' . $company->bus_id . '_' . time() . '_' . $originalName;
$file->move($uploadDir, $filename);
if ($company->bus_document) {
$oldDirs = [
dirname(__DIR__, 4) . '/storage/uploads/documents',
dirname(__DIR__, 4) . '/storage/uploads/company/documents'
];
foreach ($oldDirs as $dir) {
$oldFile = $dir . '/' . $company->bus_document;
if (file_exists($oldFile)) {
unlink($oldFile);
break;
}
}
}
$company->bus_document = $filename;
}
}
}
$company->save();
// Restore session to ensure super_admin remains logged
$_SESSION['user_id'] = $currentUserId;
$_SESSION['user_role'] = $currentUserRole;
$_SESSION['user_idbus'] = $currentUserBus;
return new RedirectResponse(url('admin/companies'));
}
private function getErrorMessage(string $error): string
{
$messages = [
'missing_fields' => 'Por favor, preencha todos os campos obrigatórios.',
'email_exists' => 'Este endereço de e-mail já está registrado.',
'invalid_email' => 'Por favor, insira um endereço de e-mail válido.',
];
return $messages[$error] ?? 'Ocorreu um erro.';
}
public function destroy(Request $request, string $id): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new RedirectResponse(url('admin/dashboard?error=unauthorized'));
}
$company = Company::find($id);
if ($company) {
// Delete logo file if exists
if ($company->bus_logo) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/logos';
if (file_exists($uploadDir . '/' . $company->bus_logo)) {
unlink($uploadDir . '/' . $company->bus_logo);
}
}
// Delete business card file if exists
if ($company->bus_business_card) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/business-cards';
if (file_exists($uploadDir . '/' . $company->bus_business_card)) {
unlink($uploadDir . '/' . $company->bus_business_card);
}
}
// Delete document file if exists
if ($company->bus_document) {
$uploadDir = dirname(__DIR__, 4) . '/storage/uploads/company/documents';
if (file_exists($uploadDir . '/' . $company->bus_document)) {
unlink($uploadDir . '/' . $company->bus_document);
}
}
$company->delete();
}
return new RedirectResponse(url('admin/companies'));
}
}
+147
View File
@@ -0,0 +1,147 @@
<?php
namespace Modules\Company\Middleware;
use Closure;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
class CompanyMiddleware
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$user = $this->getCurrentUser();
if (!$user) {
return $this->unauthorizedResponse($request);
}
// Super Admin can access any company
if ($user->isSuperAdmin()) {
return $next($request);
}
// Routes that do not require a company context
$bypassRoutes = ['/profile', '/logout', '/dashboard'];
if (in_array($request->getPathInfo(), $bypassRoutes)) {
return $next($request);
}
// Get company ID from route or user session
$companyId = $this->getCompanyId($request, $user);
// Check if user can access the company
if (!$this->canAccessCompany($user, $companyId)) {
return $this->forbiddenResponse($request);
}
// Set company context in session for easy access
$_SESSION['current_company_id'] = $companyId;
return $next($request);
}
/**
* Get current authenticated user.
*
* @return \Modules\Auth\Models\User|null
*/
protected function getCurrentUser(): ?\Modules\Auth\Models\User
{
if (!isset($_SESSION['user_id'])) {
return null;
}
try {
if (class_exists('\Modules\Auth\Models\User')) {
return \Modules\Auth\Models\User::find($_SESSION['user_id']);
}
} catch (\Exception $e) {
error_log("Error loading user: " . $e->getMessage());
}
return null;
}
/**
* Get company ID from request or user session.
*
* @param Request $request
* @param \Modules\Auth\Models\User $user
* @return int|null
*/
protected function getCompanyId(Request $request, $user): ?int
{
// 1. Check URL parameter (if present)
// Note: FastRoute parameters are passed to controller, not easily accessible in middleware via standard request
// We can parse the path if necessary, but standard app logic usually relies on session or explicit selection.
// 2. Check Session
if (isset($_SESSION['current_company_id'])) {
return (int) $_SESSION['current_company_id'];
}
// 3. Default to user's primary business ID
return $user->users_bus_id;
}
/**
* Check if user can access the company.
*
* @param \Modules\Auth\Models\User $user
* @param int|null $companyId
* @return bool
*/
protected function canAccessCompany($user, $companyId): bool
{
if (!$companyId) {
return false;
}
// If user belongs to this company directly
if ($user->users_bus_id == $companyId) {
return true;
}
// If user has permission/association to this company (Multi-tenant logic)
// TODO: Implement multi-company association logic if needed
return false;
}
protected function unauthorizedResponse(Request $request)
{
if ($this->expectsJson($request)) {
return new JsonResponse(['message' => 'Unauthorized'], 401);
}
return new Response('', 302, ['Location' => url('/login')]);
}
protected function forbiddenResponse(Request $request)
{
if ($this->expectsJson($request)) {
return new JsonResponse(['message' => 'Forbidden - Access denied to this company'], 403);
}
// Show 403 error page
try {
$view = new \Kernel\View();
return new Response($view->render('errors/403.twig'), 403);
} catch (\Throwable $e) {
return new Response('Access denied to this company', 403);
}
}
protected function expectsJson(Request $request): bool
{
return $request->headers->get('Accept') === 'application/json' ||
$request->headers->get('Content-Type') === 'application/json' ||
str_contains($request->getPathInfo(), '/api/');
}
}
+316
View File
@@ -0,0 +1,316 @@
<?php
namespace Modules\Company\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Company extends Model
{
/**
* The table associated with model.
*
* @var string
*/
protected $table = 'bus';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'bus_id';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'bus_name',
'bus_slug',
'bus_description',
'bus_logo',
'bus_business_card',
'bus_document',
'bus_email',
'bus_phone',
'bus_address',
'bus_city',
'bus_state',
'bus_country',
'bus_postal_code',
'bus_website',
'bus_tax_id',
'bus_type',
'bus_status',
'bus_settings',
'openrouter_api_key',
'ai_provider',
'ai_model',
'ai_endpoint',
'platform_name'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'bus_settings' => 'array',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime'
];
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = [
'created_at',
'updated_at',
'deleted_at'
];
/**
* Get users for the company.
*/
public function users(): HasMany
{
return $this->hasMany(\Modules\Auth\Models\User::class, 'users_bus_id');
}
/**
* Get modules for the company.
*/
public function modules(): BelongsToMany
{
return $this->belongsToMany(\App\Models\Module::class, 'bus_modules', 'bus_modules_bus_id', 'bus_modules_module_id')
->withPivot(['bus_modules_is_enabled', 'bus_modules_settings', 'bus_modules_enabled_by', 'bus_modules_enabled_at', 'bus_modules_show_public_nav', 'bus_modules_show_dashboard_nav'])
->withTimestamps();
}
/**
* Get activity logs for the company.
*/
public function activityLogs(): HasMany
{
return $this->hasMany(\App\Models\ActivityLog::class, 'activity_logs_bus_id');
}
/**
* Get enabled modules only.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeEnabledModules($query)
{
return $query->where('bus_modules.bus_modules_is_enabled', true);
}
/**
* Get active companies only.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeActive($query)
{
return $query->where('bus_status', 'active');
}
/**
* Get companies by type.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $type
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeType($query, string $type)
{
return $query->where('bus_type', $type);
}
/**
* Check if company has specific module enabled.
*
* @param string $moduleName
* @return bool
*/
public function hasModuleEnabled(string $moduleName): bool
{
return $this->modules()
->where('modules.modules_name', $moduleName)
->where('bus_modules.bus_modules_is_enabled', true)
->exists();
}
/**
* Enable module for company.
*
* @param int $moduleId
* @param int $enabledBy
* @param array $settings
* @return bool
*/
public function enableModule(int $moduleId, int $enabledBy, array $settings = []): bool
{
$this->modules()->attach($moduleId, [
'bus_modules_is_enabled' => true,
'bus_modules_settings' => json_encode($settings),
'bus_modules_enabled_by' => $enabledBy,
'bus_modules_enabled_at' => \Carbon\Carbon::now()
]);
return true;
}
/**
* Disable module for company.
*
* @param int $moduleId
* @return bool
*/
public function disableModule(int $moduleId): bool
{
return $this->modules()->updateExistingPivot($moduleId, [
'bus_modules_is_enabled' => false
]);
}
/**
* Get logo URL.
*
* @return string
*/
public function getLogoUrlAttribute(): string
{
if ($this->bus_logo) {
return url('storage/uploads/company/logos/' . $this->bus_logo);
}
return asset_url('img/default-logo.png');
}
/**
* Get business card URL.
*
* @return string
*/
public function getBusinessCardUrlAttribute(): string
{
if ($this->bus_business_card) {
return url('storage/uploads/company/business-cards/' . $this->bus_business_card);
}
return asset_url('img/default-business-card.png');
}
/**
* Get document URL.
*
* @return string
*/
public function getDocumentUrlAttribute(): string
{
if ($this->bus_document) {
return url('storage/uploads/company/documents/' . $this->bus_document);
}
return '#';
}
/**
* Get display name.
*
* @return string
*/
public function getDisplayNameAttribute(): string
{
return $this->bus_name ?: $this->bus_slug;
}
/**
* Get full address.
*
* @return string
*/
public function getFullAddressAttribute(): string
{
$address = [];
if ($this->bus_address) {
$address[] = $this->bus_address;
}
if ($this->bus_city) {
$address[] = $this->bus_city;
}
if ($this->bus_state) {
$address[] = $this->bus_state;
}
if ($this->bus_country) {
$address[] = $this->bus_country;
}
if ($this->bus_postal_code) {
$address[] = $this->bus_postal_code;
}
return implode(', ', $address);
}
/**
* Get setting value.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public function getSetting(string $key, $default = null)
{
$settings = $this->bus_settings ?? [];
return $settings[$key] ?? $default;
}
/**
* Set setting value.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function setSetting(string $key, $value): void
{
$settings = $this->bus_settings ?? [];
$settings[$key] = $value;
$this->bus_settings = $settings;
}
/**
* Get user count.
*
* @return int
*/
public function getUserCountAttribute(): int
{
return $this->users()->count();
}
/**
* Get active user count.
*
* @return int
*/
public function getActiveUserCountAttribute(): int
{
return $this->users()->active()->count();
}
}
@@ -0,0 +1,33 @@
<?php
namespace Modules\Company\Providers;
use Kernel\Application;
use Kernel\View;
use Modules\Company\Models\Company;
use Modules\Company\Middleware\CompanyMiddleware;
class CompanyServiceProvider
{
public function register()
{
}
public function boot()
{
$app = Application::getInstance();
$app->addProtectedMiddleware(new CompanyMiddleware());
try {
if (class_exists(Company::class)) {
$mainCompany = Company::find(1);
if ($mainCompany) {
$view = new View();
$view->getEngine()->addGlobal('main_company', $mainCompany);
}
}
} catch (\Exception $e) {
// Silently fail
}
}
}