راهنمای طراحی ماژول Dima CMS

معرفی

ماژول‌ها در Dima CMS به شما امکان اضافه کردن قابلیت‌های جدید به سیستم را می‌دهند. هر ماژول یک واحد مستقل است که می‌تواند فعال یا غیرفعال شود.

ویژگی‌های ماژول‌ها:
  • ساختار مستقل و قابل حمل
  • سیستم هوک برای تعامل با هسته
  • مدیریت منوهای خودکار
  • تنظیمات قابل سفارشی
  • سیستم دسترسی و مجوزها

ساختار ماژول

هر ماژول باید ساختار زیر را داشته باشد:

modules/ ├── YourModule/ │ ├── config.php │ ├── install.php │ ├── uninstall.php │ ├── menu.php │ ├── Controllers/ │ │ ├── MainController.php │ │ └── AdminController.php │ ├── Models/ │ │ ├── ModuleModel.php │ │ └── DataModel.php │ ├── Views/ │ │ ├── admin/ │ │ │ ├── index.php │ │ │ └── settings.php │ │ └── frontend/ │ │ ├── display.php │ │ └── form.php │ ├── hooks/ │ │ ├── before_header.php │ │ ├── after_content.php │ │ └── admin_dashboard.php │ ├── assets/ │ │ ├── css/ │ │ │ └── style.css │ │ ├── js/ │ │ │ └── script.js │ │ └── images/ │ │ └── icon.png │ └── database/ │ └── schema.sql

فایل‌های ضروری:

  • config.php: تنظیمات و اطلاعات ماژول
  • install.php: اسکریپت نصب ماژول
  • menu.php: تعریف منوهای ماژول
  • Controllers/: کنترلرهای ماژول
  • Models/: مدل‌های داده

فایل config.php

این فایل اطلاعات اصلی ماژول را تعریف می‌کند:

<?php return [ 'name' => 'نام ماژول', 'description' => 'توضیحات کامل ماژول', 'version' => '1.0.0', 'author' => 'نام نویسنده', 'author_url' => 'https://example.com', 'category' => 'دسته‌بندی ماژول', 'license' => 'MIT', // ویژگی‌های ماژول 'features' => [ 'ویژگی اول', 'ویژگی دوم', 'ویژگی سوم' ], // وابستگی‌ها 'dependencies' => [ 'ModuleName' => '1.0.0' ], // مجوزهای مورد نیاز 'permissions' => [ 'manage_module' => 'مدیریت ماژول', 'view_data' => 'مشاهده داده‌ها', 'edit_data' => 'ویرایش داده‌ها', 'delete_data' => 'حذف داده‌ها' ], // مسیرهای ماژول 'routes' => [ 'main' => 'Controllers/MainController.php', 'admin' => 'Controllers/AdminController.php', 'api' => 'Controllers/ApiController.php' ], // تنظیمات پیش‌فرض 'settings' => [ 'enabled' => '1', 'max_items' => '10', 'auto_save' => '1', 'notifications' => '1' ], // اطلاعات نصب 'install' => [ 'tables' => [ 'module_table' => 'database/schema.sql' ], 'hooks' => [ 'before_header' => 'hooks/before_header.php', 'after_content' => 'hooks/after_content.php' ] ] ];

کنترلرها

کنترلرها منطق اصلی ماژول را مدیریت می‌کنند:

کنترلر اصلی:

<?php class MainController { private $pdo; private $moduleName; public function __construct($pdo) { $this->pdo = $pdo; $this->moduleName = 'YourModule'; } // نمایش صفحه اصلی ماژول public function index() { try { // دریافت داده‌ها $data = $this->getData(); // بارگذاری نمایش $this->loadView('frontend/index.php', [ 'data' => $data, 'title' => 'عنوان صفحه' ]); } catch (Exception $e) { $this->showError($e->getMessage()); } } // نمایش فرم public function form() { if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->processForm(); } else { $this->loadView('frontend/form.php'); } } // پردازش فرم private function processForm() { try { $data = $this->validateInput($_POST); $this->saveData($data); $this->redirect('index.php?message=success'); } catch (Exception $e) { $this->showError($e->getMessage()); } } // دریافت داده‌ها private function getData() { $stmt = $this->pdo->prepare("SELECT * FROM {$this->moduleName}_data ORDER BY created_at DESC"); $stmt->execute(); return $stmt->fetchAll(); } // ذخیره داده private function saveData($data) { $stmt = $this->pdo->prepare(" INSERT INTO {$this->moduleName}_data (title, content, created_at) VALUES (?, ?, NOW()) "); $stmt->execute([$data['title'], $data['content']]); } // اعتبارسنجی ورودی private function validateInput($input) { $data = []; if (empty($input['title'])) { throw new Exception('عنوان الزامی است'); } if (empty($input['content'])) { throw new Exception('محتوا الزامی است'); } $data['title'] = htmlspecialchars(trim($input['title'])); $data['content'] = htmlspecialchars(trim($input['content'])); return $data; } // بارگذاری نمایش private function loadView($view, $data = []) { extract($data); include __DIR__ . "/../Views/$view"; } // نمایش خطا private function showError($message) { echo "<div class='alert alert-danger'>$message</div>"; } // هدایت private function redirect($url) { header("Location: $url"); exit; } }

کنترلر مدیریت:

<?php class AdminController { private $pdo; private $moduleName; public function __construct($pdo) { $this->pdo = $pdo; $this->moduleName = 'YourModule'; // بررسی دسترسی if (!isset($_SESSION['logged_in']) || $_SESSION['role'] !== 'admin') { die('دسترسی غیرمجاز'); } } // صفحه اصلی مدیریت public function index() { $data = $this->getAllData(); $this->loadView('admin/index.php', ['data' => $data]); } // تنظیمات ماژول public function settings() { if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->saveSettings($_POST); } $settings = $this->getSettings(); $this->loadView('admin/settings.php', ['settings' => $settings]); } // حذف آیتم public function delete() { $id = $_GET['id'] ?? 0; if ($id) { $stmt = $this->pdo->prepare("DELETE FROM {$this->moduleName}_data WHERE id = ?"); $stmt->execute([$id]); } $this->redirect('admin.php?message=deleted'); } // دریافت تمام داده‌ها private function getAllData() { $stmt = $this->pdo->prepare("SELECT * FROM {$this->moduleName}_data ORDER BY created_at DESC"); $stmt->execute(); return $stmt->fetchAll(); } // دریافت تنظیمات private function getSettings() { $stmt = $this->pdo->prepare(" SELECT setting_key, setting_value FROM module_settings WHERE module_name = ? "); $stmt->execute([$this->moduleName]); $settings = []; while ($row = $stmt->fetch()) { $settings[$row['setting_key']] = $row['setting_value']; } return $settings; } // ذخیره تنظیمات private function saveSettings($data) { foreach ($data as $key => $value) { $stmt = $this->pdo->prepare(" INSERT INTO module_settings (module_name, setting_key, setting_value) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE setting_value = ? "); $stmt->execute([$this->moduleName, $key, $value, $value]); } } // بارگذاری نمایش private function loadView($view, $data = []) { extract($data); include __DIR__ . "/../Views/$view"; } // هدایت private function redirect($url) { header("Location: $url"); exit; } }

مدل‌ها

مدل‌ها برای کار با داده‌ها استفاده می‌شوند:

<?php class ModuleModel { private $pdo; private $tableName; public function __construct($pdo, $tableName) { $this->pdo = $pdo; $this->tableName = $tableName; } // دریافت تمام رکوردها public function getAll($limit = null, $offset = 0) { $sql = "SELECT * FROM {$this->tableName} ORDER BY created_at DESC"; if ($limit) { $sql .= " LIMIT $limit OFFSET $offset"; } $stmt = $this->pdo->prepare($sql); $stmt->execute(); return $stmt->fetchAll(); } // دریافت رکورد بر اساس ID public function getById($id) { $stmt = $this->pdo->prepare("SELECT * FROM {$this->tableName} WHERE id = ?"); $stmt->execute([$id]); return $stmt->fetch(); } // ایجاد رکورد جدید public function create($data) { $fields = implode(', ', array_keys($data)); $placeholders = ':' . implode(', :', array_keys($data)); $sql = "INSERT INTO {$this->tableName} ($fields) VALUES ($placeholders)"; $stmt = $this->pdo->prepare($sql); return $stmt->execute($data); } // بروزرسانی رکورد public function update($id, $data) { $fields = []; foreach ($data as $key => $value) { $fields[] = "$key = :$key"; } $sql = "UPDATE {$this->tableName} SET " . implode(', ', $fields) . " WHERE id = :id"; $data['id'] = $id; $stmt = $this->pdo->prepare($sql); return $stmt->execute($data); } // حذف رکورد public function delete($id) { $stmt = $this->pdo->prepare("DELETE FROM {$this->tableName} WHERE id = ?"); return $stmt->execute([$id]); } // شمارش رکوردها public function count() { $stmt = $this->pdo->prepare("SELECT COUNT(*) FROM {$this->tableName}"); $stmt->execute(); return $stmt->fetchColumn(); } // جستجو public function search($query, $fields = ['title', 'content']) { $conditions = []; $params = []; foreach ($fields as $field) { $conditions[] = "$field LIKE ?"; $params[] = "%$query%"; } $sql = "SELECT * FROM {$this->tableName} WHERE " . implode(' OR ', $conditions); $stmt = $this->pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(); } }

نمایش‌ها

نمایش‌ها فایل‌های HTML هستند که محتوای ماژول را نمایش می‌دهند:

نمایش فرانت‌اند:

<!-- Views/frontend/index.php --> <div class="container mt-4"> <h2><?php echo htmlspecialchars($title); ?></h2> <?php if (!empty($data)): ?> <div class="row"> <?php foreach ($data as $item): ?> <div class="col-md-6 col-lg-4 mb-4"> <div class="card"> <div class="card-body"> <h5 class="card-title"> <?php echo htmlspecialchars($item['title']); ?> </h5> <p class="card-text"> <?php echo htmlspecialchars($item['content']); ?> </p> <small class="text-muted"> <?php echo date('Y/m/d H:i', strtotime($item['created_at'])); ?> </small> </div> </div> </div> <?php endforeach; ?> </div> <?php else: ?> <div class="alert alert-info"> هیچ داده‌ای یافت نشد. </div> <?php endif; ?> <a href="?action=form" class="btn btn-primary"> <i class="bi bi-plus"></i> افزودن جدید </a> </div>

نمایش مدیریت:

<!-- Views/admin/index.php --> <div class="container-fluid"> <div class="d-flex justify-content-between align-items-center mb-4"> <h4>مدیریت ماژول</h4> <a href="?action=settings" class="btn btn-primary"> <i class="bi bi-gear"></i> تنظیمات </a> </div> <?php if (!empty($data)): ?> <div class="card"> <div class="card-body"> <div class="table-responsive"> <table class="table table-hover"> <thead> <tr> <th>عنوان</th> <th>محتوا</th> <th>تاریخ</th> <th>عملیات</th> </tr> </thead> <tbody> <?php foreach ($data as $item): ?> <tr> <td><?php echo htmlspecialchars($item['title']); ?></td> <td><?php echo htmlspecialchars(substr($item['content'], 0, 100)); ?>...</td> <td><?php echo date('Y/m/d H:i', strtotime($item['created_at'])); ?></td> <td> <div class="btn-group btn-group-sm"> <a href="?action=edit&id=<?php echo $item['id']; ?>" class="btn btn-outline-primary"> <i class="bi bi-pencil"></i> </a> <a href="?action=delete&id=<?php echo $item['id']; ?>" class="btn btn-outline-danger" onclick="return confirm('آیا مطمئن هستید؟')"> <i class="bi bi-trash"></i> </a> </div> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> </div> </div> <?php else: ?> <div class="alert alert-info"> هیچ داده‌ای یافت نشد. </div> <?php endif; ?> </div>

هوک‌ها

هوک‌ها به ماژول‌ها امکان تعامل با هسته سیستم را می‌دهند:

هوک قبل از هدر:

<?php // hooks/before_header.php // این هوک قبل از بارگذاری هدر اجرا می‌شود // اضافه کردن CSS ماژول echo '<link href="modules/YourModule/assets/css/style.css" rel="stylesheet">'; // اضافه کردن متغیرهای ماژول $moduleData = [ 'title' => 'عنوان ماژول', 'description' => 'توضیحات ماژول' ]; // ذخیره در متغیر سراسری $GLOBALS['moduleData'] = $moduleData; ?>

هوک بعد از محتوا:

<?php // hooks/after_content.php // این هوک بعد از محتوای اصلی اجرا می‌شود // نمایش ویجت ماژول $stmt = $pdo->prepare("SELECT * FROM YourModule_data ORDER BY created_at DESC LIMIT 5"); $stmt->execute(); $recentData = $stmt->fetchAll(); if (!empty($recentData)): ?> <div class="card mt-4"> <div class="card-header"> <h5 class="mb-0">آخرین مطالب ماژول</h5> </div> <div class="card-body"> <ul class="list-unstyled"> <?php foreach ($recentData as $item): ?> <li class="mb-2"> <a href="modules/YourModule/index.php?id=<?php echo $item['id']; ?>"> <?php echo htmlspecialchars($item['title']); ?> </a> </li> <?php endforeach; ?> </ul> </div> </div> <?php endif; ?>

هوک داشبورد مدیریت:

<?php // hooks/admin_dashboard.php // این هوک در داشبورد مدیریت اجرا می‌شود // آمار ماژول $stmt = $pdo->prepare("SELECT COUNT(*) FROM YourModule_data"); $stmt->execute(); $totalItems = $stmt->fetchColumn(); $stmt = $pdo->prepare("SELECT COUNT(*) FROM YourModule_data WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)"); $stmt->execute(); $recentItems = $stmt->fetchColumn(); ?> <div class="col-md-4 mb-4"> <div class="card"> <div class="card-body"> <h5 class="card-title">آمار ماژول</h5> <p class="card-text"> کل آیتم‌ها: <strong><?php echo $totalItems; ?></strong><br> آیتم‌های هفته گذشته: <strong><?php echo $recentItems; ?></strong> </p> <a href="admin.php?page=modules&module=YourModule" class="btn btn-primary btn-sm"> مدیریت ماژول </a> </div> </div> </div>

نصب و فعال‌سازی

فایل install.php:

<?php // install.php // این فایل هنگام نصب ماژول اجرا می‌شود try { // ایجاد جداول $sql = file_get_contents(__DIR__ . '/database/schema.sql'); $pdo->exec($sql); // اضافه کردن تنظیمات پیش‌فرض $defaultSettings = [ 'enabled' => '1', 'max_items' => '10', 'auto_save' => '1' ]; foreach ($defaultSettings as $key => $value) { $stmt = $pdo->prepare(" INSERT INTO module_settings (module_name, setting_key, setting_value) VALUES (?, ?, ?) "); $stmt->execute(['YourModule', $key, $value]); } // ثبت ماژول در جدول modules $stmt = $pdo->prepare(" INSERT INTO modules (name, status, activated_at) VALUES (?, 'active', NOW()) "); $stmt->execute(['YourModule']); echo "ماژول با موفقیت نصب شد."; } catch (Exception $e) { echo "خطا در نصب ماژول: " . $e->getMessage(); } ?>

فایل uninstall.php:

<?php // uninstall.php // این فایل هنگام حذف ماژول اجرا می‌شود try { // حذف جداول $pdo->exec("DROP TABLE IF EXISTS YourModule_data"); // حذف تنظیمات $stmt = $pdo->prepare("DELETE FROM module_settings WHERE module_name = ?"); $stmt->execute(['YourModule']); // حذف از جدول modules $stmt = $pdo->prepare("DELETE FROM modules WHERE name = ?"); $stmt->execute(['YourModule']); echo "ماژول با موفقیت حذف شد."; } catch (Exception $e) { echo "خطا در حذف ماژول: " . $e->getMessage(); } ?>

مثال‌های عملی

ماژول فرم تماس:

// config.php return [ 'name' => 'فرم تماس', 'description' => 'ماژول فرم تماس با قابلیت ارسال ایمیل', 'version' => '1.0.0', 'author' => 'Dima CMS', 'category' => 'forms', 'permissions' => [ 'manage_forms' => 'مدیریت فرم‌ها', 'view_submissions' => 'مشاهده پاسخ‌ها' ] ]; // Controllers/ContactController.php class ContactController { public function submit() { if ($_SERVER['REQUEST_METHOD'] === 'POST') { $data = [ 'name' => $_POST['name'], 'email' => $_POST['email'], 'message' => $_POST['message'], 'created_at' => date('Y-m-d H:i:s') ]; $stmt = $this->pdo->prepare(" INSERT INTO contact_submissions (name, email, message, created_at) VALUES (?, ?, ?, ?) "); $stmt->execute([$data['name'], $data['email'], $data['message'], $data['created_at']]); // ارسال ایمیل $this->sendEmail($data); echo "پیام شما با موفقیت ارسال شد."; } } }

ماژول گالری تصاویر:

// config.php return [ 'name' => 'گالری تصاویر', 'description' => 'ماژول نمایش گالری تصاویر', 'version' => '1.0.0', 'author' => 'Dima CMS', 'category' => 'media', 'permissions' => [ 'manage_gallery' => 'مدیریت گالری', 'upload_images' => 'آپلود تصاویر' ] ]; // Controllers/GalleryController.php class GalleryController { public function index() { $stmt = $this->pdo->prepare(" SELECT * FROM gallery_images WHERE status = 'active' ORDER BY created_at DESC "); $stmt->execute(); $images = $stmt->fetchAll(); $this->loadView('frontend/gallery.php', ['images' => $images]); } public function upload() { if (isset($_FILES['image'])) { $file = $_FILES['image']; $uploadDir = 'uploads/gallery/'; if (move_uploaded_file($file['tmp_name'], $uploadDir . $file['name'])) { $stmt = $this->pdo->prepare(" INSERT INTO gallery_images (filename, title, status, created_at) VALUES (?, ?, 'active', NOW()) "); $stmt->execute([$file['name'], $_POST['title']]); } } } }

نکات مهم:

  • همیشه از htmlspecialchars() برای نمایش متن استفاده کنید
  • از Prepared Statements برای جلوگیری از SQL Injection استفاده کنید
  • فایل‌های ماژول را در پوشه‌های مناسب سازماندهی کنید
  • از سیستم مجوزها برای کنترل دسترسی استفاده کنید
  • هوک‌ها را برای تعامل با هسته سیستم استفاده کنید