@maxisoft/instantcms-mcp 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -0
  3. package/dist/__tests__/artifact-tool.test.js +196 -0
  4. package/dist/__tests__/db-tool.test.js +152 -0
  5. package/dist/__tests__/define-tool.test.js +106 -0
  6. package/dist/__tests__/find-tool.test.js +185 -0
  7. package/dist/__tests__/get-component-api.test.js +144 -0
  8. package/dist/__tests__/hardening.test.js +73 -0
  9. package/dist/__tests__/hooks-tool.test.js +173 -0
  10. package/dist/__tests__/mcp-integration-extra.test.js +166 -0
  11. package/dist/__tests__/mcp-integration.test.js +53 -0
  12. package/dist/__tests__/pagination.test.js +114 -0
  13. package/dist/__tests__/performance-baseline.test.js +56 -0
  14. package/dist/__tests__/project-patch.test.js +144 -0
  15. package/dist/__tests__/project-source.test.js +44 -0
  16. package/dist/__tests__/project-workflows-extra.test.js +165 -0
  17. package/dist/__tests__/project-workflows.test.js +51 -0
  18. package/dist/__tests__/scaffold-addon-roundtrip.test.js +287 -0
  19. package/dist/__tests__/serialization.test.js +170 -0
  20. package/dist/__tests__/source-knowledge-parsers.test.js +42 -0
  21. package/dist/__tests__/template-development.test.js +78 -0
  22. package/dist/__tests__/template-productivity.test.js +87 -0
  23. package/dist/__tests__/tools.test.js +2143 -0
  24. package/dist/data/components.js +2818 -0
  25. package/dist/data/controllers-map.js +7571 -0
  26. package/dist/data/core-api.js +9356 -0
  27. package/dist/data/database-schema.js +4555 -0
  28. package/dist/data/events-map.js +1339 -0
  29. package/dist/data/fields-map.js +3692 -0
  30. package/dist/data/hooks.js +2257 -0
  31. package/dist/data/js-api.js +123 -0
  32. package/dist/data/libs-api.js +264 -0
  33. package/dist/data/routes-map.js +203 -0
  34. package/dist/data/schemas-validation.js +255 -0
  35. package/dist/data/schemas.js +1404 -0
  36. package/dist/data/traits-map.js +1004 -0
  37. package/dist/data/version-profiles.js +41 -0
  38. package/dist/data/widgets-map.js +131 -0
  39. package/dist/data/wysiwyg-map.js +486 -0
  40. package/dist/generated/components-source.js +14748 -0
  41. package/dist/generated/hooks-source.js +3399 -0
  42. package/dist/generated/knowledge-meta.js +101 -0
  43. package/dist/index.js +13 -0
  44. package/dist/registry/database-tools.js +112 -0
  45. package/dist/registry/extension-tools.js +473 -0
  46. package/dist/registry/generator-tools.js +506 -0
  47. package/dist/registry/knowledge-tools.js +441 -0
  48. package/dist/registry/language-tools.js +99 -0
  49. package/dist/registry/meta-tools.js +152 -0
  50. package/dist/registry/project-tools.js +48 -0
  51. package/dist/registry/resources.js +98 -0
  52. package/dist/registry/source-tools.js +211 -0
  53. package/dist/registry/template-development-tools.js +79 -0
  54. package/dist/server.js +32 -0
  55. package/dist/tools/addon-tool.js +1437 -0
  56. package/dist/tools/admin-partial-tool.js +498 -0
  57. package/dist/tools/api-tool.js +294 -0
  58. package/dist/tools/artifact-tool.js +95 -0
  59. package/dist/tools/cache-tool.js +360 -0
  60. package/dist/tools/component-tool.js +415 -0
  61. package/dist/tools/controllers-tool.js +125 -0
  62. package/dist/tools/cron-tool.js +197 -0
  63. package/dist/tools/crud-tool.js +659 -0
  64. package/dist/tools/db-tool.js +198 -0
  65. package/dist/tools/email-tool.js +222 -0
  66. package/dist/tools/external-api-tool.js +596 -0
  67. package/dist/tools/filter-tool.js +341 -0
  68. package/dist/tools/form-tool.js +275 -0
  69. package/dist/tools/grid-tool.js +189 -0
  70. package/dist/tools/hooks-tool.js +104 -0
  71. package/dist/tools/import-export-tool.js +548 -0
  72. package/dist/tools/lang-tool.js +251 -0
  73. package/dist/tools/layout-override-tool.js +125 -0
  74. package/dist/tools/layout-tool.js +548 -0
  75. package/dist/tools/maria-tool.js +127 -0
  76. package/dist/tools/mariadb.js +201 -0
  77. package/dist/tools/migration-tool.js +329 -0
  78. package/dist/tools/oauth-tool.js +520 -0
  79. package/dist/tools/parser/components-parser.js +76 -0
  80. package/dist/tools/parser/controllers-parser.js +313 -0
  81. package/dist/tools/parser/core-parser.js +294 -0
  82. package/dist/tools/parser/coverage-generator.js +424 -0
  83. package/dist/tools/parser/events-parser.js +127 -0
  84. package/dist/tools/parser/fields-parser.js +382 -0
  85. package/dist/tools/parser/hooks-parser.js +106 -0
  86. package/dist/tools/parser/sql-parser.js +197 -0
  87. package/dist/tools/parser/traits-parser.js +161 -0
  88. package/dist/tools/parser/widgets-parser.js +150 -0
  89. package/dist/tools/permission-tool.js +348 -0
  90. package/dist/tools/project-patch-tool.js +73 -0
  91. package/dist/tools/project-source-tool.js +188 -0
  92. package/dist/tools/project-workflow-tool.js +186 -0
  93. package/dist/tools/requirement-tool.js +212 -0
  94. package/dist/tools/scaffold-tool.js +815 -0
  95. package/dist/tools/seo-tool.js +412 -0
  96. package/dist/tools/source-tool.js +155 -0
  97. package/dist/tools/template-development-tool.js +271 -0
  98. package/dist/tools/template-overrides-tool.js +294 -0
  99. package/dist/tools/template-productivity-tool.js +319 -0
  100. package/dist/tools/template-tool.js +665 -0
  101. package/dist/tools/test-tool.js +183 -0
  102. package/dist/tools/webhook-tool.js +427 -0
  103. package/dist/tools/widget-tool.js +331 -0
  104. package/dist/tools/wysiwyg-tool.js +137 -0
  105. package/dist/types/scaffold.js +68 -0
  106. package/dist/utils/define-tool.js +37 -0
  107. package/dist/utils/find-tool.js +97 -0
  108. package/dist/utils/mcp-result.js +18 -0
  109. package/dist/utils/pagination.js +27 -0
  110. package/dist/utils/serialization.js +27 -0
  111. package/package.json +94 -0
@@ -0,0 +1,2257 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hookCategories = exports.hooks = void 0;
4
+ const hooks_source_js_1 = require("../generated/hooks-source.js");
5
+ const curatedHooks = [
6
+ // ── ENGINE ──────────────────────────────────────────────────────────────────
7
+ {
8
+ name: 'engine_start',
9
+ type: 'action',
10
+ category: 'engine',
11
+ description: 'Вызывается при инициализации приложения. Используется для регистрации ресурсов и начальной настройки.',
12
+ parameters: [{ name: '$data', type: 'array', description: 'Пустой массив' }],
13
+ return_type: 'void',
14
+ example: `class onMyaddonEngineStart extends cmsAction {
15
+ public function run($data) {
16
+ // Подключение к базе данных, инициализация компонентов
17
+ $this->model->initMyAddon();
18
+ // Регистрация обработчиков событий
19
+ cmsEventsManager::hook('my_custom_event', [$this, 'handleCustomEvent']);
20
+ return $data;
21
+ }
22
+ }`,
23
+ },
24
+ {
25
+ name: 'engine_stop',
26
+ type: 'action',
27
+ category: 'engine',
28
+ description: 'Вызывается при завершении работы приложения.',
29
+ parameters: [{ name: '$data', type: 'array', description: 'Пустой массив' }],
30
+ return_type: 'void',
31
+ example: `class onMyaddonEngineStop extends cmsAction {
32
+ public function run($data) {
33
+ // Сохранение сессии, закрытие соединений, запись логов
34
+ $this->model->saveSessionData();
35
+ cmsCache::getInstance()->shutdown();
36
+ return $data;
37
+ }
38
+ }`,
39
+ },
40
+ {
41
+ name: 'page_is_allowed',
42
+ type: 'filter',
43
+ category: 'engine',
44
+ description: 'Проверка доступа к странице. Вернуть false — запретить доступ.',
45
+ parameters: [
46
+ {
47
+ name: '$is_allowed',
48
+ type: 'bool',
49
+ description: 'Текущий статус доступа',
50
+ modifiable: true,
51
+ },
52
+ ],
53
+ return_type: 'bool',
54
+ example: `class onMyaddonPageIsAllowed extends cmsAction {
55
+ public function run($is_allowed) {
56
+ if (/* условие */) { return false; }
57
+ return $is_allowed;
58
+ }
59
+ }`,
60
+ },
61
+ {
62
+ name: 'error_404',
63
+ type: 'action',
64
+ category: 'engine',
65
+ description: 'Вызывается при ошибке 404.',
66
+ parameters: [{ name: '$data', type: 'array', description: 'Данные запроса' }],
67
+ return_type: 'void',
68
+ example: `class onMyaddonError404 extends cmsAction {
69
+ public function run($data) {
70
+ // Логирование 404 для аналитики
71
+ $this->model->log404([
72
+ 'url' => $_SERVER['REQUEST_URI'] ?? '',
73
+ 'referer' => $_SERVER['HTTP_REFERER'] ?? '',
74
+ 'user_id' => $this->cms_user->id,
75
+ 'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
76
+ 'created_at' => date('Y-m-d H:i:s')
77
+ ]);
78
+ // Перенаправление на страницу поиска или главную
79
+ if (strpos($_SERVER['REQUEST_URI'], '/products/') === 0) {
80
+ cmsCore::redirect(href_to('search') . '?q=' . urlencode(basename($_SERVER['REQUEST_URI'])));
81
+ }
82
+ return $data;
83
+ }
84
+ }`,
85
+ },
86
+ {
87
+ name: 'parse_text',
88
+ type: 'filter',
89
+ category: 'engine',
90
+ description: 'Фильтрация и преобразование текстового контента (BBcode, шорткоды и т.д.). Вызывается при обработке текстов перед выводом.',
91
+ parameters: [
92
+ { name: '$html', type: 'string', description: 'HTML-строка для обработки', modifiable: true },
93
+ ],
94
+ return_type: 'string',
95
+ example: `class onMyaddonParseText extends cmsAction {
96
+ public function run($html) {
97
+ // Заменить шорткод [youtube:ID] на embed
98
+ $html = preg_replace('/\[youtube:([a-zA-Z0-9_-]+)\]/', '<iframe src="https://www.youtube.com/embed/$1"></iframe>', $html);
99
+ return $html;
100
+ }
101
+ }`,
102
+ },
103
+ {
104
+ name: 'html_filter',
105
+ type: 'filter',
106
+ category: 'engine',
107
+ description: 'Фильтрация HTML контента (тексты, описания). Вызывается при финальном выводе HTML.',
108
+ parameters: [{ name: '$html', type: 'string', description: 'HTML контент', modifiable: true }],
109
+ return_type: 'string',
110
+ example: `class onMyaddonHtmlFilter extends cmsAction {
111
+ public function run($html) {
112
+ $html = preg_replace('/\[mycode\]/', '<div class="mycode">...</div>', $html);
113
+ return $html;
114
+ }
115
+ }`,
116
+ },
117
+ {
118
+ name: 'before_send_email',
119
+ type: 'filter',
120
+ category: 'engine',
121
+ description: 'Вызывается перед отправкой любого письма. Позволяет изменить данные письма.',
122
+ parameters: [
123
+ { name: "$data['to']", type: 'string', description: 'Email получателя', modifiable: true },
124
+ { name: "$data['subject']", type: 'string', description: 'Тема письма', modifiable: true },
125
+ { name: "$data['body']", type: 'string', description: 'Тело письма', modifiable: true },
126
+ ],
127
+ return_type: 'array',
128
+ example: `class onMyaddonBeforeSendEmail extends cmsAction {
129
+ public function run($data) {
130
+ // Добавить подпись к теме
131
+ $data['subject'] = '[MySite] ' . $data['subject'];
132
+ return $data;
133
+ }
134
+ }`,
135
+ },
136
+ {
137
+ name: 'site_settings_after_update',
138
+ type: 'action',
139
+ category: 'engine',
140
+ description: 'Вызывается после сохранения общих настроек сайта.',
141
+ parameters: [{ name: '$values', type: 'array', description: 'Сохранённые настройки сайта' }],
142
+ return_type: 'void',
143
+ example: `class onMyaddonSiteSettingsAfterUpdate extends cmsAction {
144
+ public function run($values) {
145
+ // Очистить кэш при изменении настроек
146
+ cmsCache::getInstance()->flush();
147
+ return $values;
148
+ }
149
+ }`,
150
+ },
151
+ {
152
+ name: 'site_settings_before_update',
153
+ type: 'filter',
154
+ category: 'engine',
155
+ description: 'Вызывается перед сохранением общих настроек сайта.',
156
+ parameters: [
157
+ { name: '$values', type: 'array', description: 'Данные для сохранения', modifiable: true },
158
+ ],
159
+ return_type: 'array',
160
+ example: `class onMyaddonSiteSettingsBeforeUpdate extends cmsAction {
161
+ public function run($values) {
162
+ return $values;
163
+ }
164
+ }`,
165
+ },
166
+ // ── CONTENT ──────────────────────────────────────────────────────────────────
167
+ {
168
+ name: 'content_before_add',
169
+ type: 'filter',
170
+ category: 'content',
171
+ description: 'Вызывается перед добавлением нового материала. Позволяет изменить данные перед сохранением.',
172
+ parameters: [
173
+ { name: "$data['ctype_name']", type: 'string', description: 'Имя типа контента' },
174
+ {
175
+ name: "$data['item']",
176
+ type: 'array',
177
+ description: 'Данные нового материала',
178
+ modifiable: true,
179
+ },
180
+ ],
181
+ return_type: 'array',
182
+ example: `class onMyaddonContentBeforeAdd extends cmsAction {
183
+ public function run($data) {
184
+ $data['item']['my_field'] = 'default_value';
185
+ return $data;
186
+ }
187
+ }`,
188
+ },
189
+ {
190
+ name: 'content_after_add_approve',
191
+ type: 'action',
192
+ category: 'content',
193
+ description: 'Вызывается после одобрения нового материала. Используется для уведомлений, добавления в активность и т.д.',
194
+ parameters: [
195
+ {
196
+ name: "$data['ctype_name']",
197
+ type: 'string',
198
+ description: "Имя типа контента (например: 'articles')",
199
+ },
200
+ {
201
+ name: "$data['item']",
202
+ type: 'array',
203
+ description: 'Данные материала: id, title, slug, user_id, is_pub, url и др.',
204
+ },
205
+ ],
206
+ return_type: 'array',
207
+ example: `class onMyaddonContentAfterAddApprove extends cmsAction {
208
+ public function run($data) {
209
+ $ctype_name = $data['ctype_name'];
210
+ $item = $data['item'];
211
+ // Добавить запись активности
212
+ $activity = cmsCore::getController('activity');
213
+ $activity->addEntry('myaddon_new_item', ['item_id' => $item['id']]);
214
+ return $data;
215
+ }
216
+ }`,
217
+ },
218
+ {
219
+ name: 'content_before_delete',
220
+ type: 'filter',
221
+ category: 'content',
222
+ description: 'Вызывается перед удалением материала. Можно отменить удаление, вернув false.',
223
+ parameters: [
224
+ { name: "$data['ctype_name']", type: 'string', description: 'Имя типа контента' },
225
+ { name: "$data['item']", type: 'array', description: 'Данные удаляемого материала' },
226
+ ],
227
+ return_type: 'array|false',
228
+ example: `class onMyaddonContentBeforeDelete extends cmsAction {
229
+ public function run($data) {
230
+ $ctype_name = $data['ctype_name'];
231
+ $item = $data['item'];
232
+
233
+ // Проверка, можно ли удалять материал
234
+ if ($item['is_system']) {
235
+ cmsCore::addSessionMessage('Системные материалы удалять нельзя', 'error');
236
+ return false;
237
+ }
238
+
239
+ // Удаление связанных файлов
240
+ $this->model->deleteRelatedFiles($item['id']);
241
+
242
+ // Удаление из поискового индекса
243
+ cmsCore::getModel('search')->removeItem($ctype_name, $item['id']);
244
+
245
+ return $data;
246
+ }
247
+ }`,
248
+ },
249
+ {
250
+ name: 'content_after_delete',
251
+ type: 'action',
252
+ category: 'content',
253
+ description: 'Вызывается после удаления материала. Используется для очистки связанных данных.',
254
+ parameters: [
255
+ { name: "$data['ctype_name']", type: 'string', description: 'Имя типа контента' },
256
+ { name: "$data['item']", type: 'array', description: 'Удалённый материал' },
257
+ ],
258
+ return_type: 'array',
259
+ example: `class onMyaddonContentAfterDelete extends cmsAction {
260
+ public function run($data) {
261
+ $item_id = $data['item']['id'];
262
+ $this->model->filterEqual('item_id', $item_id)->deleteFiltered('myaddon_links');
263
+ return $data;
264
+ }
265
+ }`,
266
+ },
267
+ {
268
+ name: 'content_after_update_approve',
269
+ type: 'action',
270
+ category: 'content',
271
+ description: 'Вызывается после одобрения редактирования материала.',
272
+ parameters: [
273
+ { name: "$data['ctype_name']", type: 'string', description: 'Имя типа контента' },
274
+ { name: "$data['item']", type: 'array', description: 'Обновлённые данные материала' },
275
+ ],
276
+ return_type: 'array',
277
+ example: `class onMyaddonContentAfterUpdateApprove extends cmsAction {
278
+ public function run($data) {
279
+ $ctype_name = $data['ctype_name'];
280
+ $item = $data['item'];
281
+
282
+ // Отправка уведомления подписчикам об изменении
283
+ $this->model->notifySubscribers($ctype_name, $item['id'], 'update');
284
+
285
+ // Добавление в ленту активности
286
+ $activity = cmsCore::getController('activity');
287
+ $activity->addEntry('myaddon_item_updated', [
288
+ 'ctype_name' => $ctype_name,
289
+ 'item_id' => $item['id'],
290
+ 'title' => $item['title']
291
+ ]);
292
+
293
+ // Обновление sitemap
294
+ cmsCore::getController('sitemap')->recacheUrl($ctype_name, $item['id']);
295
+
296
+ return $data;
297
+ }
298
+ }`,
299
+ },
300
+ {
301
+ name: 'content_after_restore',
302
+ type: 'action',
303
+ category: 'content',
304
+ description: 'Вызывается после восстановления материала из корзины.',
305
+ parameters: [
306
+ { name: "$data['ctype_name']", type: 'string', description: 'Имя типа контента' },
307
+ { name: "$data['item']", type: 'array', description: 'Восстановленный материал' },
308
+ ],
309
+ return_type: 'array',
310
+ example: `class onMyaddonContentAfterRestore extends cmsAction {
311
+ public function run($data) {
312
+ $ctype_name = $data['ctype_name'];
313
+ $item = $data['item'];
314
+
315
+ // Восстановление связей
316
+ $this->model->restoreRelations($ctype_name, $item['id']);
317
+
318
+ // Возврат в поисковый индекс
319
+ cmsCore::getModel('search')->indexItem($ctype_name, $item['id']);
320
+
321
+ // Отправка уведомления автору
322
+ $notice = cmsCore::getController('messages');
323
+ $notice->addNotice($item['user_id'], 'content_restored', [
324
+ 'ctype_name' => $ctype_name,
325
+ 'item_id' => $item['id'],
326
+ 'title' => $item['title']
327
+ ]);
328
+
329
+ return $data;
330
+ }
331
+ }`,
332
+ },
333
+ {
334
+ name: 'content_after_trash_put',
335
+ type: 'action',
336
+ category: 'content',
337
+ description: 'Вызывается после перемещения материала в корзину.',
338
+ parameters: [
339
+ { name: "$data['ctype_name']", type: 'string', description: 'Имя типа контента' },
340
+ { name: "$data['item']", type: 'array', description: 'Перемещённый материал' },
341
+ ],
342
+ return_type: 'array',
343
+ example: `class onMyaddonContentAfterTrashPut extends cmsAction {
344
+ public function run($data) {
345
+ $ctype_name = $data['ctype_name'];
346
+ $item = $data['item'];
347
+
348
+ // Удаление из поискового индекса
349
+ cmsCore::getModel('search')->removeItem($ctype_name, $item['id']);
350
+
351
+ // Отправка уведомления автору
352
+ $notice = cmsCore::getController('messages');
353
+ $notice->addNotice($item['user_id'], 'content_trashed', [
354
+ 'ctype_name' => $ctype_name,
355
+ 'item_id' => $item['id'],
356
+ 'title' => $item['title']
357
+ ]);
358
+
359
+ // Логирование действия
360
+ $this->model->logAction('trash', $ctype_name, $item['id'], $item['user_id']);
361
+
362
+ return $data;
363
+ }
364
+ }`,
365
+ },
366
+ {
367
+ name: 'content_before_item',
368
+ type: 'filter',
369
+ category: 'content',
370
+ description: 'Вызывается перед отображением материала. Позволяет изменить данные и добавить поля.',
371
+ parameters: [
372
+ {
373
+ name: '$data[0]',
374
+ type: 'array',
375
+ description: 'Данные типа контента ($ctype)',
376
+ modifiable: true,
377
+ },
378
+ {
379
+ name: '$data[1]',
380
+ type: 'array',
381
+ description: 'Данные материала ($item)',
382
+ modifiable: true,
383
+ },
384
+ {
385
+ name: '$data[2]',
386
+ type: 'array',
387
+ description: 'Поля материала ($fields)',
388
+ modifiable: true,
389
+ },
390
+ ],
391
+ return_type: 'array',
392
+ example: `class onMyaddonContentBeforeItem extends cmsAction {
393
+ public function run($data) {
394
+ [$ctype, $item, $fields] = $data;
395
+ // Добавить дополнительные данные
396
+ $item['rating'] = $this->model->getRating($item['id']);
397
+ return [$ctype, $item, $fields];
398
+ }
399
+ }`,
400
+ },
401
+ {
402
+ name: 'content_before_list',
403
+ type: 'filter',
404
+ category: 'content',
405
+ description: 'Вызывается перед отображением списка материалов.',
406
+ parameters: [
407
+ { name: '$items', type: 'array', description: 'Массив материалов', modifiable: true },
408
+ ],
409
+ return_type: 'array',
410
+ example: `class onMyaddonContentBeforeList extends cmsAction {
411
+ public function run($items) {
412
+ foreach ($items as &$item) {
413
+ $item['badge'] = $this->getBadge($item['id']);
414
+ }
415
+ return $items;
416
+ }
417
+ }`,
418
+ },
419
+ {
420
+ name: 'content_list_filter',
421
+ type: 'filter',
422
+ category: 'content',
423
+ description: 'Вызывается для применения дополнительных фильтров к запросу списка материалов.',
424
+ parameters: [
425
+ {
426
+ name: '$model',
427
+ type: 'cmsModel',
428
+ description: 'Объект модели для добавления фильтров',
429
+ modifiable: true,
430
+ },
431
+ ],
432
+ return_type: 'cmsModel',
433
+ example: `class onMyaddonContentListFilter extends cmsAction {
434
+ public function run($model) {
435
+ // Добавить фильтр
436
+ $model->filterEqual('my_field', 1);
437
+ return $model;
438
+ }
439
+ }`,
440
+ },
441
+ {
442
+ name: 'content_validate',
443
+ type: 'filter',
444
+ category: 'content',
445
+ description: 'Валидация данных материала перед сохранением. Вернуть массив ошибок.',
446
+ parameters: [
447
+ { name: '$errors', type: 'array', description: 'Текущие ошибки валидации', modifiable: true },
448
+ ],
449
+ return_type: 'array',
450
+ example: `class onMyaddonContentValidate extends cmsAction {
451
+ public function run($errors) {
452
+ if (empty($this->request->post('my_field'))) {
453
+ $errors[] = 'Поле обязательно для заполнения';
454
+ }
455
+ return $errors;
456
+ }
457
+ }`,
458
+ },
459
+ {
460
+ name: 'content_item_form',
461
+ type: 'filter',
462
+ category: 'content',
463
+ description: 'Модификация формы добавления/редактирования материала.',
464
+ parameters: [
465
+ { name: '$data[0]', type: 'cmsForm', description: 'Объект формы ($form)', modifiable: true },
466
+ { name: '$data[1]', type: 'array', description: 'Тип контента ($ctype)' },
467
+ { name: '$data[2]', type: 'array', description: 'Данные материала ($item)' },
468
+ ],
469
+ return_type: 'array',
470
+ example: `class onMyaddonContentItemForm extends cmsAction {
471
+ public function run($data) {
472
+ [$form, $ctype, $item] = $data;
473
+ $form->addField('my_field', new fieldString('my_field', ['title' => 'Моё поле']));
474
+ return [$form, $ctype, $item];
475
+ }
476
+ }`,
477
+ },
478
+ {
479
+ name: 'content_add_permissions',
480
+ type: 'filter',
481
+ category: 'content',
482
+ description: 'Проверка прав на добавление контента. Вернуть false — запретить.',
483
+ parameters: [
484
+ {
485
+ name: '$is_allowed',
486
+ type: 'bool',
487
+ description: 'Разрешено ли добавление',
488
+ modifiable: true,
489
+ },
490
+ ],
491
+ return_type: 'bool',
492
+ example: `class onMyaddonContentAddPermissions extends cmsAction {
493
+ public function run($is_allowed) {
494
+ return $is_allowed && $this->checkMyCondition();
495
+ }
496
+ }`,
497
+ },
498
+ {
499
+ name: 'content_delete_permissions',
500
+ type: 'filter',
501
+ category: 'content',
502
+ description: 'Проверка прав на удаление материала.',
503
+ parameters: [
504
+ { name: '$is_allowed', type: 'bool', description: 'Разрешено ли удаление', modifiable: true },
505
+ ],
506
+ return_type: 'bool',
507
+ example: `class onMyaddonContentDeletePermissions extends cmsAction {
508
+ public function run($is_allowed) {
509
+ return $is_allowed;
510
+ }
511
+ }`,
512
+ },
513
+ {
514
+ name: 'content_edit_permissions',
515
+ type: 'filter',
516
+ category: 'content',
517
+ description: 'Проверка прав на редактирование материала.',
518
+ parameters: [
519
+ {
520
+ name: '$is_allowed',
521
+ type: 'bool',
522
+ description: 'Разрешено ли редактирование',
523
+ modifiable: true,
524
+ },
525
+ ],
526
+ return_type: 'bool',
527
+ example: `class onMyaddonContentEditPermissions extends cmsAction {
528
+ public function run($is_allowed) {
529
+ return $is_allowed;
530
+ }
531
+ }`,
532
+ },
533
+ {
534
+ name: 'content_toolbar_html',
535
+ type: 'filter',
536
+ category: 'content',
537
+ description: 'Добавление HTML в тулбар страницы материала.',
538
+ parameters: [
539
+ { name: '$html', type: 'string', description: 'Текущий HTML тулбара', modifiable: true },
540
+ ],
541
+ return_type: 'string',
542
+ example: `class onMyaddonContentToolbarHtml extends cmsAction {
543
+ public function run($html) {
544
+ $html .= '<a href="..." class="btn">Моя кнопка</a>';
545
+ return $html;
546
+ }
547
+ }`,
548
+ },
549
+ // ── CONTENT TYPES (CTYPES) ───────────────────────────────────────────────────
550
+ {
551
+ name: 'ctype_after_add',
552
+ type: 'action',
553
+ category: 'content',
554
+ description: 'Вызывается после создания нового типа контента.',
555
+ parameters: [{ name: '$ctype', type: 'array', description: 'Данные созданного типа контента' }],
556
+ return_type: 'void',
557
+ example: `class onMyaddonCtypeAfterAdd extends cmsAction {
558
+ public function run($ctype) {
559
+ // Создать связанные структуры для нового типа контента
560
+ return $ctype;
561
+ }
562
+ }`,
563
+ },
564
+ {
565
+ name: 'ctype_before_delete',
566
+ type: 'filter',
567
+ category: 'content',
568
+ description: 'Вызывается перед удалением типа контента. Используется для очистки связанных данных.',
569
+ parameters: [{ name: '$ctype', type: 'array', description: 'Данные удаляемого типа контента' }],
570
+ return_type: 'array',
571
+ example: `class onMyaddonCtypeBeforeDelete extends cmsAction {
572
+ public function run($ctype) {
573
+ // Удалить связанные данные типа контента
574
+ $this->model->filterEqual('ctype_id', $ctype['id'])->deleteFiltered('myaddon_ctype_data');
575
+ return $ctype;
576
+ }
577
+ }`,
578
+ },
579
+ {
580
+ name: 'ctype_content_fields',
581
+ type: 'filter',
582
+ category: 'content',
583
+ description: 'Добавление дополнительных полей к типу контента.',
584
+ parameters: [
585
+ {
586
+ name: '$fields',
587
+ type: 'array',
588
+ description: 'Массив полей типа контента',
589
+ modifiable: true,
590
+ },
591
+ { name: '$ctype', type: 'array', description: 'Данные типа контента' },
592
+ ],
593
+ return_type: 'array',
594
+ example: `class onMyaddonCtypeContentFields extends cmsAction {
595
+ public function run($fields) {
596
+ return $fields;
597
+ }
598
+ }`,
599
+ },
600
+ // ── USERS / AUTH ──────────────────────────────────────────────────────────────
601
+ {
602
+ name: 'user_registered',
603
+ type: 'action',
604
+ category: 'users',
605
+ description: 'Вызывается сразу после регистрации нового пользователя.',
606
+ parameters: [
607
+ {
608
+ name: '$user',
609
+ type: 'array',
610
+ description: 'Данные нового пользователя: id, login, email, group_id',
611
+ },
612
+ ],
613
+ return_type: 'array',
614
+ example: `class onMyaddonUserRegistered extends cmsAction {
615
+ public function run($user) {
616
+ // Создать профильные данные
617
+ $this->model->insert('myaddon_profiles', ['user_id' => $user['id']]);
618
+ return $user;
619
+ }
620
+ }`,
621
+ },
622
+ {
623
+ name: 'registration_validation',
624
+ type: 'filter',
625
+ category: 'users',
626
+ description: 'Дополнительная валидация при регистрации. Позволяет добавить ошибки или изменить данные.',
627
+ parameters: [
628
+ { name: '$data[0]', type: 'array', description: 'Массив ошибок ($errors)', modifiable: true },
629
+ {
630
+ name: '$data[1]',
631
+ type: 'array',
632
+ description: 'Данные регистрационной формы ($user)',
633
+ modifiable: true,
634
+ },
635
+ ],
636
+ return_type: 'array',
637
+ example: `class onMyaddonRegistrationValidation extends cmsAction {
638
+ public function run($data) {
639
+ [$errors, $user] = $data;
640
+ if (empty($user['invite_code'])) {
641
+ $errors[] = 'Необходим код приглашения';
642
+ }
643
+ return [$errors, $user];
644
+ }
645
+ }`,
646
+ },
647
+ {
648
+ name: 'auth_login',
649
+ type: 'action',
650
+ category: 'users',
651
+ description: 'Вызывается при успешной авторизации пользователя.',
652
+ parameters: [{ name: '$user_id', type: 'int', description: 'ID вошедшего пользователя' }],
653
+ return_type: 'void',
654
+ example: `class onMyaddonAuthLogin extends cmsAction {
655
+ public function run($user_id) {
656
+ // Обновить время последнего входа в своей таблице
657
+ $this->model->update('myaddon_stats', null, ['last_login' => date('Y-m-d H:i:s'), 'user_id' => $user_id], 'user_id');
658
+ return $user_id;
659
+ }
660
+ }`,
661
+ },
662
+ {
663
+ name: 'auth_logout',
664
+ type: 'action',
665
+ category: 'users',
666
+ description: 'Вызывается при выходе пользователя.',
667
+ parameters: [{ name: '$user_id', type: 'int', description: 'ID выходящего пользователя' }],
668
+ return_type: 'void',
669
+ example: `class onMyaddonAuthLogout extends cmsAction {
670
+ public function run($user_id) {
671
+ return $user_id;
672
+ }
673
+ }`,
674
+ },
675
+ {
676
+ name: 'user_delete',
677
+ type: 'action',
678
+ category: 'users',
679
+ description: 'Вызывается при удалении пользователя. Используется для очистки связанных данных.',
680
+ parameters: [
681
+ {
682
+ name: '$user',
683
+ type: 'array',
684
+ description: 'Данные удаляемого пользователя: id, login, email',
685
+ },
686
+ ],
687
+ return_type: 'array',
688
+ example: `class onMyaddonUserDelete extends cmsAction {
689
+ public function run($user) {
690
+ $this->model->filterEqual('user_id', $user['id'])->deleteFiltered('myaddon_data');
691
+ return $user;
692
+ }
693
+ }`,
694
+ },
695
+ {
696
+ name: 'users_before_update',
697
+ type: 'filter',
698
+ category: 'users',
699
+ description: 'Вызывается перед сохранением обновлённого профиля пользователя.',
700
+ parameters: [
701
+ {
702
+ name: '$data[0]',
703
+ type: 'array',
704
+ description: 'Новые данные профиля ($profile)',
705
+ modifiable: true,
706
+ },
707
+ { name: '$data[1]', type: 'array', description: 'Старые данные профиля ($old)' },
708
+ { name: '$data[2]', type: 'array', description: 'Поля профиля ($fields)' },
709
+ ],
710
+ return_type: 'array',
711
+ example: `class onMyaddonUsersBeforeUpdate extends cmsAction {
712
+ public function run($data) {
713
+ [$profile, $old, $fields] = $data;
714
+ // Нормализовать данные
715
+ $profile['phone'] = preg_replace('/[^0-9+]/', '', $profile['phone'] ?? '');
716
+ return [$profile, $old, $fields];
717
+ }
718
+ }`,
719
+ },
720
+ {
721
+ name: 'users_after_update',
722
+ type: 'action',
723
+ category: 'users',
724
+ description: 'Вызывается после сохранения обновлённого профиля пользователя.',
725
+ parameters: [
726
+ { name: '$data[0]', type: 'array', description: 'Новые данные профиля ($profile)' },
727
+ { name: '$data[1]', type: 'array', description: 'Старые данные профиля ($old)' },
728
+ { name: '$data[2]', type: 'array', description: 'Поля профиля ($fields)' },
729
+ ],
730
+ return_type: 'array',
731
+ example: `class onMyaddonUsersAfterUpdate extends cmsAction {
732
+ public function run($data) {
733
+ [$profile, $old, $fields] = $data;
734
+ return $data;
735
+ }
736
+ }`,
737
+ },
738
+ {
739
+ name: 'users_profile_edit_form',
740
+ type: 'filter',
741
+ category: 'users',
742
+ description: 'Модификация формы редактирования профиля. Позволяет добавить поля.',
743
+ parameters: [
744
+ { name: '$data[0]', type: 'cmsForm', description: 'Объект формы ($form)', modifiable: true },
745
+ { name: '$data[1]', type: 'array', description: 'Данные профиля ($profile)' },
746
+ { name: '$data[2]', type: 'array', description: 'Поля профиля ($fields)' },
747
+ ],
748
+ return_type: 'array',
749
+ example: `class onMyaddonUsersProfileEditForm extends cmsAction {
750
+ public function run($data) {
751
+ [$form, $profile, $fields] = $data;
752
+ $form->addField('my_field', new fieldString('my_field', ['title' => 'Моё поле']));
753
+ return [$form, $profile, $fields];
754
+ }
755
+ }`,
756
+ },
757
+ {
758
+ name: 'profile_before_view',
759
+ type: 'filter',
760
+ category: 'users',
761
+ description: 'Вызывается перед отображением страницы профиля пользователя.',
762
+ parameters: [
763
+ {
764
+ name: '$data[0]',
765
+ type: 'array',
766
+ description: 'Данные профиля ($profile)',
767
+ modifiable: true,
768
+ },
769
+ { name: '$data[1]', type: 'array', description: 'Поля профиля ($fields)', modifiable: true },
770
+ ],
771
+ return_type: 'array',
772
+ example: `class onMyaddonProfileBeforeView extends cmsAction {
773
+ public function run($data) {
774
+ [$profile, $fields] = $data;
775
+ $profile['myaddon_data'] = $this->model->getByUser($profile['id']);
776
+ return [$profile, $fields];
777
+ }
778
+ }`,
779
+ },
780
+ {
781
+ name: 'profiles_before_list',
782
+ type: 'filter',
783
+ category: 'users',
784
+ description: 'Вызывается перед выводом списка пользователей.',
785
+ parameters: [
786
+ {
787
+ name: '$data[0]',
788
+ type: 'array',
789
+ description: 'Массив профилей ($profiles)',
790
+ modifiable: true,
791
+ },
792
+ { name: '$data[1]', type: 'array', description: 'Поля ($fields)' },
793
+ ],
794
+ return_type: 'array',
795
+ example: `class onMyaddonProfilesBeforeList extends cmsAction {
796
+ public function run($data) {
797
+ [$profiles, $fields] = $data;
798
+ return [$profiles, $fields];
799
+ }
800
+ }`,
801
+ },
802
+ {
803
+ name: 'user_profile_buttons',
804
+ type: 'filter',
805
+ category: 'users',
806
+ description: 'Добавление кнопок на страницу профиля пользователя.',
807
+ parameters: [
808
+ { name: "$data['profile']", type: 'array', description: 'Данные профиля' },
809
+ { name: "$data['buttons']", type: 'array', description: 'Массив кнопок', modifiable: true },
810
+ ],
811
+ return_type: 'array',
812
+ example: `class onMyaddonUserProfileButtons extends cmsAction {
813
+ public function run($data) {
814
+ $data['buttons'][] = [
815
+ 'title' => 'Мои данные',
816
+ 'url' => href_to('myaddon', 'user', $data['profile']['id']),
817
+ 'icon' => 'star'
818
+ ];
819
+ return $data;
820
+ }
821
+ }`,
822
+ },
823
+ {
824
+ name: 'user_notify_types',
825
+ type: 'filter',
826
+ category: 'users',
827
+ description: 'Регистрация типов уведомлений своего дополнения.',
828
+ parameters: [
829
+ { name: '$types', type: 'array', description: 'Массив типов уведомлений', modifiable: true },
830
+ ],
831
+ return_type: 'array',
832
+ example: `class onMyaddonUserNotifyTypes extends cmsAction {
833
+ public function run($types) {
834
+ $types['myaddon_event'] = [
835
+ 'controller' => 'myaddon',
836
+ 'title' => 'Событие из моего дополнения',
837
+ 'default' => 1
838
+ ];
839
+ return $types;
840
+ }
841
+ }`,
842
+ },
843
+ {
844
+ name: 'users_add_friendship',
845
+ type: 'action',
846
+ category: 'users',
847
+ description: 'Вызывается при отправке заявки в друзья.',
848
+ parameters: [
849
+ { name: "$data['user_id']", type: 'int', description: 'ID инициатора' },
850
+ { name: "$data['friend_id']", type: 'int', description: 'ID получателя заявки' },
851
+ ],
852
+ return_type: 'array',
853
+ example: `class onMyaddonUsersAddFriendship extends cmsAction {
854
+ public function run($data) {
855
+ $user_id = $data['user_id'];
856
+ $friend_id = $data['friend_id'];
857
+
858
+ // Запись в историю дружбы
859
+ $this->model->insert('myaddon_friends_history', [
860
+ 'user_id' => $user_id,
861
+ 'friend_id' => $friend_id,
862
+ 'action' => 'request',
863
+ 'created_at' => date('Y-m-d H:i:s')
864
+ ]);
865
+
866
+ // Отправка уведомления получателю
867
+ $notice = cmsCore::getController('messages');
868
+ $notice->addNotice($friend_id, 'friend_request', [
869
+ 'from_user_id' => $user_id,
870
+ 'message' => 'Хочет добавить вас в друзья'
871
+ ]);
872
+
873
+ return $data;
874
+ }
875
+ }`,
876
+ },
877
+ {
878
+ name: 'users_after_delete_friendship',
879
+ type: 'action',
880
+ category: 'users',
881
+ description: 'Вызывается после удаления дружбы между пользователями.',
882
+ parameters: [
883
+ { name: "$data['user_id']", type: 'int', description: 'ID пользователя' },
884
+ { name: "$data['friend_id']", type: 'int', description: 'ID бывшего друга' },
885
+ ],
886
+ return_type: 'array',
887
+ example: `class onMyaddonUsersAfterDeleteFriendship extends cmsAction {
888
+ public function run($data) {
889
+ $user_id = $data['user_id'];
890
+ $friend_id = $data['friend_id'];
891
+
892
+ // Удаление общих данных
893
+ $this->model->filterEqual('user_id', $user_id)
894
+ ->filterEqual('friend_id', $friend_id)
895
+ ->deleteFiltered('myaddon_friends_data');
896
+
897
+ // Удаление из списка онлайн-друзей
898
+ cmsCache::getInstance()->delete("user_{$user_id}_friends");
899
+ cmsCache::getInstance()->delete("user_{$friend_id}_friends");
900
+
901
+ // Обновление счётчика друзей
902
+ $this->model->updateFriendsCount($user_id);
903
+ $this->model->updateFriendsCount($friend_id);
904
+
905
+ return $data;
906
+ }
907
+ }`,
908
+ },
909
+ {
910
+ name: 'users_karma_vote',
911
+ type: 'action',
912
+ category: 'users',
913
+ description: 'Вызывается при голосовании за карму пользователя.',
914
+ parameters: [
915
+ {
916
+ name: "$data['target_id']",
917
+ type: 'int',
918
+ description: 'ID пользователя, которому голосуют',
919
+ },
920
+ { name: "$data['vote']", type: 'int', description: 'Значение голоса (+1 или -1)' },
921
+ { name: "$data['user_id']", type: 'int', description: 'ID голосующего' },
922
+ ],
923
+ return_type: 'array',
924
+ example: `class onMyaddonUsersKarmaVote extends cmsAction {
925
+ public function run($data) {
926
+ $target_id = $data['target_id'];
927
+ $vote = $data['vote'];
928
+ $user_id = $data['user_id'];
929
+
930
+ // Запись в историю голосований за карму
931
+ $this->model->insert('myaddon_karma_history', [
932
+ 'user_id' => $user_id,
933
+ 'target_id' => $target_id,
934
+ 'vote' => $vote,
935
+ 'created_at' => date('Y-m-d H:i:s')
936
+ ]);
937
+
938
+ // Начисление бонусов за положительную карму
939
+ if ($vote > 0) {
940
+ $this->model->addBonus($target_id, 'positive_karma', $vote);
941
+ }
942
+
943
+ // Отправка уведомления
944
+ $notice = cmsCore::getController('messages');
945
+ $notice->addNotice($target_id, 'karma_vote', [
946
+ 'from_user_id' => $user_id,
947
+ 'vote' => $vote
948
+ ]);
949
+
950
+ return $data;
951
+ }
952
+ }`,
953
+ },
954
+ // ── COMMENTS ────────────────────────────────────────────────────────────────
955
+ {
956
+ name: 'comment_before_add',
957
+ type: 'filter',
958
+ category: 'comments',
959
+ description: 'Вызывается перед добавлением комментария. Позволяет изменить данные.',
960
+ parameters: [
961
+ { name: '$comment', type: 'array', description: 'Данные комментария', modifiable: true },
962
+ ],
963
+ return_type: 'array',
964
+ example: `class onMyaddonCommentBeforeAdd extends cmsAction {
965
+ public function run($comment) {
966
+ // Дополнить данные комментария
967
+ return $comment;
968
+ }
969
+ }`,
970
+ },
971
+ {
972
+ name: 'comment_after_add',
973
+ type: 'action',
974
+ category: 'comments',
975
+ description: 'Вызывается после успешного добавления комментария.',
976
+ parameters: [
977
+ {
978
+ name: '$comment',
979
+ type: 'array',
980
+ description: 'Данные добавленного комментария: id, text, user_id, target_id, target_controller',
981
+ },
982
+ ],
983
+ return_type: 'array',
984
+ example: `class onMyaddonCommentAfterAdd extends cmsAction {
985
+ public function run($comment) {
986
+ // Отправить уведомление автору контента
987
+ $notice = cmsCore::getController('messages');
988
+ $notice->addNotice($comment['target_user_id'], 'new_comment', $comment);
989
+ return $comment;
990
+ }
991
+ }`,
992
+ },
993
+ {
994
+ name: 'comment_before_update',
995
+ type: 'filter',
996
+ category: 'comments',
997
+ description: 'Вызывается перед обновлением комментария.',
998
+ parameters: [
999
+ { name: '$comment', type: 'array', description: 'Данные комментария', modifiable: true },
1000
+ ],
1001
+ return_type: 'array',
1002
+ example: `class onMyaddonCommentBeforeUpdate extends cmsAction {
1003
+ public function run($comment) {
1004
+ return $comment;
1005
+ }
1006
+ }`,
1007
+ },
1008
+ {
1009
+ name: 'comment_after_update',
1010
+ type: 'action',
1011
+ category: 'comments',
1012
+ description: 'Вызывается после обновления комментария.',
1013
+ parameters: [
1014
+ { name: '$comment', type: 'array', description: 'Обновлённые данные комментария' },
1015
+ ],
1016
+ return_type: 'array',
1017
+ example: `class onMyaddonCommentAfterUpdate extends cmsAction {
1018
+ public function run($comment) {
1019
+ return $comment;
1020
+ }
1021
+ }`,
1022
+ },
1023
+ {
1024
+ name: 'comments_before_list',
1025
+ type: 'filter',
1026
+ category: 'comments',
1027
+ description: 'Вызывается перед выводом списка комментариев.',
1028
+ parameters: [
1029
+ { name: '$comments', type: 'array', description: 'Массив комментариев', modifiable: true },
1030
+ ],
1031
+ return_type: 'array',
1032
+ example: `class onMyaddonCommentsBeforeList extends cmsAction {
1033
+ public function run($comments) {
1034
+ return $comments;
1035
+ }
1036
+ }`,
1037
+ },
1038
+ {
1039
+ name: 'comments_after_delete',
1040
+ type: 'action',
1041
+ category: 'comments',
1042
+ description: 'Вызывается после удаления комментария.',
1043
+ parameters: [{ name: '$comment', type: 'array', description: 'Данные удалённого комментария' }],
1044
+ return_type: 'array',
1045
+ example: `class onMyaddonCommentsAfterDelete extends cmsAction {
1046
+ public function run($comment) {
1047
+ return $comment;
1048
+ }
1049
+ }`,
1050
+ },
1051
+ {
1052
+ name: 'comments_targets',
1053
+ type: 'filter',
1054
+ category: 'comments',
1055
+ description: 'Регистрация целей (объектов) для системы комментариев.',
1056
+ parameters: [
1057
+ {
1058
+ name: '$targets',
1059
+ type: 'array',
1060
+ description: 'Массив зарегистрированных целей',
1061
+ modifiable: true,
1062
+ },
1063
+ ],
1064
+ return_type: 'array',
1065
+ example: `class onMyaddonCommentsTargets extends cmsAction {
1066
+ public function run($targets) {
1067
+ $targets['myaddon'] = [
1068
+ 'controller' => 'myaddon',
1069
+ 'title' => 'Элементы моего дополнения'
1070
+ ];
1071
+ return $targets;
1072
+ }
1073
+ }`,
1074
+ },
1075
+ {
1076
+ name: 'comments_rate_after',
1077
+ type: 'action',
1078
+ category: 'comments',
1079
+ description: 'Вызывается после оценки комментария.',
1080
+ parameters: [
1081
+ { name: "$data['comment']", type: 'array', description: 'Данные комментария' },
1082
+ { name: "$data['vote']", type: 'int', description: 'Значение голоса (+1 или -1)' },
1083
+ ],
1084
+ return_type: 'array',
1085
+ example: `class onMyaddonCommentsRateAfter extends cmsAction {
1086
+ public function run($data) {
1087
+ $comment = $data['comment'];
1088
+ $vote = $data['vote'];
1089
+
1090
+ // Начисление рейтинга автору комментария
1091
+ $this->model->updateUserRating($comment['user_id'], $vote);
1092
+
1093
+ // Запись в историю оценок
1094
+ $this->model->insert('myaddon_comment_votes', [
1095
+ 'comment_id' => $comment['id'],
1096
+ 'user_id' => $comment['user_id'],
1097
+ 'vote' => $vote,
1098
+ 'voter_id' => $this->cms_user->id,
1099
+ 'created_at' => date('Y-m-d H:i:s')
1100
+ ]);
1101
+
1102
+ // Уведомление автора комментария
1103
+ if ($comment['user_id'] != $this->cms_user->id) {
1104
+ $notice = cmsCore::getController('messages');
1105
+ $notice->addNotice($comment['user_id'], 'comment_rated', [
1106
+ 'comment_id' => $comment['id'],
1107
+ 'vote' => $vote
1108
+ ]);
1109
+ }
1110
+
1111
+ return $data;
1112
+ }
1113
+ }`,
1114
+ },
1115
+ {
1116
+ name: 'comment_add_permissions',
1117
+ type: 'filter',
1118
+ category: 'comments',
1119
+ description: 'Проверка прав на добавление комментария.',
1120
+ parameters: [
1121
+ {
1122
+ name: '$is_allowed',
1123
+ type: 'bool',
1124
+ description: 'Разрешено ли добавление',
1125
+ modifiable: true,
1126
+ },
1127
+ ],
1128
+ return_type: 'bool',
1129
+ example: `class onMyaddonCommentAddPermissions extends cmsAction {
1130
+ public function run($is_allowed) {
1131
+ return $is_allowed;
1132
+ }
1133
+ }`,
1134
+ },
1135
+ // ── WALL ────────────────────────────────────────────────────────────────────
1136
+ {
1137
+ name: 'wall_before_add',
1138
+ type: 'filter',
1139
+ category: 'users',
1140
+ description: 'Вызывается перед добавлением записи на стену.',
1141
+ parameters: [{ name: '$entry', type: 'array', description: 'Данные записи', modifiable: true }],
1142
+ return_type: 'array',
1143
+ example: `class onMyaddonWallBeforeAdd extends cmsAction {
1144
+ public function run($entry) {
1145
+ return $entry;
1146
+ }
1147
+ }`,
1148
+ },
1149
+ {
1150
+ name: 'wall_before_list',
1151
+ type: 'filter',
1152
+ category: 'users',
1153
+ description: 'Вызывается перед выводом записей стены.',
1154
+ parameters: [
1155
+ { name: '$entries', type: 'array', description: 'Массив записей', modifiable: true },
1156
+ ],
1157
+ return_type: 'array',
1158
+ example: `class onMyaddonWallBeforeList extends cmsAction {
1159
+ public function run($entries) {
1160
+ return $entries;
1161
+ }
1162
+ }`,
1163
+ },
1164
+ {
1165
+ name: 'wall_after_delete',
1166
+ type: 'action',
1167
+ category: 'users',
1168
+ description: 'Вызывается после удаления записи со стены.',
1169
+ parameters: [{ name: '$entry', type: 'array', description: 'Данные удалённой записи' }],
1170
+ return_type: 'array',
1171
+ example: `class onMyaddonWallAfterDelete extends cmsAction {
1172
+ public function run($entry) {
1173
+ return $entry;
1174
+ }
1175
+ }`,
1176
+ },
1177
+ {
1178
+ name: 'wall_entry_actions',
1179
+ type: 'filter',
1180
+ category: 'users',
1181
+ description: 'Добавление действий к записям стены (кнопки в интерфейсе).',
1182
+ parameters: [
1183
+ {
1184
+ name: '$data[0]',
1185
+ type: 'array',
1186
+ description: 'Разрешения ($permissions)',
1187
+ modifiable: true,
1188
+ },
1189
+ {
1190
+ name: '$data[1]',
1191
+ type: 'array',
1192
+ description: 'Массив действий ($actions)',
1193
+ modifiable: true,
1194
+ },
1195
+ ],
1196
+ return_type: 'array',
1197
+ example: `class onMyaddonWallEntryActions extends cmsAction {
1198
+ public function run($data) {
1199
+ [$permissions, $actions] = $data;
1200
+ $actions[] = [
1201
+ 'hint' => 'Пожаловаться',
1202
+ 'icon' => 'flag',
1203
+ 'href' => '#report',
1204
+ 'onclick' => 'return myaddon.report({id})',
1205
+ 'handler' => fn($entry) => $this->cms_user->isLogged()
1206
+ ];
1207
+ return [$permissions, $actions];
1208
+ }
1209
+ }`,
1210
+ },
1211
+ // ── MESSAGES / NOTIFICATIONS ────────────────────────────────────────────────
1212
+ {
1213
+ name: 'messages_after_send',
1214
+ type: 'action',
1215
+ category: 'users',
1216
+ description: 'Вызывается после отправки личного сообщения.',
1217
+ parameters: [{ name: '$message_id', type: 'int', description: 'ID отправленного сообщения' }],
1218
+ return_type: 'int',
1219
+ example: `class onMyaddonMessagesAfterSend extends cmsAction {
1220
+ public function run($message_id) {
1221
+ return $message_id;
1222
+ }
1223
+ }`,
1224
+ },
1225
+ {
1226
+ name: 'send_user_notice',
1227
+ type: 'action',
1228
+ category: 'users',
1229
+ description: 'Вызывается при отправке системного уведомления пользователю.',
1230
+ parameters: [
1231
+ { name: '$data[0]', type: 'array', description: 'Получатели ($recipients)' },
1232
+ { name: '$data[1]', type: 'array', description: 'Данные уведомления ($notice)' },
1233
+ ],
1234
+ return_type: 'array',
1235
+ example: `class onMyaddonSendUserNotice extends cmsAction {
1236
+ public function run($data) {
1237
+ [$recipients, $notice] = $data;
1238
+ return $data;
1239
+ }
1240
+ }`,
1241
+ },
1242
+ // ── GROUPS ──────────────────────────────────────────────────────────────────
1243
+ {
1244
+ name: 'group_before_view',
1245
+ type: 'filter',
1246
+ category: 'groups',
1247
+ description: 'Вызывается перед отображением страницы группы.',
1248
+ parameters: [
1249
+ { name: '$data[0]', type: 'array', description: 'Данные группы ($group)', modifiable: true },
1250
+ { name: '$data[1]', type: 'array', description: 'Поля группы ($fields)', modifiable: true },
1251
+ ],
1252
+ return_type: 'array',
1253
+ example: `class onMyaddonGroupBeforeView extends cmsAction {
1254
+ public function run($data) {
1255
+ [$group, $fields] = $data;
1256
+ $group['myaddon'] = $this->model->getGroupData($group['id']);
1257
+ return [$group, $fields];
1258
+ }
1259
+ }`,
1260
+ },
1261
+ {
1262
+ name: 'group_tabs',
1263
+ type: 'filter',
1264
+ category: 'groups',
1265
+ description: 'Добавление вкладок на страницу группы.',
1266
+ parameters: [
1267
+ { name: '$data[0]', type: 'array', description: 'Массив вкладок ($menu)', modifiable: true },
1268
+ { name: '$data[1]', type: 'array', description: 'Данные группы ($group)' },
1269
+ ],
1270
+ return_type: 'array',
1271
+ example: `class onMyaddonGroupTabs extends cmsAction {
1272
+ public function run($data) {
1273
+ [$menu, $group] = $data;
1274
+ $menu['myaddon'] = [
1275
+ 'title' => 'Моя вкладка',
1276
+ 'url' => href_to('myaddon', 'group', $group['id']),
1277
+ 'count' => $this->model->countByGroup($group['id'])
1278
+ ];
1279
+ return [$menu, $group];
1280
+ }
1281
+ }`,
1282
+ },
1283
+ {
1284
+ name: 'group_after_join',
1285
+ type: 'action',
1286
+ category: 'groups',
1287
+ description: 'Вызывается после вступления пользователя в группу.',
1288
+ parameters: [
1289
+ { name: '$data[0]', type: 'array', description: 'Данные группы ($group)' },
1290
+ { name: '$data[1]', type: 'mixed', description: 'Данные приглашения ($invite)' },
1291
+ ],
1292
+ return_type: 'array',
1293
+ example: `class onMyaddonGroupAfterJoin extends cmsAction {
1294
+ public function run($data) {
1295
+ [$group, $invite] = $data;
1296
+ return $data;
1297
+ }
1298
+ }`,
1299
+ },
1300
+ {
1301
+ name: 'group_after_leave',
1302
+ type: 'action',
1303
+ category: 'groups',
1304
+ description: 'Вызывается после выхода пользователя из группы.',
1305
+ parameters: [{ name: '$group', type: 'array', description: 'Данные группы' }],
1306
+ return_type: 'array',
1307
+ example: `class onMyaddonGroupAfterLeave extends cmsAction {
1308
+ public function run($group) {
1309
+ return $group;
1310
+ }
1311
+ }`,
1312
+ },
1313
+ {
1314
+ name: 'group_before_delete',
1315
+ type: 'action',
1316
+ category: 'groups',
1317
+ description: 'Вызывается перед удалением группы. Используется для очистки связанных данных.',
1318
+ parameters: [{ name: '$group', type: 'array', description: 'Данные удаляемой группы' }],
1319
+ return_type: 'array',
1320
+ example: `class onMyaddonGroupBeforeDelete extends cmsAction {
1321
+ public function run($group) {
1322
+ $this->model->filterEqual('group_id', $group['id'])->deleteFiltered('myaddon_group_data');
1323
+ return $group;
1324
+ }
1325
+ }`,
1326
+ },
1327
+ {
1328
+ name: 'group_item_form',
1329
+ type: 'filter',
1330
+ category: 'groups',
1331
+ description: 'Модификация формы создания/редактирования группы.',
1332
+ parameters: [
1333
+ { name: '$data[0]', type: 'cmsForm', description: 'Объект формы', modifiable: true },
1334
+ { name: '$data[1]', type: 'array', description: 'Данные группы' },
1335
+ ],
1336
+ return_type: 'array',
1337
+ example: `class onMyaddonGroupItemForm extends cmsAction {
1338
+ public function run($data) {
1339
+ [$form, $group] = $data;
1340
+ $form->addField('myaddon_field', new fieldString('myaddon_field', ['title' => 'Поле']));
1341
+ return [$form, $group];
1342
+ }
1343
+ }`,
1344
+ },
1345
+ {
1346
+ name: 'group_view_buttons',
1347
+ type: 'filter',
1348
+ category: 'groups',
1349
+ description: 'Добавление кнопок на страницу просмотра группы.',
1350
+ parameters: [
1351
+ { name: '$buttons', type: 'array', description: 'Массив кнопок', modifiable: true },
1352
+ ],
1353
+ return_type: 'array',
1354
+ example: `class onMyaddonGroupViewButtons extends cmsAction {
1355
+ public function run($buttons) {
1356
+ $buttons[] = ['title' => 'Действие', 'url' => '#', 'icon' => 'star'];
1357
+ return $buttons;
1358
+ }
1359
+ }`,
1360
+ },
1361
+ // ── ACTIVITY ────────────────────────────────────────────────────────────────
1362
+ {
1363
+ name: 'activity_before_add',
1364
+ type: 'filter',
1365
+ category: 'activity',
1366
+ description: 'Вызывается перед добавлением записи в ленту активности.',
1367
+ parameters: [
1368
+ { name: '$entry', type: 'array', description: 'Данные записи активности', modifiable: true },
1369
+ ],
1370
+ return_type: 'array',
1371
+ example: `class onMyaddonActivityBeforeAdd extends cmsAction {
1372
+ public function run($entry) {
1373
+ return $entry;
1374
+ }
1375
+ }`,
1376
+ },
1377
+ {
1378
+ name: 'activity_after_add',
1379
+ type: 'action',
1380
+ category: 'activity',
1381
+ description: 'Вызывается после добавления записи в ленту активности.',
1382
+ parameters: [{ name: '$entry', type: 'array', description: 'Добавленная запись активности' }],
1383
+ return_type: 'void',
1384
+ example: `class onMyaddonActivityAfterAdd extends cmsAction {
1385
+ public function run($entry) {
1386
+ return $entry;
1387
+ }
1388
+ }`,
1389
+ },
1390
+ {
1391
+ name: 'activity_before_list',
1392
+ type: 'filter',
1393
+ category: 'activity',
1394
+ description: 'Вызывается перед выводом ленты активности.',
1395
+ parameters: [
1396
+ { name: '$items', type: 'array', description: 'Массив записей активности', modifiable: true },
1397
+ ],
1398
+ return_type: 'array',
1399
+ example: `class onMyaddonActivityBeforeList extends cmsAction {
1400
+ public function run($items) {
1401
+ return $items;
1402
+ }
1403
+ }`,
1404
+ },
1405
+ {
1406
+ name: 'activity_before_delete_entry',
1407
+ type: 'action',
1408
+ category: 'activity',
1409
+ description: 'Вызывается перед удалением записи из ленты активности.',
1410
+ parameters: [{ name: '$entry', type: 'array', description: 'Данные удаляемой записи' }],
1411
+ return_type: 'array',
1412
+ example: `class onMyaddonActivityBeforeDeleteEntry extends cmsAction {
1413
+ public function run($entry) {
1414
+ return $entry;
1415
+ }
1416
+ }`,
1417
+ },
1418
+ // ── PHOTOS ──────────────────────────────────────────────────────────────────
1419
+ {
1420
+ name: 'photos_before_item',
1421
+ type: 'filter',
1422
+ category: 'content',
1423
+ description: 'Вызывается перед отображением фотографии.',
1424
+ parameters: [
1425
+ { name: '$data[0]', type: 'array', description: 'Данные фото ($photo)', modifiable: true },
1426
+ { name: '$data[1]', type: 'array', description: 'Данные альбома ($album)' },
1427
+ { name: '$data[2]', type: 'array', description: 'Тип контента ($ctype)' },
1428
+ ],
1429
+ return_type: 'array',
1430
+ example: `class onMyaddonPhotosBeforeItem extends cmsAction {
1431
+ public function run($data) {
1432
+ [$photo, $album, $ctype] = $data;
1433
+ return $data;
1434
+ }
1435
+ }`,
1436
+ },
1437
+ {
1438
+ name: 'photos_before_list',
1439
+ type: 'filter',
1440
+ category: 'content',
1441
+ description: 'Вызывается перед выводом списка фотографий.',
1442
+ parameters: [
1443
+ { name: '$photos', type: 'array', description: 'Массив фотографий', modifiable: true },
1444
+ ],
1445
+ return_type: 'array',
1446
+ example: `class onMyaddonPhotosBeforeList extends cmsAction {
1447
+ public function run($photos) {
1448
+ return $photos;
1449
+ }
1450
+ }`,
1451
+ },
1452
+ {
1453
+ name: 'photos_after_delete',
1454
+ type: 'action',
1455
+ category: 'content',
1456
+ description: 'Вызывается после удаления фотографии.',
1457
+ parameters: [{ name: '$photo', type: 'array', description: 'Данные удалённой фотографии' }],
1458
+ return_type: 'array',
1459
+ example: `class onMyaddonPhotosAfterDelete extends cmsAction {
1460
+ public function run($photo) {
1461
+ return $photo;
1462
+ }
1463
+ }`,
1464
+ },
1465
+ // ── IMAGES ──────────────────────────────────────────────────────────────────
1466
+ {
1467
+ name: 'images_after_upload',
1468
+ type: 'action',
1469
+ category: 'content',
1470
+ description: 'Вызывается после загрузки изображения.',
1471
+ parameters: [
1472
+ {
1473
+ name: '$image',
1474
+ type: 'array',
1475
+ description: 'Данные загруженного изображения: path, width, height, size',
1476
+ },
1477
+ ],
1478
+ return_type: 'array',
1479
+ example: `class onMyaddonImagesAfterUpload extends cmsAction {
1480
+ public function run($image) {
1481
+ return $image;
1482
+ }
1483
+ }`,
1484
+ },
1485
+ {
1486
+ name: 'images_before_upload',
1487
+ type: 'filter',
1488
+ category: 'content',
1489
+ description: 'Вызывается перед загрузкой изображения. Можно запретить загрузку, вернув false.',
1490
+ parameters: [
1491
+ {
1492
+ name: '$image',
1493
+ type: 'array',
1494
+ description: 'Данные загружаемого изображения',
1495
+ modifiable: true,
1496
+ },
1497
+ ],
1498
+ return_type: 'array|false',
1499
+ example: `class onMyaddonImagesBeforeUpload extends cmsAction {
1500
+ public function run($image) {
1501
+ return $image;
1502
+ }
1503
+ }`,
1504
+ },
1505
+ // ── FORMS ────────────────────────────────────────────────────────────────────
1506
+ {
1507
+ name: 'form_make',
1508
+ type: 'filter',
1509
+ category: 'forms',
1510
+ description: 'Вызывается при создании любой формы. Позволяет модифицировать поля.',
1511
+ parameters: [{ name: '$form', type: 'cmsForm', description: 'Объект формы', modifiable: true }],
1512
+ return_type: 'cmsForm',
1513
+ example: `class onMyaddonFormMake extends cmsAction {
1514
+ public function run($form) {
1515
+ if ($this->params['form_name'] === 'users_options') {
1516
+ $form->addField('myaddon_field', new fieldString('myaddon_field', [
1517
+ 'title' => 'Поле от моего дополнения'
1518
+ ]));
1519
+ }
1520
+ return $form;
1521
+ }
1522
+ }`,
1523
+ },
1524
+ {
1525
+ name: 'forms_before_validate',
1526
+ type: 'filter',
1527
+ category: 'forms',
1528
+ description: 'Вызывается перед валидацией произвольной формы (контроллер forms).',
1529
+ parameters: [
1530
+ {
1531
+ name: '$data[0]',
1532
+ type: 'array',
1533
+ description: 'Данные формы ($form_data)',
1534
+ modifiable: true,
1535
+ },
1536
+ { name: '$data[1]', type: 'array', description: 'Конфигурация формы ($form_config)' },
1537
+ ],
1538
+ return_type: 'array',
1539
+ example: `class onMyaddonFormsBeforeValidate extends cmsAction {
1540
+ public function run($data) {
1541
+ [$form_data, $form_config] = $data;
1542
+
1543
+ // Кастомная валидация телефона
1544
+ if (!empty($form_data['phone'])) {
1545
+ $phone = preg_replace('/[^0-9+]/', '', $form_data['phone']);
1546
+ if (strlen($phone) < 11) {
1547
+ $errors[] = 'Некорректный номер телефона';
1548
+ }
1549
+ }
1550
+
1551
+ // Проверка уникальности поля slug
1552
+ if (!empty($form_data['slug'])) {
1553
+ if ($this->model->slugExists($form_data['slug'])) {
1554
+ $errors[] = 'URL-псевдоним уже существует';
1555
+ }
1556
+ }
1557
+
1558
+ return $data;
1559
+ }
1560
+ }`,
1561
+ },
1562
+ {
1563
+ name: 'forms_after_validate',
1564
+ type: 'action',
1565
+ category: 'forms',
1566
+ description: 'Вызывается после успешной валидации произвольной формы.',
1567
+ parameters: [
1568
+ { name: "$data['form_data']", type: 'array', description: 'Данные формы' },
1569
+ { name: "$data['form_config']", type: 'array', description: 'Конфигурация формы' },
1570
+ ],
1571
+ return_type: 'array',
1572
+ example: `class onMyaddonFormsAfterValidate extends cmsAction {
1573
+ public function run($data) {
1574
+ $form_data = $data['form_data'];
1575
+ $form_config = $data['form_config'];
1576
+
1577
+ // Обработка данных после успешной валидации
1578
+ if ($form_config['name'] === 'contact_form') {
1579
+ // Отправка email администратору
1580
+ cmsCore::sendEmail([
1581
+ 'to' => cmsConfig::get('admin_email'),
1582
+ 'subject' => 'Новая заявка с сайта',
1583
+ 'body' => "Имя: {$form_data['name']}\nEmail: {$form_data['email']}\n{$form_data['message']}"
1584
+ ]);
1585
+
1586
+ // Сохранение в базу
1587
+ $this->model->insert('myaddon_contacts', $form_data);
1588
+ }
1589
+
1590
+ return $data;
1591
+ }
1592
+ }`,
1593
+ },
1594
+ // ── ADMIN ────────────────────────────────────────────────────────────────────
1595
+ {
1596
+ name: 'adminpanel_menu',
1597
+ type: 'filter',
1598
+ category: 'admin',
1599
+ description: 'Добавление пункта в главное меню административной панели.',
1600
+ parameters: [
1601
+ { name: '$menu', type: 'array', description: 'Массив пунктов меню', modifiable: true },
1602
+ ],
1603
+ return_type: 'array',
1604
+ example: `class onMyaddonAdminpanelMenu extends cmsAction {
1605
+ public function run($menu) {
1606
+ $menu['myaddon'] = [
1607
+ 'title' => 'Моё дополнение',
1608
+ 'url' => href_to_admin('myaddon'),
1609
+ 'icon' => 'puzzle-piece',
1610
+ 'position' => 50
1611
+ ];
1612
+ return $menu;
1613
+ }
1614
+ }`,
1615
+ },
1616
+ {
1617
+ name: 'admin_dashboard_block',
1618
+ type: 'filter',
1619
+ category: 'admin',
1620
+ description: 'Добавление блока на дашборд администратора.',
1621
+ parameters: [
1622
+ { name: '$blocks', type: 'array', description: 'Массив блоков дашборда', modifiable: true },
1623
+ ],
1624
+ return_type: 'array',
1625
+ example: `class onMyaddonAdminDashboardBlock extends cmsAction {
1626
+ public function run($blocks) {
1627
+ $count = $this->model->getCount('myaddon_items');
1628
+ $blocks[] = [
1629
+ 'title' => 'Мой счётчик',
1630
+ 'value' => $count,
1631
+ 'icon' => 'star',
1632
+ 'url' => href_to_admin('myaddon')
1633
+ ];
1634
+ return $blocks;
1635
+ }
1636
+ }`,
1637
+ },
1638
+ {
1639
+ name: 'admin_inline_save',
1640
+ type: 'filter',
1641
+ category: 'admin',
1642
+ description: 'Вызывается при inline-редактировании записей в гриде административной панели.',
1643
+ parameters: [
1644
+ { name: '$data[0]', type: 'array', description: 'Изменённые данные', modifiable: true },
1645
+ { name: '$data[1]', type: 'array', description: 'Оригинальные данные' },
1646
+ { name: '$data[2]', type: 'int', description: 'Индекс строки' },
1647
+ ],
1648
+ return_type: 'array',
1649
+ example: `class onMyaddonAdminInlineSave extends cmsAction {
1650
+ public function run($data) {
1651
+ [$changed, $original, $row_index] = $data;
1652
+
1653
+ // Логирование изменений
1654
+ $this->model->logAdminEdit([
1655
+ 'table' => $original['table_name'] ?? 'unknown',
1656
+ 'row_id' => $original['id'],
1657
+ 'changed' => $changed,
1658
+ 'admin_id' => $this->cms_user->id,
1659
+ 'row_index' => $row_index,
1660
+ 'created_at' => date('Y-m-d H:i:s')
1661
+ ]);
1662
+
1663
+ // Специальная обработка для поля ordering
1664
+ if (isset($changed['ordering'])) {
1665
+ $this->model->reorderItems($original['id'], $changed['ordering']);
1666
+ }
1667
+
1668
+ return $data;
1669
+ }
1670
+ }`,
1671
+ },
1672
+ {
1673
+ name: 'admin_users_filter',
1674
+ type: 'filter',
1675
+ category: 'admin',
1676
+ description: 'Добавление фильтров к списку пользователей в админ-панели.',
1677
+ parameters: [
1678
+ {
1679
+ name: '$model',
1680
+ type: 'cmsModel',
1681
+ description: 'Модель для добавления условий фильтрации',
1682
+ modifiable: true,
1683
+ },
1684
+ ],
1685
+ return_type: 'cmsModel',
1686
+ example: `class onMyaddonAdminUsersFilter extends cmsAction {
1687
+ public function run($model) {
1688
+ return $model;
1689
+ }
1690
+ }`,
1691
+ },
1692
+ {
1693
+ name: 'admin_subscriptions_list',
1694
+ type: 'filter',
1695
+ category: 'admin',
1696
+ description: 'Добавление типов подписок в список в административной панели.',
1697
+ parameters: [
1698
+ { name: '$list', type: 'array', description: 'Список типов подписок', modifiable: true },
1699
+ ],
1700
+ return_type: 'array',
1701
+ example: `class onMyaddonAdminSubscriptionsList extends cmsAction {
1702
+ public function run($list) {
1703
+ $list['myaddon'] = [
1704
+ 'title' => 'Подписки на мой контент',
1705
+ 'controller' => 'myaddon'
1706
+ ];
1707
+ return $list;
1708
+ }
1709
+ }`,
1710
+ },
1711
+ // ── TEMPLATE / RENDER ────────────────────────────────────────────────────────
1712
+ {
1713
+ name: 'render_page',
1714
+ type: 'filter',
1715
+ category: 'template',
1716
+ description: 'Вызывается при рендере полной страницы. Позволяет изменить HTML вывод.',
1717
+ parameters: [{ name: '$html', type: 'string', description: 'HTML страницы', modifiable: true }],
1718
+ return_type: 'string',
1719
+ example: `class onMyaddonRenderPage extends cmsAction {
1720
+ public function run($html) {
1721
+ $html = str_replace('</body>', '<script src="/static/myaddon/app.js"></script></body>', $html);
1722
+ return $html;
1723
+ }
1724
+ }`,
1725
+ },
1726
+ {
1727
+ name: 'before_render_page',
1728
+ type: 'action',
1729
+ category: 'template',
1730
+ description: 'Вызывается перед рендером страницы. Используется для добавления CSS/JS.',
1731
+ parameters: [{ name: '$data', type: 'array', description: 'Данные страницы' }],
1732
+ return_type: 'array',
1733
+ example: `class onMyaddonBeforeRenderPage extends cmsAction {
1734
+ public function run($data) {
1735
+ $this->cms_template->addHeadCSS('/static/myaddon/style.css');
1736
+ $this->cms_template->addBottomJS('/static/myaddon/app.js');
1737
+ return $data;
1738
+ }
1739
+ }`,
1740
+ },
1741
+ {
1742
+ name: 'menu_before_list',
1743
+ type: 'filter',
1744
+ category: 'template',
1745
+ description: 'Вызывается перед выводом меню сайта.',
1746
+ parameters: [
1747
+ { name: '$menus', type: 'array', description: 'Массив пунктов меню', modifiable: true },
1748
+ ],
1749
+ return_type: 'array',
1750
+ example: `class onMyaddonMenuBeforeList extends cmsAction {
1751
+ public function run($menus) {
1752
+ return $menus;
1753
+ }
1754
+ }`,
1755
+ },
1756
+ {
1757
+ name: 'frontpage_action_index',
1758
+ type: 'action',
1759
+ category: 'template',
1760
+ description: 'Вызывается при загрузке главной страницы сайта.',
1761
+ parameters: [{ name: '$data', type: 'false', description: 'Не используется (false)' }],
1762
+ return_type: 'void',
1763
+ example: `class onMyaddonFrontpageActionIndex extends cmsAction {
1764
+ public function run($data) {
1765
+ // Добавление CSS/JS для главной страницы
1766
+ $this->cms_template->addHeadCSS('/static/myaddon/frontpage.css');
1767
+ $this->cms_template->addBottomJS('/static/myaddon/frontpage.js');
1768
+
1769
+ // Регистрация виджетов
1770
+ cmsCore::getController('widget')->registerPosition('frontpage_banner');
1771
+
1772
+ // Получение данных для баннера
1773
+ $banners = $this->model->getActiveBanners();
1774
+ $this->cms_template->setContext('banners', $banners);
1775
+
1776
+ return $data;
1777
+ }
1778
+ }`,
1779
+ },
1780
+ {
1781
+ name: 'widget_options_full_form',
1782
+ type: 'filter',
1783
+ category: 'template',
1784
+ description: 'Модификация полной формы настроек виджета.',
1785
+ parameters: [
1786
+ {
1787
+ name: '$form',
1788
+ type: 'cmsForm',
1789
+ description: 'Объект формы настроек виджета',
1790
+ modifiable: true,
1791
+ },
1792
+ ],
1793
+ return_type: 'cmsForm',
1794
+ example: `class onMyaddonWidgetOptionsFullForm extends cmsAction {
1795
+ public function run($form) {
1796
+ return $form;
1797
+ }
1798
+ }`,
1799
+ },
1800
+ {
1801
+ name: 'widgets_before_list',
1802
+ type: 'filter',
1803
+ category: 'template',
1804
+ description: 'Вызывается перед выводом списка виджетов на странице.',
1805
+ parameters: [
1806
+ { name: '$widgets', type: 'array', description: 'Массив виджетов', modifiable: true },
1807
+ ],
1808
+ return_type: 'array',
1809
+ example: `class onMyaddonWidgetsBeforeList extends cmsAction {
1810
+ public function run($widgets) {
1811
+ return $widgets;
1812
+ }
1813
+ }`,
1814
+ },
1815
+ // ── SEARCH ──────────────────────────────────────────────────────────────────
1816
+ {
1817
+ name: 'fulltext_search',
1818
+ type: 'filter',
1819
+ category: 'search',
1820
+ description: 'Добавление результатов из своего дополнения в полнотекстовый поиск.',
1821
+ parameters: [
1822
+ {
1823
+ name: '$results',
1824
+ type: 'array',
1825
+ description: 'Текущие результаты поиска',
1826
+ modifiable: true,
1827
+ },
1828
+ ],
1829
+ return_type: 'array',
1830
+ example: `class onMyaddonFulltextSearch extends cmsAction {
1831
+ public function run($results) {
1832
+ $query = $this->params['query'];
1833
+ $items = $this->model->filterLike('title', $query)->get('myaddon_items');
1834
+ foreach ($items as $item) {
1835
+ $results[] = [
1836
+ 'title' => $item['title'],
1837
+ 'url' => href_to('myaddon', 'view', $item['id']),
1838
+ 'snippet' => mb_substr(strip_tags($item['text']), 0, 200),
1839
+ 'controller' => 'myaddon'
1840
+ ];
1841
+ }
1842
+ return $results;
1843
+ }
1844
+ }`,
1845
+ },
1846
+ // ── SITEMAP ──────────────────────────────────────────────────────────────────
1847
+ {
1848
+ name: 'sitemap_urls',
1849
+ type: 'filter',
1850
+ category: 'sitemap',
1851
+ description: 'Добавление URL в sitemap.xml.',
1852
+ parameters: [
1853
+ { name: '$urls', type: 'array', description: 'Массив URL для sitemap', modifiable: true },
1854
+ ],
1855
+ return_type: 'array',
1856
+ example: `class onMyaddonSitemapUrls extends cmsAction {
1857
+ public function run($urls) {
1858
+ $items = $this->model->filterEqual('is_pub', 1)->get('myaddon_items');
1859
+ foreach ($items as $item) {
1860
+ $urls[] = [
1861
+ 'url' => href_to('myaddon', 'view', $item['id']),
1862
+ 'changefreq' => 'weekly',
1863
+ 'priority' => 0.6,
1864
+ 'lastmod' => $item['date_pub']
1865
+ ];
1866
+ }
1867
+ return $urls;
1868
+ }
1869
+ }`,
1870
+ },
1871
+ {
1872
+ name: 'sitemap_urls_list_{controller}',
1873
+ type: 'filter',
1874
+ category: 'sitemap',
1875
+ description: 'Динамический хук для добавления URL конкретного контроллера в sitemap. {controller} = имя контроллера (например: sitemap_urls_list_content).',
1876
+ parameters: [
1877
+ {
1878
+ name: '$data[0]',
1879
+ type: 'array',
1880
+ description: 'Элемент для генерации URL ($item)',
1881
+ modifiable: true,
1882
+ },
1883
+ { name: '$data[1]', type: 'array', description: 'Массив URL ($urls)', modifiable: true },
1884
+ ],
1885
+ return_type: 'array',
1886
+ example: `// Файл: hooks/sitemap_urls_list_myaddon.php
1887
+ class onMyaddonSitemapUrlsListMyaddon extends cmsAction {
1888
+ public function run($data) {
1889
+ [$item, $urls] = $data;
1890
+ $urls[] = ['url' => href_to('myaddon', 'view', $item['id']), 'priority' => 0.7];
1891
+ return [$item, $urls];
1892
+ }
1893
+ }`,
1894
+ },
1895
+ // ── RSS ──────────────────────────────────────────────────────────────────────
1896
+ {
1897
+ name: 'rss_feed_list',
1898
+ type: 'filter',
1899
+ category: 'rss',
1900
+ description: 'Добавление своей RSS ленты в список лент сайта.',
1901
+ parameters: [
1902
+ { name: '$feeds', type: 'array', description: 'Массив RSS лент', modifiable: true },
1903
+ ],
1904
+ return_type: 'array',
1905
+ example: `class onMyaddonRssFeedList extends cmsAction {
1906
+ public function run($feeds) {
1907
+ $feeds['myaddon'] = [
1908
+ 'title' => 'Лента моего дополнения',
1909
+ 'url' => href_to('myaddon', 'rss')
1910
+ ];
1911
+ return $feeds;
1912
+ }
1913
+ }`,
1914
+ },
1915
+ // ── CRON ────────────────────────────────────────────────────────────────────
1916
+ {
1917
+ name: 'cron_clean',
1918
+ type: 'action',
1919
+ category: 'cron',
1920
+ description: 'Вызывается при выполнении задачи очистки по расписанию (обычно ночью).',
1921
+ parameters: [{ name: '$data', type: 'array', description: 'Пустой массив' }],
1922
+ return_type: 'void',
1923
+ example: `class onMyaddonCronClean extends cmsAction {
1924
+ public function run($data) {
1925
+ $this->model->filterLt('date_expire', date('Y-m-d H:i:s'))->deleteFiltered('myaddon_temp');
1926
+ return $data;
1927
+ }
1928
+ }`,
1929
+ },
1930
+ {
1931
+ name: 'cron_publication',
1932
+ type: 'action',
1933
+ category: 'cron',
1934
+ description: 'Вызывается при обработке отложенных публикаций (каждый час).',
1935
+ parameters: [{ name: '$data', type: 'array', description: 'Пустой массив' }],
1936
+ return_type: 'void',
1937
+ example: `class onMyaddonCronPublication extends cmsAction {
1938
+ public function run($data) {
1939
+ // Опубликовать запланированные материалы
1940
+ $items = $this->model
1941
+ ->filterEqual('is_pub', 0)
1942
+ ->filterLtEqual('date_pub', date('Y-m-d H:i:s'))
1943
+ ->get('myaddon_items');
1944
+ foreach ($items as $item) {
1945
+ $this->model->update('myaddon_items', $item['id'], ['is_pub' => 1]);
1946
+ }
1947
+ return $data;
1948
+ }
1949
+ }`,
1950
+ },
1951
+ {
1952
+ name: 'publish_delayed_content',
1953
+ type: 'action',
1954
+ category: 'cron',
1955
+ description: 'Вызывается при публикации отложенного контента.',
1956
+ parameters: [{ name: '$data', type: 'array', description: 'Данные публикации' }],
1957
+ return_type: 'void',
1958
+ example: `class onMyaddonPublishDelayedContent extends cmsAction {
1959
+ public function run($data) {
1960
+ // Обработка отложенного контента
1961
+ $items = $this->model
1962
+ ->filterEqual('is_pub', 0)
1963
+ ->filterNotNull('date_pub')
1964
+ ->filterLtEqual('date_pub', date('Y-m-d H:i:s'))
1965
+ ->get('myaddon_items');
1966
+
1967
+ foreach ($items as $item) {
1968
+ $this->model->update('myaddon_items', $item['id'], [
1969
+ 'is_pub' => 1,
1970
+ 'published_at' => date('Y-m-d H:i:s')
1971
+ ]);
1972
+
1973
+ // Добавление в поисковый индекс
1974
+ cmsCore::getModel('search')->indexItem('myaddon', $item['id']);
1975
+
1976
+ // Отправка уведомлений подписчикам
1977
+ $this->model->notifySubscribers($item['id']);
1978
+ }
1979
+
1980
+ return $data;
1981
+ }
1982
+ }`,
1983
+ },
1984
+ // ── SUBSCRIPTIONS ─────────────────────────────────────────────────────────────
1985
+ {
1986
+ name: 'subscribe',
1987
+ type: 'action',
1988
+ category: 'subscriptions',
1989
+ description: 'Вызывается при оформлении подписки на объект.',
1990
+ parameters: [
1991
+ { name: "$data['user_id']", type: 'int', description: 'ID подписчика' },
1992
+ { name: "$data['target_id']", type: 'int', description: 'ID объекта подписки' },
1993
+ { name: "$data['target_type']", type: 'string', description: 'Тип объекта' },
1994
+ ],
1995
+ return_type: 'array',
1996
+ example: `class onMyaddonSubscribe extends cmsAction {
1997
+ public function run($data) {
1998
+ $user_id = $data['user_id'];
1999
+ $target_id = $data['target_id'];
2000
+ $target_type = $data['target_type'];
2001
+
2002
+ // Сохранение подписки в своей таблице
2003
+ $this->model->insert('myaddon_subscriptions', [
2004
+ 'user_id' => $user_id,
2005
+ 'target_id' => $target_id,
2006
+ 'target_type' => $target_type,
2007
+ 'created_at' => date('Y-m-d H:i:s')
2008
+ ]);
2009
+
2010
+ // Обновление счётчика подписчиков
2011
+ $this->model->incrementSubscribersCount($target_type, $target_id);
2012
+
2013
+ // Отправка подтверждения
2014
+ $notice = cmsCore::getController('messages');
2015
+ $notice->addNotice($user_id, 'subscription_confirmed', [
2016
+ 'target_type' => $target_type,
2017
+ 'target_id' => $target_id
2018
+ ]);
2019
+
2020
+ return $data;
2021
+ }
2022
+ }`,
2023
+ },
2024
+ {
2025
+ name: 'unsubscribe',
2026
+ type: 'action',
2027
+ category: 'subscriptions',
2028
+ description: 'Вызывается при отмене подписки.',
2029
+ parameters: [
2030
+ { name: "$data['user_id']", type: 'int', description: 'ID подписчика' },
2031
+ { name: "$data['target_id']", type: 'int', description: 'ID объекта' },
2032
+ { name: "$data['target_type']", type: 'string', description: 'Тип объекта' },
2033
+ ],
2034
+ return_type: 'array',
2035
+ example: `class onMyaddonUnsubscribe extends cmsAction {
2036
+ public function run($data) {
2037
+ $user_id = $data['user_id'];
2038
+ $target_id = $data['target_id'];
2039
+ $target_type = $data['target_type'];
2040
+
2041
+ // Удаление подписки
2042
+ $this->model->filterEqual('user_id', $user_id)
2043
+ ->filterEqual('target_id', $target_id)
2044
+ ->filterEqual('target_type', $target_type)
2045
+ ->deleteFiltered('myaddon_subscriptions');
2046
+
2047
+ // Обновление счётчика подписчиков
2048
+ $this->model->decrementSubscribersCount($target_type, $target_id);
2049
+
2050
+ // Удаление из кэша
2051
+ cmsCache::getInstance()->delete("subscribers_{$target_type}_{$target_id}");
2052
+
2053
+ return $data;
2054
+ }
2055
+ }`,
2056
+ },
2057
+ {
2058
+ name: 'notify_subscribers',
2059
+ type: 'filter',
2060
+ category: 'subscriptions',
2061
+ description: 'Вызывается при рассылке уведомлений подписчикам.',
2062
+ parameters: [
2063
+ {
2064
+ name: '$data[0]',
2065
+ type: 'array',
2066
+ description: 'Данные подписки ($subscription)',
2067
+ modifiable: true,
2068
+ },
2069
+ {
2070
+ name: '$data[1]',
2071
+ type: 'array',
2072
+ description: 'Список подписчиков ($subscribers)',
2073
+ modifiable: true,
2074
+ },
2075
+ {
2076
+ name: '$data[2]',
2077
+ type: 'array',
2078
+ description: 'Список совпадений ($match_list)',
2079
+ modifiable: true,
2080
+ },
2081
+ ],
2082
+ return_type: 'array',
2083
+ example: `class onMyaddonNotifySubscribers extends cmsAction {
2084
+ public function run($data) {
2085
+ [$subscription, $subscribers, $match_list] = $data;
2086
+ return $data;
2087
+ }
2088
+ }`,
2089
+ },
2090
+ // ── RATING ──────────────────────────────────────────────────────────────────
2091
+ {
2092
+ name: 'rating_vote',
2093
+ type: 'action',
2094
+ category: 'rating',
2095
+ description: 'Вызывается при голосовании за любой объект системы.',
2096
+ parameters: [
2097
+ {
2098
+ name: "$data['target']",
2099
+ type: 'string',
2100
+ description: 'Тип объекта (content, comment и т.д.)',
2101
+ },
2102
+ { name: "$data['target_id']", type: 'int', description: 'ID объекта' },
2103
+ { name: "$data['vote']", type: 'int', description: 'Значение голоса' },
2104
+ { name: "$data['user_id']", type: 'int', description: 'ID голосующего' },
2105
+ ],
2106
+ return_type: 'array',
2107
+ example: `class onMyaddonRatingVote extends cmsAction {
2108
+ public function run($data) {
2109
+ $target = $data['target'];
2110
+ $target_id = $data['target_id'];
2111
+ $vote = $data['vote'];
2112
+ $user_id = $data['user_id'];
2113
+
2114
+ // Проверка, не голосовал ли уже пользователь
2115
+ if ($this->model->hasVoted($target, $target_id, $user_id)) {
2116
+ return $data; // Игнорируем повторный голос
2117
+ }
2118
+
2119
+ // Сохранение голоса
2120
+ $this->model->insert('myaddon_ratings', [
2121
+ 'target' => $target,
2122
+ 'target_id' => $target_id,
2123
+ 'user_id' => $user_id,
2124
+ 'vote' => $vote,
2125
+ 'created_at' => date('Y-m-d H:i:s')
2126
+ ]);
2127
+
2128
+ // Начисление баллов автору контента
2129
+ $content = $this->model->getItem($target, $target_id);
2130
+ if ($content && $content['user_id']) {
2131
+ $this->model->addRatingPoints($content['user_id'], $vote);
2132
+ }
2133
+
2134
+ return $data;
2135
+ }
2136
+ }`,
2137
+ },
2138
+ // ── MODERATION ──────────────────────────────────────────────────────────────
2139
+ {
2140
+ name: 'moderation_list',
2141
+ type: 'filter',
2142
+ category: 'moderation',
2143
+ description: 'Добавление элементов своего дополнения в очередь модерации.',
2144
+ parameters: [
2145
+ { name: '$items', type: 'array', description: 'Элементы на модерацию', modifiable: true },
2146
+ ],
2147
+ return_type: 'array',
2148
+ example: `class onMyaddonModerationList extends cmsAction {
2149
+ public function run($items) {
2150
+ $pending = $this->model->filterEqual('is_approved', 0)->get('myaddon_items');
2151
+ foreach ($pending as $item) {
2152
+ $items[] = [
2153
+ 'title' => $item['title'],
2154
+ 'url' => href_to('myaddon', 'view', $item['id']),
2155
+ 'controller' => 'myaddon',
2156
+ 'target_id' => $item['id']
2157
+ ];
2158
+ }
2159
+ return $items;
2160
+ }
2161
+ }`,
2162
+ },
2163
+ // ── CONTROLLER-SPECIFIC ──────────────────────────────────────────────────────
2164
+ {
2165
+ name: 'controller_{name}_before_save_options',
2166
+ type: 'action',
2167
+ category: 'controllers',
2168
+ description: 'Вызывается перед сохранением настроек контроллера. {name} = имя контроллера.',
2169
+ parameters: [
2170
+ { name: '$data[0]', type: 'object', description: 'Объект контроллера' },
2171
+ { name: '$data[1]', type: 'array', description: 'Новые настройки' },
2172
+ ],
2173
+ return_type: 'array',
2174
+ example: `// Хук для контроллера 'content': hooks/controller_content_before_save_options.php
2175
+ class onMyaddonControllerContentBeforeSaveOptions extends cmsAction {
2176
+ public function run($data) {
2177
+ [$controller, $options] = $data;
2178
+ return $data;
2179
+ }
2180
+ }`,
2181
+ },
2182
+ {
2183
+ name: 'controller_{name}_perms',
2184
+ type: 'filter',
2185
+ category: 'controllers',
2186
+ description: 'Добавление своих разрешений к контроллеру. {name} = имя контроллера.',
2187
+ parameters: [
2188
+ { name: '$perms', type: 'array', description: 'Массив разрешений', modifiable: true },
2189
+ ],
2190
+ return_type: 'array',
2191
+ example: `class onMyaddonControllerContentPerms extends cmsAction {
2192
+ public function run($perms) {
2193
+ $perms['myaddon_action'] = [
2194
+ 'title' => 'Моё действие',
2195
+ 'default' => ['admins']
2196
+ ];
2197
+ return $perms;
2198
+ }
2199
+ }`,
2200
+ },
2201
+ ];
2202
+ const curatedHookCategories = [
2203
+ 'engine',
2204
+ 'content',
2205
+ 'users',
2206
+ 'comments',
2207
+ 'forms',
2208
+ 'admin',
2209
+ 'template',
2210
+ 'search',
2211
+ 'sitemap',
2212
+ 'rss',
2213
+ 'cron',
2214
+ 'activity',
2215
+ 'subscriptions',
2216
+ 'groups',
2217
+ 'rating',
2218
+ 'moderation',
2219
+ 'controllers',
2220
+ ];
2221
+ function inferredHook(evidence) {
2222
+ const category = evidence.name.split('_')[0] || 'other';
2223
+ const parameterNames = evidence.parameters.length > 0 ? evidence.parameters : ['$data'];
2224
+ return {
2225
+ name: evidence.name,
2226
+ type: evidence.inferredType,
2227
+ category,
2228
+ description: `Хук найден в официальных исходниках InstantCMS (${evidence.occurrences} выз.).`,
2229
+ parameters: parameterNames.map(name => ({
2230
+ name,
2231
+ type: 'mixed',
2232
+ description: 'Параметр выведен из вызова хука в исходниках',
2233
+ modifiable: evidence.inferredType === 'filter',
2234
+ })),
2235
+ return_type: evidence.inferredType === 'filter' ? 'mixed' : 'void',
2236
+ example: `class onMyaddon${evidence.name
2237
+ .split('_')
2238
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
2239
+ .join('')} extends cmsAction {\n public function run($data) {\n return $data;\n }\n}`,
2240
+ };
2241
+ }
2242
+ const curatedHooksByName = new Map(curatedHooks.map(hook => [hook.name, hook]));
2243
+ const sourceHookNames = new Set(hooks_source_js_1.sourceHooks.map(hook => hook.name));
2244
+ exports.hooks = [
2245
+ ...hooks_source_js_1.sourceHooks.map(evidence => ({
2246
+ ...(curatedHooksByName.get(evidence.name) ?? inferredHook(evidence)),
2247
+ source: {
2248
+ repository: 'https://github.com/instantsoft/icms2',
2249
+ files: evidence.files,
2250
+ occurrences: evidence.occurrences,
2251
+ },
2252
+ })),
2253
+ ...curatedHooks.filter(hook => !sourceHookNames.has(hook.name)),
2254
+ ].sort((left, right) => left.name.localeCompare(right.name));
2255
+ exports.hookCategories = [
2256
+ ...new Set([...curatedHookCategories, ...exports.hooks.map(h => h.category)]),
2257
+ ].sort();