mirror of
https://github.com/avecms/AVE.cms.git
synced 2026-08-01 08:45:44 +00:00
8.4 KiB
8.4 KiB
Step by step: your module from scratch
← Back to the “Modules” section
Let's put together a minimal but working module "Bookmarks" (bookmarks) - a list of personal
admin links. Let's go all the way: folder → descriptor → migrations → routes →
controller/model → template → assets → check.
Analysis of components - in module.php, files.md, migrations.md, contributions.md.
1. Folder
adminx/modules/Bookmarks/
├── module.php
├── Controller.php
├── Model.php
├── migrations/
│ ├── 001_create_admin_bookmarks.sql
│ └── uninstall.sql
├── view/
│ └── index.twig
└── assets/
├── bookmarks.less
└── bookmarks.css (собирается из .less)
2. Rights and menus
We will declare static rights and menu items directly in module.php in step 7. For them
no need for separate classes. Provider is written to the file only when it is calculated or
reused logic.
3. Migrations
migrations/001_create_admin_bookmarks.sql:
CREATE TABLE IF NOT EXISTS `{{prefix}}_admin_bookmarks` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT UNSIGNED NOT NULL,
`title` VARCHAR(200) NOT NULL DEFAULT '',
`url` VARCHAR(500) NOT NULL DEFAULT '',
`created_at` INT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
migrations/uninstall.sql:
DROP TABLE IF EXISTS `{{prefix}}_admin_bookmarks`;
There is no need to register rights separately via SQL: when installing the manager
synchronizes the permissions.items array with the system registry.
4. Model – Model.php
<?php
namespace App\Adminx\Bookmarks;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Common\SystemTables;
use DB;
class Model
{
public static function table() { return SystemTables::prefix() . '_admin_bookmarks'; }
public static function all($userId)
{
return DB::query(
'SELECT * FROM ' . self::table() . ' WHERE user_id = %i ORDER BY id DESC',
(int) $userId
)->getAll() ?: array();
}
public static function create($userId, $title, $url)
{
DB::Insert(self::table(), array(
'user_id' => (int) $userId,
'title' => trim((string) $title),
'url' => trim((string) $url),
'created_at' => time(),
));
return (int) DB::insertId();
}
public static function delete($id, $userId)
{
DB::Delete(self::table(), 'id = %i AND user_id = %i', (int) $id, (int) $userId);
return DB::affectedRows() > 0;
}
}
5. Controller - Controller.php
<?php
namespace App\Adminx\Bookmarks;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Common\Auth;
use App\Common\Controller as BaseController;
use App\Common\Permission;
use App\Helpers\Request;
use App\Helpers\Response;
use App\Helpers\Valid;
class Controller extends BaseController
{
public function index(array $params = array())
{
if (!Permission::check('view_bookmarks')) { Response::forbidden(); return ''; }
return $this->render('@bookmarks/index.twig', array(
'bookmarks' => Model::all(Auth::id()),
'can_manage' => Permission::check('manage_bookmarks'),
));
}
public function store(array $params = array())
{
if (($err = $this->guard()) !== null) { return $err; }
$errors = Valid::check($_POST, array('title' => 'required', 'url' => 'required|url'));
if ($errors) { return $this->error('Проверьте поля', $errors); }
Model::create(Auth::id(), Request::postStr('title'), Request::postStr('url'));
return $this->success('Закладка добавлена');
}
public function delete(array $params = array())
{
if (($err = $this->guard()) !== null) { return $err; }
$id = isset($params['id']) ? (int) $params['id'] : 0;
if (!Model::delete($id, Auth::id())) { return $this->error('Не найдено', array(), 404); }
return $this->success('Удалено');
}
protected function guard()
{
if (($err = $this->csrfGuard()) !== null) { return $err; }
return Permission::check('manage_bookmarks') ? null : $this->error('Недостаточно прав', array(), 403);
}
}
6. Template - view/index.twig
{% extends '@adminx/main.twig' %}
{% block title %}Закладки{% endblock %}
{% block content %}
<div class="page-header"><div class="between">
<div><h1>Закладки</h1><p class="text-secondary">Личные ссылки</p></div>
</div></div>
<div class="card ax-list-card">
<ul>
{% for b in bookmarks %}
<li><a href="{{ b.url }}" target="_blank" rel="noopener">{{ b.title }}</a></li>
{% else %}
<li class="ax-empty">Пока пусто</li>
{% endfor %}
</ul>
</div>
{% endblock %}
7. module.php
<?php
use App\Adminx\Bookmarks\Controller;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
return array(
'code' => 'bookmarks', 'name' => 'Закладки', 'version' => '1.0.0',
'description' => 'Личные закладки администратора.', 'author' => 'AVE.cms',
'lifecycle' => array('managed' => true,
'uninstall' => array('migrations/uninstall.sql')),
'permissions' => array(
'key' => 'bookmarks',
'items' => array(
array('code' => 'view_bookmarks', 'group_code' => 'navigation',
'name' => 'Закладки: просмотр', 'sort_order' => 10),
array('code' => 'manage_bookmarks', 'group_code' => 'content',
'name' => 'Закладки: управление', 'sort_order' => 20),
),
'icon' => 'ti ti-bookmark', 'priority' => 48,
),
'routes' => array(
array('GET', '/bookmarks', array(Controller::class, 'index')),
array('POST', '/bookmarks', array(Controller::class, 'store')),
array('POST', '/bookmarks/{id}/delete', array(Controller::class, 'delete')),
),
'migrations' => array(
array('id' => '001_create_admin_bookmarks', 'file' => 'migrations/001_create_admin_bookmarks.sql'),
),
'assets' => array(
'styles' => array(array('url' => ADMINX_BASE . '/modules/Bookmarks/assets/bookmarks.css', 'priority' => 48)),
),
'admin_extension' => array(
'url' => '/bookmarks', 'icon' => 'ti ti-bookmark',
'feature' => 'Личные закладки',
'menu' => array(array(
'code' => 'modules_bookmarks', 'label' => 'Закладки', 'url' => '/bookmarks',
'icon' => 'ti ti-bookmark', 'permission' => 'view_bookmarks',
'group' => 'Система', 'parent' => 'modules', 'sort_order' => 12,
'match' => array('/bookmarks'),
)),
),
);
8. Collect styles and check
# LESS → CSS (даже пустой файл нужно собрать, чтобы .css существовал)
./node_modules/.bin/lessc adminx/modules/Bookmarks/assets/bookmarks.less \
adminx/modules/Bookmarks/assets/bookmarks.css
# синтаксис PHP
php -l adminx/modules/Bookmarks/Controller.php
Next:
- Open System → Modules and install the detected
bookmarks. - Give the role the rights
view_bookmarks/manage_bookmarksin “Roles and Rights”. - Make sure that “Bookmarks” appears in the menu and POST works via Ajax.
- On a test copy, check switching off, turning on and uninstalling.
Readiness checklist
codeis unique and matches the class namespace (App\Adminx\Bookmarks).- The table name is built from
SystemTables::prefix(), the prefix is not hardcoded. - Migrations are idempotent (
IF NOT EXISTS,ON DUPLICATE KEY UPDATE). - Each POST action:
csrfGuard()+Permission::check(). - Rights separated
view_*/manage_*. - LESS compiled in CSS; JS (if any) does not use native
confirm/alert. - There is
uninstall.sqlfor tables belonging to the module; the rights will be removed by the manager. - Installation, shutdown, update and uninstallation checked through the control panel.