403Webshell
Server IP : 46.105.57.169  /  Your IP : 216.73.217.35
Web Server : Apache
System : Linux webm002.cluster120.gra.hosting.ovh.net 6.18.42-ovh-vps-grsec-zfs+ #1 SMP PREEMPT_DYNAMIC Wed Aug 5 15:59:48 CEST 2026 x86_64
User : verseaumee ( 152031)
PHP Version : 8.5.7
Disable Function : _dyuweyrj4,_dyuweyrj4r,dl
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /home/verseaumee/quaadraa/app/Controllers/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /home/verseaumee/quaadraa/app/Controllers/AdminController.php
<?php

namespace App\Controllers;

use App\Core\Lang;
use App\Core\Security;
use App\Models\Database;
use App\Models\SubsidiaryModel;

class AdminController
{
    public function __construct(private Lang $lang) {}

    private function guard(): void
{
    if (session_status() === PHP_SESSION_NONE) session_start();

    if (empty($_SESSION['admin_id'])) {
        header('Location: /admin/login');
        exit;
    }

    $timeout = 1800; 
    if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > $timeout)) {
        session_unset();
        session_destroy();
        Security::log('SESSION_EXPIRED', 'Inactivité détectée pour admin_id: ' . $_SESSION['admin_id']);
        header('Location: /admin/login?expired=1');
        exit;
    }
    $_SESSION['last_activity'] = time(); 

    $sessId = $_SESSION['admin_session_id'] ?? '';
    $row    = Database::fetchOne(
        'SELECT * FROM admin_sessions WHERE id = :id AND admin_id = :aid AND expires_at > NOW()',
        [':id' => $sessId, ':aid' => $_SESSION['admin_id']]
    );

    if (!$row || 
        $row['ip_address'] !== ($_SERVER['REMOTE_ADDR'] ?? '') || 
        $row['user_agent'] !== ($_SERVER['HTTP_USER_AGENT'] ?? '')) {
        
        Security::log('SECURITY_BREACH', 'Tentative de session invalide. IP: ' . ($_SERVER['REMOTE_ADDR'] ?? 'n/a'));
        session_destroy();
        header('Location: /admin/login?expired=1');
        exit;
    }
}

private function checkAccess(array $allowedRoles): void
{
    if (session_status() === PHP_SESSION_NONE) session_start();

    if (!isset($_SESSION['admin_id'])) {
        header('Location: /admin/login');
        exit;
    }

    $admin = \App\Models\Database::fetchOne('SELECT is_active FROM admins WHERE id = ?', [$_SESSION['admin_id']]);
   
    if (!$admin || (int)$admin['is_active'] === 0) {
        session_destroy();
        header('Location: /admin/login');
        exit;
    }

    if ($_SESSION['admin_id'] == 1) {
        return; 
    }

    if (!in_array($_SESSION['admin_role'] ?? '', $allowedRoles)) {
        header('Location: /admin?error=unauthorized');
        exit;
    }
}
    
    private function renderAdmin(string $view, array $data = []): void
    {
        $data['adminLang'] = $this->lang;
        $data['e']         = fn(mixed $v) => Security::e($v);
        $data['csrf']      = Security::csrfToken();
        $data['admin']     = $_SESSION['admin_username'] ?? 'Admin';

        extract($data, EXTR_SKIP);

        $viewFile = dirname(__DIR__, 2) . "/admin/views/{$view}.php";
        if (!file_exists($viewFile)) {
            die("Admin view not found: {$view}");
        }

        require dirname(__DIR__, 2) . '/admin/views/layout.php';
    }
    
public function users(): void
{
    $this->checkAccess(['superadmin']);
    
    $this->renderAdmin('users', [
        'pageTitle'  => 'Gestion des utilisateurs',
        'admins'     => Database::fetchAll('SELECT * FROM admins'),
        'activeMenu' => 'users',
        'csrf'       => \App\Core\Security::csrfToken() 
    ]);
}

    public function userCreate(): void
    {
        $this->checkAccess(['superadmin']);
        Security::verifyCsrf();
        Database::insert('INSERT INTO admins (username, email, password_hash, role, created_at) VALUES (:u, :e, :p, :r, NOW())', [
            ':u' => $_POST['username'], ':e' => $_POST['email'], ':p' => password_hash($_POST['password'], PASSWORD_DEFAULT), ':r' => $_POST['role']
        ]);
        header('Location: /admin/users'); exit;
    }

    public function userToggle(array $params): void
    {
        $this->checkAccess(['superadmin']);
        Security::verifyCsrf();
        if ((int)$params['id'] !== (int)$_SESSION['admin_id']) {
            Database::execute('UPDATE admins SET is_active = NOT is_active WHERE id = :id', [':id' => $params['id']]);
        }
        header('Location: /admin/users'); exit;
    }

    public function userDelete(array $params): void
    {
        $this->checkAccess(['superadmin']);
        Security::verifyCsrf();
        if ((int)$params['id'] !== (int)$_SESSION['admin_id']) {
            Database::execute('DELETE FROM admins WHERE id = :id', [':id' => $params['id']]);
        }
        header('Location: /admin/users'); exit;
    }

    public function loginForm(array $params = []): void
    {
        if (session_status() === PHP_SESSION_NONE) session_start();

        if (!empty($_SESSION['admin_id'])) {
            header('Location: /admin');
            exit;
        }

        $expired = isset($_GET['expired']);
        require dirname(__DIR__, 2) . '/admin/views/login.php';
    }

    public function loginSubmit(array $params = []): void
    {
        if (session_status() === PHP_SESSION_NONE) session_start();
        Security::verifyCsrf();

        if (!Security::rateLimit('admin_login_' . $_SERVER['REMOTE_ADDR'], 5, 300)) {
            $error = 'Trop de tentatives. Réessayez dans 5 minutes.';
            require dirname(__DIR__, 2) . '/admin/views/login.php';
            return;
        }

        $email    = Security::sanitizeEmail($_POST['email'] ?? '');
        $password = $_POST['password'] ?? '';

        $admin = Database::fetchOne(
            'SELECT * FROM admins WHERE email = :email AND is_active = 1',
            [':email' => $email]
        );

        if (!$admin || !Security::verifyPassword($password, $admin['password_hash'])) {
            $error = 'Identifiants incorrects.';
            require dirname(__DIR__, 2) . '/admin/views/login.php';
            return;
        }
        
        session_regenerate_id(true);

        $sessionId = bin2hex(random_bytes(64));
        $expiresAt = date('Y-m-d H:i:s', time() + 7200);

        Database::insert(
            'INSERT INTO admin_sessions (id, admin_id, ip_address, user_agent, expires_at)
             VALUES (:id, :aid, :ip, :ua, :exp)',
            [
                ':id'  => $sessionId,
                ':aid' => $admin['id'],
                ':ip'  => $_SERVER['REMOTE_ADDR'] ?? '',
                ':ua'  => $_SERVER['HTTP_USER_AGENT'] ?? '',
                ':exp' => $expiresAt,
            ]
        );

        Database::execute(
            'UPDATE admins SET last_login = NOW() WHERE id = :id',
            [':id' => $admin['id']]
        );

        $_SESSION['admin_id']         = $admin['id'];
        $_SESSION['admin_username']   = $admin['username'];
        $_SESSION['admin_role']       = $admin['role'];
        $_SESSION['admin_session_id'] = $sessionId;
        $_SESSION['last_activity']    = time();

        header('Location: /admin');
        exit;
    }
   
    public function logout(array $params = []): void
    {
        if (session_status() === PHP_SESSION_NONE) session_start();

        if (!empty($_SESSION['admin_session_id'])) {
            Database::execute(
                'DELETE FROM admin_sessions WHERE id = :id',
                [':id' => $_SESSION['admin_session_id']]
            );
        }

        session_destroy();
        header('Location: /admin/login');
        exit;
    }

    public function dashboard(array $params = []): void
    {
        $this->guard();

        $stats = [
            'news'     => Database::fetchOne('SELECT COUNT(*) AS cnt FROM news')['cnt'] ?? 0,
            'contacts' => Database::fetchOne('SELECT COUNT(*) AS cnt FROM contacts WHERE is_read = 0')['cnt'] ?? 0,
            'subs'     => Database::fetchOne('SELECT COUNT(*) AS cnt FROM subsidiaries')['cnt'] ?? 0,
        ];

        $recentContacts = Database::fetchAll(
            'SELECT * FROM contacts ORDER BY created_at DESC LIMIT 5'
        );

        $this->renderAdmin('dashboard', [
            'pageTitle'      => 'Dashboard',
            'stats'          => $stats,
            'recentContacts' => $recentContacts,
            'activeMenu'     => 'dashboard',
        ]);
    }

    public function pages(array $params = []): void
    {
        $this->guard();
        $pages = Database::fetchAll('SELECT * FROM pages ORDER BY lang, sort_order');

        $this->renderAdmin('pages', [
            'pageTitle'  => 'Gestion des pages',
            'pages'      => $pages,
            'activeMenu' => 'pages',
        ]);
    }

    public function pageEdit(array $params = []): void
    {
        $this->guard();

        $id       = (int)($params['id'] ?? 0);
        $pageData = Database::fetchOne('SELECT * FROM pages WHERE id = :id', [':id' => $id]);

        if (!$pageData) {
            header('Location: /admin/pages');
            exit;
        }

        $this->renderAdmin('page-edit', [
            'pageTitle'  => 'Modifier : ' . mb_substr($pageData['title'], 0, 40),
            'pageData'   => $pageData,
            'errors'     => [],
            'success'    => false,
            'activeMenu' => 'pages',
        ]);
    }

    public function pageUpdate(array $params = []): void
    {
        $this->guard();
        Security::verifyCsrf();

        $id       = (int)($params['id'] ?? 0);
        $pageData = Database::fetchOne('SELECT * FROM pages WHERE id = :id', [':id' => $id]);

        if (!$pageData) {
            header('Location: /admin/pages');
            exit;
        }

        $errors       = [];
        $title        = trim($_POST['title']         ?? '');
        $metaDesc     = trim($_POST['meta_desc']       ?? '');
        $metaKeywords = trim($_POST['meta_keywords']   ?? '');
        $content      = $_POST['content']              ?? '';
        $sortOrder    = max(0, (int)($_POST['sort_order'] ?? 0));
        $isPublished  = isset($_POST['is_published']) ? 1 : 0;
        $heroImagePath = $_POST['current_hero_image'] ?? $pageData['hero_image'];

        if (!empty($_FILES['hero_image']['name']) && $_FILES['hero_image']['error'] === UPLOAD_ERR_OK) {
            $uploadDir = ROOT_PATH . '/assets/images';
            $fileName = Security::secureUpload($_FILES['hero_image'], $uploadDir);
            
            if ($fileName) {
                $heroImagePath = '/assets/images/' . $fileName;
            } else {
                $errors['hero_image'] = 'Format d\'image non autorisé.';
            }
        }

        if (strlen($title) < 2) {
            $errors['title'] = 'Le titre est obligatoire (min. 2 caractères).';
        }

        if (!empty($errors)) {
            $this->renderAdmin('page-edit', [
                'pageTitle'  => 'Modifier : ' . mb_substr($title, 0, 40),
                'pageData'   => array_merge($pageData, $_POST, ['id' => $id, 'hero_image' => $heroImagePath]),
                'errors'     => $errors,
                'success'    => false,
                'activeMenu' => 'pages',
            ]);
            return;
        }

        Database::execute(
            'UPDATE pages SET title = :title, meta_desc = :meta_desc, meta_keywords = :meta_keywords, hero_image = :hero_image, content = :content, sort_order = :sort_order, is_published = :is_published, updated_by = :updated_by, updated_at = NOW() WHERE id = :id',
            [':title' => $title, ':meta_desc' => $metaDesc, ':meta_keywords' => $metaKeywords, ':hero_image' => $heroImagePath, ':content' => $content, ':sort_order' => $sortOrder, ':is_published' => $isPublished, ':updated_by' => $_SESSION['admin_id'], ':id' => $id]
        );

        $pageData = Database::fetchOne('SELECT * FROM pages WHERE id = :id', [':id' => $id]);

        $this->renderAdmin('page-edit', [
            'pageTitle'  => 'Modifier : ' . mb_substr($pageData['title'], 0, 40),
            'pageData'   => $pageData,
            'errors'     => [],
            'success'    => true,
            'activeMenu' => 'pages',
        ]);
    }

    public function subsidiaries(array $params = []): void
    {
        $this->guard();
        $subs = Database::fetchAll('SELECT s.*, st.name FROM subsidiaries s LEFT JOIN subsidiary_translations st ON s.id = st.subsidiary_id WHERE st.lang = "fr"');

        $this->renderAdmin('subsidiaries', [
            'pageTitle'  => 'Gestion des Filiales',
            'subs'       => $subs,
            'activeMenu' => 'subsidiaries',
        ]);
    }

    public function subsidiaryEdit(array $params = []): void
    {
        $this->guard();
        $id = (int)($params['id'] ?? 0);
        $sub = Database::fetchOne('SELECT * FROM subsidiaries WHERE id = :id', [':id' => $id]);
        $translations = Database::fetchAll('SELECT * FROM subsidiary_translations WHERE subsidiary_id = :id', [':id' => $id]);
        
        $data = [];
        foreach ($translations as $t) { $data[$t['lang']] = $t; }

        $this->renderAdmin('subsidiary-edit', [
            'pageTitle'    => 'Modifier Filiale',
            'id'           => $id,
            'sub'          => $sub,
            'translations' => $data,
            'activeMenu'   => 'subsidiaries',
        ]);
    }

    public function subsidiaryDelete(array $params = []): void
    {
        $this->guard();
        Security::verifyCsrf();
        $id = (int)($params['id'] ?? 0);
        
        $sub = Database::fetchOne('SELECT image_path FROM subsidiaries WHERE id = ?', [$id]);
        if ($sub && !empty($sub['image_path'])) {
            $filePath = ROOT_PATH . $sub['image_path'];
            if (file_exists($filePath)) unlink($filePath);
        }

        Database::execute('DELETE FROM subsidiary_translations WHERE subsidiary_id = ?', [$id]);
        Database::execute('DELETE FROM subsidiaries WHERE id = ?', [$id]);

        header('Location: /admin/subsidiaries?deleted=1');
        exit;
    }

    public function subsidiaryUpdate(array $params = []): void
    {
        $this->guard();
        Security::verifyCsrf();
        $id = (int)($params['id'] ?? 0);
        $data = $_POST['data'];

        foreach ($data as $lang => $values) {
            Database::execute('UPDATE subsidiary_translations SET name = :name, tagline = :tagline, description = :description WHERE subsidiary_id = :id AND lang = :lang', 
                [':name' => $values['name'], ':tagline' => $values['tagline'], ':description' => $values['description'], ':id' => $id, ':lang' => $lang]);
        }

        if (isset($_FILES['sub_image']) && $_FILES['sub_image']['error'] === UPLOAD_ERR_OK) {
            $uploadDir = ROOT_PATH . '/assets/images/activites';
            $fileName = Security::secureUpload($_FILES['sub_image'], $uploadDir);
            if ($fileName) {
                Database::execute('UPDATE subsidiaries SET image_path = ? WHERE id = ?', ['/assets/images/activites/' . $fileName, $id]);
            }
        }

        header('Location: /admin/subsidiaries?success=1');
        exit;
    }

    public function news(array $params = []): void
    {
        $this->guard();
        $news = Database::fetchAll('SELECT n.*, st.name AS sub_name FROM news n LEFT JOIN subsidiaries s ON s.id = n.subsidiary_id LEFT JOIN subsidiary_translations st ON st.subsidiary_id = n.subsidiary_id AND st.lang = "fr" ORDER BY n.created_at DESC');

        $this->renderAdmin('news', ['pageTitle' => 'Actualités', 'news' => $news, 'activeMenu' => 'news']);
    }

    public function newsNew(array $params = []): void
    {
        $this->guard();
        $this->renderAdmin('news-form', ['pageTitle' => 'Nouvelle actualité', 'subs' => SubsidiaryModel::getAll('fr'), 'activeMenu' => 'news', 'article' => null, 'errors' => []]);
    }

    public function newsCreate(array $params = []): void
    {
        $this->guard();
        Security::verifyCsrf();
        $title = trim($_POST['title'] ?? '');
        $lang = in_array($_POST['lang'] ?? 'fr', ['fr','en']) ? $_POST['lang'] : 'fr';
        $slug = Security::slug($title) . '-' . time();
        
        \App\Models\NewsModel::create([
            ':slug' => $slug, ':lang' => $lang, ':subsidiary_id' => !empty($_POST['subsidiary_id']) ? (int)$_POST['subsidiary_id'] : null,
            ':title' => $title, ':excerpt' => trim($_POST['excerpt'] ?? ''), ':content' => $_POST['content'] ?? '',
            ':cover_image' => null, ':is_published' => isset($_POST['is_published']) ? 1 : 0,
            ':is_featured' => isset($_POST['is_featured']) ? 1 : 0, ':published_at' => isset($_POST['is_published']) ? date('Y-m-d H:i:s') : null,
            ':created_by' => $_SESSION['admin_id'],
        ]);
        header('Location: /admin/news?created=1');
        exit;
    }

    public function newsEdit(array $params = []): void
    {
        $this->guard();
        $id = (int)($params['id'] ?? 0);
        $article = Database::fetchOne('SELECT * FROM news WHERE id = :id', [':id' => $id]);
        if (!$article) { header('Location: /admin/news'); exit; }
        
        $this->renderAdmin('news-edit', ['pageTitle' => 'Modifier', 'article' => $article, 'subs' => SubsidiaryModel::getAll('fr'), 'activeMenu' => 'news']);
    }

    public function newsUpdate(array $params = []): void
    {
        $this->guard();
        Security::verifyCsrf();
        $id = (int)($params['id'] ?? 0);
        $article = Database::fetchOne('SELECT * FROM news WHERE id = :id', [':id' => $id]);
        
        $coverImage = $_POST['current_cover_image'] ?? $article['cover_image'];
        if (!empty($_FILES['cover_image']['name']) && $_FILES['cover_image']['error'] === UPLOAD_ERR_OK) {
            $fileName = Security::secureUpload($_FILES['cover_image'], ROOT_PATH . '/assets/images');
            if ($fileName) $coverImage = '/assets/images/' . $fileName;
        }
        if (!empty($_POST['remove_cover'])) $coverImage = null;

        Database::execute('UPDATE news SET title = :t, slug = :s, excerpt = :e, content = :c, cover_image = :img, is_published = :pub, updated_at = NOW() WHERE id = :id',
            [':t' => $_POST['title'], ':s' => Security::slug($_POST['slug']), ':e' => $_POST['excerpt'], ':c' => $_POST['content'], ':img' => $coverImage, ':pub' => isset($_POST['is_published']) ? 1 : 0, ':id' => $id]);

        header('Location: /admin/news?updated=1');
        exit;
    }

    public function newsDelete(array $params = []): void
    {
        $this->guard();
        Security::verifyCsrf();
        Database::execute('DELETE FROM news WHERE id = :id', [':id' => (int)($params['id'] ?? 0)]);
        header('Location: /admin/news?deleted=1');
        exit;
    }

    public function contacts(array $params = []): void
    {
        $this->guard();
        $contacts = Database::fetchAll('SELECT * FROM contacts ORDER BY created_at DESC');
        Database::execute('UPDATE contacts SET is_read = 1 WHERE is_read = 0');
        $this->renderAdmin('contacts', ['pageTitle' => 'Messages', 'contacts' => $contacts, 'activeMenu' => 'contacts']);
    }

    public function settings(array $params = []): void
    {
        $this->guard();
        $settings = array_column(Database::fetchAll('SELECT * FROM settings'), 'value', 'key');
        $this->renderAdmin('settings', ['pageTitle' => 'Paramètres', 'settings' => $settings, 'activeMenu' => 'settings']);
    }

    public function settingsSave(array $params = []): void
    {
        $this->guard();
        Security::verifyCsrf();
        foreach (Database::fetchAll('SELECT `key` FROM settings') as $row) {
            if (isset($_POST[$row['key']])) {
                Database::execute('UPDATE settings SET value = :val WHERE `key` = :key', [':val' => trim($_POST[$row['key']]), ':key' => $row['key']]);
            }
        }
        header('Location: /admin/settings?saved=1');
        exit;
    }
   
}

Youez - 2016 - github.com/yon3zu
LinuXploit