An open API service indexing awesome lists of open source software.

https://github.com/visavi/rotor-modules


https://github.com/visavi/rotor-modules

modules php rotor visavi wap

Last synced: 26 days ago
JSON representation

Awesome Lists containing this project

README

          

# Модули для Rotor

Официальный репозиторий модулей для движка [Rotor](https://github.com/visavi/rotor).

## Установка

### Через каталог модулей (рекомендуется)

В админ-панели перейдите в **Модули → Каталог**. Все доступные модули из подключённых реестров отображаются там. Нажмите «Установить» — модуль будет скачан и распакован автоматически.

### Через ZIP-архив

В **Модули → Загрузить** можно установить модуль из ZIP-файла или по прямой ссылке.

### Вручную

Распакуйте модуль в директорию `/modules/ИмяМодуля`, после чего в админ-панели нажмите «Установить».

---

## Управление модулями

- **Установка** — выполняются миграции, создаются симлинки на статические файлы, подключаются настройки, хуки и маршруты
- **Отключение** — модуль становится недоступен, данные в БД сохраняются
- **Включение** — модуль возобновляет работу с теми же данными
- **Обновление** — если версия в `module.php` выше установленной, в админке появляется кнопка «Обновить»
- **Удаление** — откатываются миграции, удаляются симлинки и данные модуля

---

## Реестры модулей

Реестры — источники, из которых каталог берёт список доступных модулей. В **Модули → Реестры** можно добавить свой или сторонний реестр в формате JSON.

Официальный реестр этого репозитория:
```
https://github.com/visavi/rotor-modules/releases/download/registry/registry.json
```

Файл `registry.json` обновляется автоматически через GitHub Actions при каждом пуше в репозиторий.

---

## Структура модуля

```
MyModule/
├── module.php # обязательный — метаданные и возможности модуля
├── changelog.md # история изменений по версиям (для каталога и страницы модуля)
├── routes.php # маршруты веб и API
├── hooks.php # вставки в шаблоны через Hook::add и регистрации Registry/Restatement
├── helpers.php # глобальные вспомогательные функции
├── middleware.php # регистрация middleware (алиасы и/или группа web)
├── config.php # конфигурация модуля (config('MyModule.key'))
├── Http/
│ ├── Controllers/ # контроллеры (Modules\MyModule\Http\Controllers)
│ ├── Requests/ # FormRequest классы
│ └── Resources/ # API-ресурсы
├── Models/ # модели Eloquent (Modules\MyModule\Models)
├── Observers/ # наблюдатели моделей
├── Middleware/ # классы middleware
├── Services/ # сервисные классы
├── Console/ # консольные команды — автоматически регистрируются
├── database/
│ └── migrations/ # миграции БД — выполняются при установке/обновлении,
│ # откатываются при удалении
├── resources/
│ ├── views/ # Blade-шаблоны: view('MyModule::dir/file')
│ ├── lang/ # переводы по языкам: __('MyModule::file.key')
│ └── assets/ # статические файлы (css, js, img);
│ # симлинк создаётся на /assets/modules/my-module/
└── screenshots/ # скриншоты модуля для карточки в админке
```

Все поддиректории необязательные — создавай только нужные.

---

## Файл module.php

Обязательный файл. Возвращает массив с метаданными и возможностями модуля:

```php
use Modules\MyModule\Models\MyModel;
use Modules\MyModule\Observers\MyObserver;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Support\Facades\DB;

return [
'name' => 'Название модуля',
'description' => 'Краткое описание',
'info' => '

Длинное описание с HTML, показывается на странице модуля в админке

',
'version' => '1.0.0',
'requires' => '14.0.0',
'author' => 'Автор',
'email' => 'author@example.com',
'homepage' => 'https://example.com',

// Возможности моделей модуля
'models' => [
MyModel::class => [
'label' => 'Метка раздела',
'search' => ['view' => 'MyModule::search/_results', 'with' => ['user']],
'feed' => ['with' => ['user', 'files'], 'view' => 'MyModule::feeds/_feed'],
'upload' => 'media',
'rating' => true,
'spam' => true,
],
],

// Наблюдатели моделей
'observers' => [
MyModel::class => MyObserver::class,
],

// Планировщик задач
'schedule' => function (Schedule $schedule) {
$schedule->command('my-module:cleanup')->daily();
},

// Пересчёты — вызываются из админки кнопкой «Пересчёт»
'restatement' => [
'mymodel' => function () {
DB::update('update ...');
},
],

// Ссылки-действия на странице модуля в админке
'actions' => [
'/admin/my-module' => 'Мой модуль',
],

// Публикация файлов: копирование из модуля в директории движка
'publish' => [
'stubs/views' => 'resources/views/vendor/my-module',
'stubs/widget.blade.php' => 'resources/views/themes/default/widgets/my-module.blade.php',
],
];
```

### Описание полей

**`name`, `description`, `author`, `email`, `homepage`** — отображаются в карточке модуля.

**`info`** — длинное описание с HTML, видно на странице модуля в админке. Сюда удобно класть инструкции по подключению.

**`version`** — текущая версия модуля. Если в `module.php` версия выше установленной, в админке появляется кнопка «Обновить».

**`requires`** — минимальная версия движка Rotor. При несовместимости модуль помечается в каталоге как «Несовместим».

**`models`** — массив `Class::class => [возможности]`. Каждая модель автоматически регистрируется в `morphMap` Laravel. Доступные возможности:

| Ключ | Описание |
|---|---|
| `label` | Метка раздела модели — название в результатах поиска, на странице «Спам» и в рейтингах. |
| `search` | Подключает модель к глобальному поиску. `view` — шаблон одного результата, `with` (опц.) — отношения для eager-load. |
| `feed` | Подключает к общей ленте активности. `with` — отношения для eager-load, `view` — шаблон записи, `scope` (опц.) — замыкание, ограничивающее выборку, `poll` (опц.) — замыкание `fn ($model): ?array`, возвращающее `[морф-имя, id]` связанной записи, если голосование привязано не к самой записи (тема → последний пост). |
| `upload` | Разрешает прикреплять файлы. `media` — изображения и видео, `file` — любые файлы. |
| `rating` | `true` — включает лайки/дизлайки. |
| `spam` | `true` — записи можно помечать как спам (раздел на странице «Спам» в админке; название раздела берётся из `label`). |

Если модель нужна только для `morphMap` (например, для полиморфных связей), но не имеет возможностей — оставь пустой массив:
```php
'models' => [
Vote::class => [],
],
```

**`observers`** — массив `Class::class => Observer::class`. Регистрирует Eloquent-наблюдателей.

**`schedule`** — замыкание, получающее `Schedule` Laravel. Регистрирует периодические задачи.

**`restatement`** — массив `'ключ' => callable`. Пересчёты счётчиков, запускаются из админки или вручную через `Restatement::run('ключ')`.

**`actions`** — массив `URL => 'Название'`. Ссылки-действия, отображаются на странице модуля в админ-панели.

**`publish`** — массив `источник => назначение`. Копирует файлы из модуля в произвольные директории движка при установке и удаляет их при отключении/удалении модуля. Подробнее в разделе «Публикация файлов».

---

## История изменений (changelog.md)

Необязательный файл `changelog.md` в корне модуля. Секции по версиям: заголовок `## X.Y.Z` обязателен, тело — произвольный текст (markdown знать не нужно).

```markdown
## 1.1.0
- Добавлена выгрузка в CSV
- Исправлена пагинация

## 1.0.0
- Первый релиз
```

Rotor показывает changelog на странице модуля в админке:

- **История изменений** — весь файл, всегда.
- **Что нового в версии X** — секция новой версии, при доступном обновлении (из реестра, ещё до установки).

При сборке реестра (`module:registry`) и в CI секция текущей версии попадает в `registry.json`. Файла нет — ничего не ломается, поле просто отсутствует.

---

## Маршруты (routes.php)

```php
use Illuminate\Support\Facades\Route;
use Modules\MyModule\Http\Controllers\MyController;

Route::middleware('web')
->controller(MyController::class)
->prefix('my-module')
->name('my-module.')
->group(function () {
Route::get('/', 'index')->name('index');
Route::get('/{id}', 'view')->name('view');
});

// Админка
Route::middleware(['web', 'check.admin', 'admin.logger'])
->prefix('admin')
->group(function () {
Route::controller(AdminMyController::class)
->prefix('my-module')
->name('admin.my-module.')
->group(function () {
Route::get('/', 'index')->name('index');
Route::delete('/{id}', 'delete')->name('delete');
});
});
```

---

## Контроллеры

Пространство имён: `Modules\MyModule\Http\Controllers`

```php
namespace Modules\MyModule\Http\Controllers;

class MyController extends \App\Http\Controllers\Controller
{
public function index() { ... }
}
```

Административные контроллеры размещаются в `Http/Controllers/Admin/` и наследуются от `\App\Http\Controllers\Admin\AdminController`.

---

## Модели

Пространство имён: `Modules\MyModule\Models`

```php
namespace Modules\MyModule\Models;

class MyModel extends \Illuminate\Database\Eloquent\Model
{
public static string $morphName = 'mymodels';
}
```

`$morphName` обязательно у моделей, заявленных в `models` (используется ядром для регистрации возможностей).

Ограничения морф-имени: максимум **20 символов** (ширина колонки `relate_type` в БД) и неизменность после релиза модуля — имя сохраняется в записях БД.

---

## Хуки (hooks.php)

Файл `hooks.php` содержит:
- вставки в шаблоны через `Hook::add` (UI-расширения);
- регистрации в `Registry` для возможностей, которые не привязаны к одной модели (sitemap, complaint, onDeleteUser, onAdminDeleteUser);
- регистрации `Restatement::register` (если не объявлено в `module.php`).

### Hook::add

Хук может быть строкой или callable. Callable получает аргументы из `@hook(...)` и возвращает свой HTML-фрагмент (или `null`/`''` если ничего не добавлять). Все фрагменты склеиваются в порядке убывания `priority`.

```php
use App\Classes\Hook;

// Статичная строка
Hook::add('sidebarMenuEnd', '

  • Текст
  • ');

    // Динамический фрагмент
    Hook::add('head', static function () {
    return '';
    });

    // С аргументом из @hook('userProfileLinks', $user)
    Hook::add('userProfileLinks', static function ($user) {
    return ' / Мои записи';
    });

    // Третий аргумент — приоритет (выше → раньше). По умолчанию 0
    Hook::add('sidebarMenuEnd', static fn () => '

  • ...
  • ', 10);
    ```

    Вызов в шаблоне:
    ```blade
    @hook('head')
    @hook('userProfileLinks', $user)
    ```

    ### Registry

    ```php
    use App\Classes\Registry;
    use Modules\MyModule\Models\MyModel;

    // Жалобы — обработчик клика на «пожаловаться»
    Registry::complaint(MyModel::$morphName, function (int $id) {
    $model = MyModel::query()->find($id);
    return ['model' => $model, 'path' => $model?->getViewUrl(false)];
    });

    // Sitemap
    Registry::sitemap('mymodels', function () {
    return [['loc' => route('my-module.index'), 'lastmod' => gmdate('c')]];
    });

    // Удаление пользователя — что подчистить
    Registry::onDeleteUser(function (\App\Models\User $user) {
    MyModel::query()->where('user_id', $user->id)->delete();
    });

    // Удаление пользователя администратором (в Request — чекбоксы формы удаления,
    // добавить свой можно через Hook::add('adminUserDeleteFields', ...))
    Registry::onAdminDeleteUser(function (\App\Models\User $user, \Illuminate\Http\Request $request) {
    if ($request->boolean('delmymodels')) {
    MyModel::query()->where('user_id', $user->id)->get()->each->delete();
    }
    });
    ```

    Полный список методов `Registry`:

    | Метод | Назначение |
    |---|---|
    | `fileType($morphName)` | тип принимает файлы (вызывается из `module.php` через `'upload' => 'file'`) |
    | `mediaType($morphName)` | тип принимает фото/видео (`'upload' => 'media'`) |
    | `ratingType($morphName)` | тип поддерживает рейтинг (`'rating' => true`) |
    | `spamType($morphName)` | тип — источник жалоб на спам (`'spam' => true`, метка берётся из `label`) |
    | `label($morphName, $label)` | отображаемое название типа (`'label' => '...'`) |
    | `feed($class, $config)` | запись в ленте активности (`'feed' => [...]`) |
    | `search($class, $view, $with)` | полнотекстовый поиск (`'search' => [...]`) |
    | `complaint($morphName, $handler)` | обработчик жалобы |
    | `sitemap($key, $handler)` | страница в sitemap |
    | `onDeleteUser($handler)` | очистка при удалении пользователя |
    | `onAdminDeleteUser($handler)` | удаление пользователя администратором |

    Первые семь обычно регистрируются декларативно через `module.php`, вручную их вызывать не нужно.

    ---

    ## Шаблоны

    Файлы в `resources/views/` вызываются с указанием неймспейса модуля:

    ```php
    view('MyModule::directory/file')
    // → resources/views/directory/file.blade.php
    ```

    ---

    ## Переводы

    Файлы в `resources/lang/ru/`, `resources/lang/en/` и т.д.:

    ```php
    __('MyModule::file.key')
    // → resources/lang/ru/file.php → ['key' => '...']
    ```

    ---

    ## Конфигурация (config.php)

    ```php
    // config.php
    return [
    'api_key' => env('MY_MODULE_API_KEY'),
    'limit' => 10,
    ];

    // Использование
    config('MyModule.api_key');
    ```

    Значения из админки записываются в поле `settings` модуля и сливаются поверх `config.php` при загрузке.

    ---

    ## Helpers (helpers.php)

    Глобальные функции, доступные везде:

    ```php
    if (! function_exists('statsMyModule')) {
    function statsMyModule(): string
    {
    return (string) MyModel::query()->count();
    }
    }
    ```

    ---

    ## Middleware (middleware.php)

    ```php
    use Modules\MyModule\Middleware\MyMiddleware;

    return [
    // Алиасы для применения в routes.php через ->middleware('alias')
    'aliases' => [
    'my-alias' => MyMiddleware::class,
    ],

    // Middleware, добавляемые в группу web автоматически
    'web' => [
    MyMiddleware::class,
    ],
    ];
    ```

    ---

    ## Консольные команды

    Файлы в `Console/` подхватываются автоматически. Имя класса = имя файла:

    ```php
    // Console/Cleanup.php
    namespace Modules\MyModule\Console;

    use Illuminate\Console\Command;

    class Cleanup extends Command
    {
    protected $signature = 'my-module:cleanup';

    public function handle(): void { /* ... */ }
    }
    ```

    ---

    ## Статические файлы

    Файлы из `resources/assets/` доступны по адресу:
    ```
    /assets/modules/my-module/
    ```

    Симлинк создаётся автоматически при установке модуля.

    ---

    ## Публикация файлов (publish)

    Ключ `publish` в `module.php` копирует файлы из модуля в любые директории движка. В отличие от симлинка `resources/assets/` (доступ к статике по URL), это нужно когда файл должен физически лежать в ядре — переопределение Blade-шаблонов темы, вставка виджета в тему, файлы в корне проекта (`favicon.ico`, `robots.txt`).

    ```php
    'publish' => [
    'stubs/views' => 'resources/views/vendor/my-module',
    'stubs/widget.blade.php' => 'resources/views/themes/default/widgets/my-module.blade.php',
    ],
    ```

    - **Источник** — путь относительно папки модуля (`modules/MyModule/`).
    - **Назначение** — путь относительно корня проекта (`base_path()`).
    - Если источник — директория, копируется/удаляется директория; если файл — файл.
    - Копирование выполняется при **установке и включении**, удаление — при **отключении и удалении** модуля.
    - При **обновлении ядра** файлы перепубликуются автоматически (деплой и обновление через админку вызывают `module:sync`), так что затёртые апдейтом оверрайды восстанавливаются.
    - Пути с `..` игнорируются.

    > Удаление снимает с диска именно опубликованный путь назначения. Не указывайте в назначении общий каталог движка (например `config` или `public`) — при отключении он будет удалён целиком. Используйте выделенные пути (`resources/views/vendor/my-module`, `public/assets/my-module`).

    ---

    ## Примеры минимальных модулей

    **Только маршруты и контроллер:**
    ```
    MyModule/module.php, routes.php, Http/Controllers/MyController.php
    ```

    **Только миграции (изменение БД):**
    ```
    MyModule/module.php, database/migrations/
    ```

    **Только внешний вид (хуки):**
    ```
    MyModule/module.php, hooks.php
    ```

    См. модуль `Template` — минимальный шаблон для старта.

    ---

    ## License

    The Rotor is open-sourced software licensed under the [GPL-3.0 license](http://opensource.org/licenses/GPL-3.0)