{"id":21762837,"url":"https://github.com/visavi/rotor-modules","last_synced_at":"2026-06-28T02:01:32.459Z","repository":{"id":142792322,"uuid":"161475158","full_name":"visavi/rotor-modules","owner":"visavi","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-21T14:23:49.000Z","size":10367,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-06-21T16:17:08.809Z","etag":null,"topics":["modules","php","rotor","visavi","wap"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/visavi.png","metadata":{"files":{"readme":"readme.md","changelog":"News/Http/Controllers/Admin/NewsController.php","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2018-12-12T10:57:34.000Z","updated_at":"2026-06-21T14:23:19.000Z","dependencies_parsed_at":"2025-03-01T00:29:25.411Z","dependency_job_id":"6f8e2586-498a-44af-aff0-fe7af01de2c5","html_url":"https://github.com/visavi/rotor-modules","commit_stats":null,"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"purl":"pkg:github/visavi/rotor-modules","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/visavi%2Frotor-modules","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/visavi%2Frotor-modules/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/visavi%2Frotor-modules/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/visavi%2Frotor-modules/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/visavi","download_url":"https://codeload.github.com/visavi/rotor-modules/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/visavi%2Frotor-modules/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34874557,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-28T02:00:05.809Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["modules","php","rotor","visavi","wap"],"created_at":"2024-11-26T12:13:17.125Z","updated_at":"2026-06-28T02:01:32.452Z","avatar_url":"https://github.com/visavi.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Модули для Rotor\n\nОфициальный репозиторий модулей для движка [Rotor](https://github.com/visavi/rotor).\n\n## Установка\n\n### Через каталог модулей (рекомендуется)\n\nВ админ-панели перейдите в **Модули → Каталог**. Все доступные модули из подключённых реестров отображаются там. Нажмите «Установить» — модуль будет скачан и распакован автоматически.\n\n### Через ZIP-архив\n\nВ **Модули → Загрузить** можно установить модуль из ZIP-файла или по прямой ссылке.\n\n### Вручную\n\nРаспакуйте модуль в директорию `/modules/ИмяМодуля`, после чего в админ-панели нажмите «Установить».\n\n---\n\n## Управление модулями\n\n- **Установка** — выполняются миграции, создаются симлинки на статические файлы, подключаются настройки, хуки и маршруты\n- **Отключение** — модуль становится недоступен, данные в БД сохраняются\n- **Включение** — модуль возобновляет работу с теми же данными\n- **Обновление** — если версия в `module.php` выше установленной, в админке появляется кнопка «Обновить»\n- **Удаление** — откатываются миграции, удаляются симлинки и данные модуля\n\n---\n\n## Реестры модулей\n\nРеестры — источники, из которых каталог берёт список доступных модулей. В **Модули → Реестры** можно добавить свой или сторонний реестр в формате JSON.\n\nОфициальный реестр этого репозитория:\n```\nhttps://github.com/visavi/rotor-modules/releases/download/registry/registry.json\n```\n\nФайл `registry.json` обновляется автоматически через GitHub Actions при каждом пуше в репозиторий.\n\n---\n\n## Структура модуля\n\n```\nMyModule/\n├── module.php          # обязательный — метаданные и возможности модуля\n├── changelog.md        # история изменений по версиям (для каталога и страницы модуля)\n├── routes.php          # маршруты веб и API\n├── hooks.php           # вставки в шаблоны через Hook::add и регистрации Registry/Restatement\n├── helpers.php         # глобальные вспомогательные функции\n├── middleware.php      # регистрация middleware (алиасы и/или группа web)\n├── config.php          # конфигурация модуля (config('MyModule.key'))\n├── Http/\n│   ├── Controllers/    # контроллеры (Modules\\MyModule\\Http\\Controllers)\n│   ├── Requests/       # FormRequest классы\n│   └── Resources/      # API-ресурсы\n├── Models/             # модели Eloquent (Modules\\MyModule\\Models)\n├── Observers/          # наблюдатели моделей\n├── Middleware/         # классы middleware\n├── Services/           # сервисные классы\n├── Console/            # консольные команды — автоматически регистрируются\n├── database/\n│   └── migrations/     # миграции БД — выполняются при установке/обновлении,\n│                       # откатываются при удалении\n├── resources/\n│   ├── views/          # Blade-шаблоны: view('MyModule::dir/file')\n│   ├── lang/           # переводы по языкам: __('MyModule::file.key')\n│   └── assets/         # статические файлы (css, js, img);\n│                       # симлинк создаётся на /assets/modules/my-module/\n└── screenshots/        # скриншоты модуля для карточки в админке\n```\n\nВсе поддиректории необязательные — создавай только нужные.\n\n---\n\n## Файл module.php\n\nОбязательный файл. Возвращает массив с метаданными и возможностями модуля:\n\n```php\nuse Modules\\MyModule\\Models\\MyModel;\nuse Modules\\MyModule\\Observers\\MyObserver;\nuse Illuminate\\Console\\Scheduling\\Schedule;\nuse Illuminate\\Support\\Facades\\DB;\n\nreturn [\n    'name'        =\u003e 'Название модуля',\n    'description' =\u003e 'Краткое описание',\n    'info'        =\u003e '\u003cp\u003eДлинное описание с HTML, показывается на странице модуля в админке\u003c/p\u003e',\n    'version'     =\u003e '1.0.0',\n    'requires'    =\u003e '14.0.0',\n    'author'      =\u003e 'Автор',\n    'email'       =\u003e 'author@example.com',\n    'homepage'    =\u003e 'https://example.com',\n\n    // Возможности моделей модуля\n    'models' =\u003e [\n        MyModel::class =\u003e [\n            'label'  =\u003e 'Метка раздела',\n            'search' =\u003e ['view' =\u003e 'MyModule::search/_results', 'with' =\u003e ['user']],\n            'feed'   =\u003e ['with' =\u003e ['user', 'files'], 'view' =\u003e 'MyModule::feeds/_feed'],\n            'upload' =\u003e 'media',\n            'rating' =\u003e true,\n            'spam'   =\u003e true,\n        ],\n    ],\n\n    // Наблюдатели моделей\n    'observers' =\u003e [\n        MyModel::class =\u003e MyObserver::class,\n    ],\n\n    // Планировщик задач\n    'schedule' =\u003e function (Schedule $schedule) {\n        $schedule-\u003ecommand('my-module:cleanup')-\u003edaily();\n    },\n\n    // Пересчёты — вызываются из админки кнопкой «Пересчёт»\n    'restatement' =\u003e [\n        'mymodel' =\u003e function () {\n            DB::update('update ...');\n        },\n    ],\n\n    // Ссылки-действия на странице модуля в админке\n    'actions' =\u003e [\n        '/admin/my-module' =\u003e 'Мой модуль',\n    ],\n\n    // Публикация файлов: копирование из модуля в директории движка\n    'publish' =\u003e [\n        'stubs/views'            =\u003e 'resources/views/vendor/my-module',\n        'stubs/widget.blade.php' =\u003e 'resources/views/themes/default/widgets/my-module.blade.php',\n    ],\n];\n```\n\n### Описание полей\n\n**`name`, `description`, `author`, `email`, `homepage`** — отображаются в карточке модуля.\n\n**`info`** — длинное описание с HTML, видно на странице модуля в админке. Сюда удобно класть инструкции по подключению.\n\n**`version`** — текущая версия модуля. Если в `module.php` версия выше установленной, в админке появляется кнопка «Обновить».\n\n**`requires`** — минимальная версия движка Rotor. При несовместимости модуль помечается в каталоге как «Несовместим».\n\n**`models`** — массив `Class::class =\u003e [возможности]`. Каждая модель автоматически регистрируется в `morphMap` Laravel. Доступные возможности:\n\n| Ключ | Описание |\n|---|---|\n| `label` | Метка раздела модели — название в результатах поиска, на странице «Спам» и в рейтингах. |\n| `search` | Подключает модель к глобальному поиску. `view` — шаблон одного результата, `with` (опц.) — отношения для eager-load. |\n| `feed` | Подключает к общей ленте активности. `with` — отношения для eager-load, `view` — шаблон записи, `scope` (опц.) — замыкание, ограничивающее выборку, `poll` (опц.) — замыкание `fn ($model): ?array`, возвращающее `[морф-имя, id]` связанной записи, если голосование привязано не к самой записи (тема → последний пост). |\n| `upload` | Разрешает прикреплять файлы. `media` — изображения и видео, `file` — любые файлы. |\n| `rating` | `true` — включает лайки/дизлайки. |\n| `spam` | `true` — записи можно помечать как спам (раздел на странице «Спам» в админке; название раздела берётся из `label`). |\n\nЕсли модель нужна только для `morphMap` (например, для полиморфных связей), но не имеет возможностей — оставь пустой массив:\n```php\n'models' =\u003e [\n    Vote::class =\u003e [],\n],\n```\n\n**`observers`** — массив `Class::class =\u003e Observer::class`. Регистрирует Eloquent-наблюдателей.\n\n**`schedule`** — замыкание, получающее `Schedule` Laravel. Регистрирует периодические задачи.\n\n**`restatement`** — массив `'ключ' =\u003e callable`. Пересчёты счётчиков, запускаются из админки или вручную через `Restatement::run('ключ')`.\n\n**`actions`** — массив `URL =\u003e 'Название'`. Ссылки-действия, отображаются на странице модуля в админ-панели.\n\n**`publish`** — массив `источник =\u003e назначение`. Копирует файлы из модуля в произвольные директории движка при установке и удаляет их при отключении/удалении модуля. Подробнее в разделе «Публикация файлов».\n\n---\n\n## История изменений (changelog.md)\n\nНеобязательный файл `changelog.md` в корне модуля. Секции по версиям: заголовок `## X.Y.Z` обязателен, тело — произвольный текст (markdown знать не нужно).\n\n```markdown\n## 1.1.0\n- Добавлена выгрузка в CSV\n- Исправлена пагинация\n\n## 1.0.0\n- Первый релиз\n```\n\nRotor показывает changelog на странице модуля в админке:\n\n- **История изменений** — весь файл, всегда.\n- **Что нового в версии X** — секция новой версии, при доступном обновлении (из реестра, ещё до установки).\n\nПри сборке реестра (`module:registry`) и в CI секция текущей версии попадает в `registry.json`. Файла нет — ничего не ломается, поле просто отсутствует.\n\n---\n\n## Маршруты (routes.php)\n\n```php\nuse Illuminate\\Support\\Facades\\Route;\nuse Modules\\MyModule\\Http\\Controllers\\MyController;\n\nRoute::middleware('web')\n    -\u003econtroller(MyController::class)\n    -\u003eprefix('my-module')\n    -\u003ename('my-module.')\n    -\u003egroup(function () {\n        Route::get('/', 'index')-\u003ename('index');\n        Route::get('/{id}', 'view')-\u003ename('view');\n    });\n\n// Админка\nRoute::middleware(['web', 'check.admin', 'admin.logger'])\n    -\u003eprefix('admin')\n    -\u003egroup(function () {\n        Route::controller(AdminMyController::class)\n            -\u003eprefix('my-module')\n            -\u003ename('admin.my-module.')\n            -\u003egroup(function () {\n                Route::get('/', 'index')-\u003ename('index');\n                Route::delete('/{id}', 'delete')-\u003ename('delete');\n            });\n    });\n```\n\n---\n\n## Контроллеры\n\nПространство имён: `Modules\\MyModule\\Http\\Controllers`\n\n```php\nnamespace Modules\\MyModule\\Http\\Controllers;\n\nclass MyController extends \\App\\Http\\Controllers\\Controller\n{\n    public function index() { ... }\n}\n```\n\nАдминистративные контроллеры размещаются в `Http/Controllers/Admin/` и наследуются от `\\App\\Http\\Controllers\\Admin\\AdminController`.\n\n---\n\n## Модели\n\nПространство имён: `Modules\\MyModule\\Models`\n\n```php\nnamespace Modules\\MyModule\\Models;\n\nclass MyModel extends \\Illuminate\\Database\\Eloquent\\Model\n{\n    public static string $morphName = 'mymodels';\n}\n```\n\n`$morphName` обязательно у моделей, заявленных в `models` (используется ядром для регистрации возможностей).\n\nОграничения морф-имени: максимум **20 символов** (ширина колонки `relate_type` в БД) и неизменность после релиза модуля — имя сохраняется в записях БД.\n\n---\n\n## Хуки (hooks.php)\n\nФайл `hooks.php` содержит:\n- вставки в шаблоны через `Hook::add` (UI-расширения);\n- регистрации в `Registry` для возможностей, которые не привязаны к одной модели (sitemap, complaint, onDeleteUser, onAdminDeleteUser);\n- регистрации `Restatement::register` (если не объявлено в `module.php`).\n\n### Hook::add\n\nХук может быть строкой или callable. Callable получает аргументы из `@hook(...)` и возвращает свой HTML-фрагмент (или `null`/`''` если ничего не добавлять). Все фрагменты склеиваются в порядке убывания `priority`.\n\n```php\nuse App\\Classes\\Hook;\n\n// Статичная строка\nHook::add('sidebarMenuEnd', '\u003cli\u003e\u003ca href=\"/page\"\u003eТекст\u003c/a\u003e\u003c/li\u003e');\n\n// Динамический фрагмент\nHook::add('head', static function () {\n    return '\u003clink rel=\"stylesheet\" href=\"/assets/modules/my-module/style.css\"\u003e';\n});\n\n// С аргументом из @hook('userProfileLinks', $user)\nHook::add('userProfileLinks', static function ($user) {\n    return ' / \u003ca href=\"/my-module/' . $user-\u003elogin . '\"\u003eМои записи\u003c/a\u003e';\n});\n\n// Третий аргумент — приоритет (выше → раньше). По умолчанию 0\nHook::add('sidebarMenuEnd', static fn () =\u003e '\u003cli\u003e...\u003c/li\u003e', 10);\n```\n\nВызов в шаблоне:\n```blade\n@hook('head')\n@hook('userProfileLinks', $user)\n```\n\n### Registry\n\n```php\nuse App\\Classes\\Registry;\nuse Modules\\MyModule\\Models\\MyModel;\n\n// Жалобы — обработчик клика на «пожаловаться»\nRegistry::complaint(MyModel::$morphName, function (int $id) {\n    $model = MyModel::query()-\u003efind($id);\n    return ['model' =\u003e $model, 'path' =\u003e $model?-\u003egetViewUrl(false)];\n});\n\n// Sitemap\nRegistry::sitemap('mymodels', function () {\n    return [['loc' =\u003e route('my-module.index'), 'lastmod' =\u003e gmdate('c')]];\n});\n\n// Удаление пользователя — что подчистить\nRegistry::onDeleteUser(function (\\App\\Models\\User $user) {\n    MyModel::query()-\u003ewhere('user_id', $user-\u003eid)-\u003edelete();\n});\n\n// Удаление пользователя администратором (в Request — чекбоксы формы удаления,\n// добавить свой можно через Hook::add('adminUserDeleteFields', ...))\nRegistry::onAdminDeleteUser(function (\\App\\Models\\User $user, \\Illuminate\\Http\\Request $request) {\n    if ($request-\u003eboolean('delmymodels')) {\n        MyModel::query()-\u003ewhere('user_id', $user-\u003eid)-\u003eget()-\u003eeach-\u003edelete();\n    }\n});\n```\n\nПолный список методов `Registry`:\n\n| Метод | Назначение |\n|---|---|\n| `fileType($morphName)` | тип принимает файлы (вызывается из `module.php` через `'upload' =\u003e 'file'`) |\n| `mediaType($morphName)` | тип принимает фото/видео (`'upload' =\u003e 'media'`) |\n| `ratingType($morphName)` | тип поддерживает рейтинг (`'rating' =\u003e true`) |\n| `spamType($morphName)` | тип — источник жалоб на спам (`'spam' =\u003e true`, метка берётся из `label`) |\n| `label($morphName, $label)` | отображаемое название типа (`'label' =\u003e '...'`) |\n| `feed($class, $config)` | запись в ленте активности (`'feed' =\u003e [...]`) |\n| `search($class, $view, $with)` | полнотекстовый поиск (`'search' =\u003e [...]`) |\n| `complaint($morphName, $handler)` | обработчик жалобы |\n| `sitemap($key, $handler)` | страница в sitemap |\n| `onDeleteUser($handler)` | очистка при удалении пользователя |\n| `onAdminDeleteUser($handler)` | удаление пользователя администратором |\n\nПервые семь обычно регистрируются декларативно через `module.php`, вручную их вызывать не нужно.\n\n---\n\n## Шаблоны\n\nФайлы в `resources/views/` вызываются с указанием неймспейса модуля:\n\n```php\nview('MyModule::directory/file')\n// → resources/views/directory/file.blade.php\n```\n\n---\n\n## Переводы\n\nФайлы в `resources/lang/ru/`, `resources/lang/en/` и т.д.:\n\n```php\n__('MyModule::file.key')\n// → resources/lang/ru/file.php → ['key' =\u003e '...']\n```\n\n---\n\n## Конфигурация (config.php)\n\n```php\n// config.php\nreturn [\n    'api_key' =\u003e env('MY_MODULE_API_KEY'),\n    'limit'   =\u003e 10,\n];\n\n// Использование\nconfig('MyModule.api_key');\n```\n\nЗначения из админки записываются в поле `settings` модуля и сливаются поверх `config.php` при загрузке.\n\n---\n\n## Helpers (helpers.php)\n\nГлобальные функции, доступные везде:\n\n```php\nif (! function_exists('statsMyModule')) {\n    function statsMyModule(): string\n    {\n        return (string) MyModel::query()-\u003ecount();\n    }\n}\n```\n\n---\n\n## Middleware (middleware.php)\n\n```php\nuse Modules\\MyModule\\Middleware\\MyMiddleware;\n\nreturn [\n    // Алиасы для применения в routes.php через -\u003emiddleware('alias')\n    'aliases' =\u003e [\n        'my-alias' =\u003e MyMiddleware::class,\n    ],\n\n    // Middleware, добавляемые в группу web автоматически\n    'web' =\u003e [\n        MyMiddleware::class,\n    ],\n];\n```\n\n---\n\n## Консольные команды\n\nФайлы в `Console/` подхватываются автоматически. Имя класса = имя файла:\n\n```php\n// Console/Cleanup.php\nnamespace Modules\\MyModule\\Console;\n\nuse Illuminate\\Console\\Command;\n\nclass Cleanup extends Command\n{\n    protected $signature = 'my-module:cleanup';\n\n    public function handle(): void { /* ... */ }\n}\n```\n\n---\n\n## Статические файлы\n\nФайлы из `resources/assets/` доступны по адресу:\n```\n/assets/modules/my-module/\n```\n\nСимлинк создаётся автоматически при установке модуля.\n\n---\n\n## Публикация файлов (publish)\n\nКлюч `publish` в `module.php` копирует файлы из модуля в любые директории движка. В отличие от симлинка `resources/assets/` (доступ к статике по URL), это нужно когда файл должен физически лежать в ядре — переопределение Blade-шаблонов темы, вставка виджета в тему, файлы в корне проекта (`favicon.ico`, `robots.txt`).\n\n```php\n'publish' =\u003e [\n    'stubs/views'            =\u003e 'resources/views/vendor/my-module',\n    'stubs/widget.blade.php' =\u003e 'resources/views/themes/default/widgets/my-module.blade.php',\n],\n```\n\n- **Источник** — путь относительно папки модуля (`modules/MyModule/`).\n- **Назначение** — путь относительно корня проекта (`base_path()`).\n- Если источник — директория, копируется/удаляется директория; если файл — файл.\n- Копирование выполняется при **установке и включении**, удаление — при **отключении и удалении** модуля.\n- При **обновлении ядра** файлы перепубликуются автоматически (деплой и обновление через админку вызывают `module:sync`), так что затёртые апдейтом оверрайды восстанавливаются.\n- Пути с `..` игнорируются.\n\n\u003e Удаление снимает с диска именно опубликованный путь назначения. Не указывайте в назначении общий каталог движка (например `config` или `public`) — при отключении он будет удалён целиком. Используйте выделенные пути (`resources/views/vendor/my-module`, `public/assets/my-module`).\n\n---\n\n## Примеры минимальных модулей\n\n**Только маршруты и контроллер:**\n```\nMyModule/module.php, routes.php, Http/Controllers/MyController.php\n```\n\n**Только миграции (изменение БД):**\n```\nMyModule/module.php, database/migrations/\n```\n\n**Только внешний вид (хуки):**\n```\nMyModule/module.php, hooks.php\n```\n\nСм. модуль `Template` — минимальный шаблон для старта.\n\n---\n\n## License\n\nThe Rotor is open-sourced software licensed under the [GPL-3.0 license](http://opensource.org/licenses/GPL-3.0)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvisavi%2Frotor-modules","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvisavi%2Frotor-modules","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvisavi%2Frotor-modules/lists"}