@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.
- package/LICENSE +21 -0
- package/README.md +308 -0
- package/dist/__tests__/artifact-tool.test.js +196 -0
- package/dist/__tests__/db-tool.test.js +152 -0
- package/dist/__tests__/define-tool.test.js +106 -0
- package/dist/__tests__/find-tool.test.js +185 -0
- package/dist/__tests__/get-component-api.test.js +144 -0
- package/dist/__tests__/hardening.test.js +73 -0
- package/dist/__tests__/hooks-tool.test.js +173 -0
- package/dist/__tests__/mcp-integration-extra.test.js +166 -0
- package/dist/__tests__/mcp-integration.test.js +53 -0
- package/dist/__tests__/pagination.test.js +114 -0
- package/dist/__tests__/performance-baseline.test.js +56 -0
- package/dist/__tests__/project-patch.test.js +144 -0
- package/dist/__tests__/project-source.test.js +44 -0
- package/dist/__tests__/project-workflows-extra.test.js +165 -0
- package/dist/__tests__/project-workflows.test.js +51 -0
- package/dist/__tests__/scaffold-addon-roundtrip.test.js +287 -0
- package/dist/__tests__/serialization.test.js +170 -0
- package/dist/__tests__/source-knowledge-parsers.test.js +42 -0
- package/dist/__tests__/template-development.test.js +78 -0
- package/dist/__tests__/template-productivity.test.js +87 -0
- package/dist/__tests__/tools.test.js +2143 -0
- package/dist/data/components.js +2818 -0
- package/dist/data/controllers-map.js +7571 -0
- package/dist/data/core-api.js +9356 -0
- package/dist/data/database-schema.js +4555 -0
- package/dist/data/events-map.js +1339 -0
- package/dist/data/fields-map.js +3692 -0
- package/dist/data/hooks.js +2257 -0
- package/dist/data/js-api.js +123 -0
- package/dist/data/libs-api.js +264 -0
- package/dist/data/routes-map.js +203 -0
- package/dist/data/schemas-validation.js +255 -0
- package/dist/data/schemas.js +1404 -0
- package/dist/data/traits-map.js +1004 -0
- package/dist/data/version-profiles.js +41 -0
- package/dist/data/widgets-map.js +131 -0
- package/dist/data/wysiwyg-map.js +486 -0
- package/dist/generated/components-source.js +14748 -0
- package/dist/generated/hooks-source.js +3399 -0
- package/dist/generated/knowledge-meta.js +101 -0
- package/dist/index.js +13 -0
- package/dist/registry/database-tools.js +112 -0
- package/dist/registry/extension-tools.js +473 -0
- package/dist/registry/generator-tools.js +506 -0
- package/dist/registry/knowledge-tools.js +441 -0
- package/dist/registry/language-tools.js +99 -0
- package/dist/registry/meta-tools.js +152 -0
- package/dist/registry/project-tools.js +48 -0
- package/dist/registry/resources.js +98 -0
- package/dist/registry/source-tools.js +211 -0
- package/dist/registry/template-development-tools.js +79 -0
- package/dist/server.js +32 -0
- package/dist/tools/addon-tool.js +1437 -0
- package/dist/tools/admin-partial-tool.js +498 -0
- package/dist/tools/api-tool.js +294 -0
- package/dist/tools/artifact-tool.js +95 -0
- package/dist/tools/cache-tool.js +360 -0
- package/dist/tools/component-tool.js +415 -0
- package/dist/tools/controllers-tool.js +125 -0
- package/dist/tools/cron-tool.js +197 -0
- package/dist/tools/crud-tool.js +659 -0
- package/dist/tools/db-tool.js +198 -0
- package/dist/tools/email-tool.js +222 -0
- package/dist/tools/external-api-tool.js +596 -0
- package/dist/tools/filter-tool.js +341 -0
- package/dist/tools/form-tool.js +275 -0
- package/dist/tools/grid-tool.js +189 -0
- package/dist/tools/hooks-tool.js +104 -0
- package/dist/tools/import-export-tool.js +548 -0
- package/dist/tools/lang-tool.js +251 -0
- package/dist/tools/layout-override-tool.js +125 -0
- package/dist/tools/layout-tool.js +548 -0
- package/dist/tools/maria-tool.js +127 -0
- package/dist/tools/mariadb.js +201 -0
- package/dist/tools/migration-tool.js +329 -0
- package/dist/tools/oauth-tool.js +520 -0
- package/dist/tools/parser/components-parser.js +76 -0
- package/dist/tools/parser/controllers-parser.js +313 -0
- package/dist/tools/parser/core-parser.js +294 -0
- package/dist/tools/parser/coverage-generator.js +424 -0
- package/dist/tools/parser/events-parser.js +127 -0
- package/dist/tools/parser/fields-parser.js +382 -0
- package/dist/tools/parser/hooks-parser.js +106 -0
- package/dist/tools/parser/sql-parser.js +197 -0
- package/dist/tools/parser/traits-parser.js +161 -0
- package/dist/tools/parser/widgets-parser.js +150 -0
- package/dist/tools/permission-tool.js +348 -0
- package/dist/tools/project-patch-tool.js +73 -0
- package/dist/tools/project-source-tool.js +188 -0
- package/dist/tools/project-workflow-tool.js +186 -0
- package/dist/tools/requirement-tool.js +212 -0
- package/dist/tools/scaffold-tool.js +815 -0
- package/dist/tools/seo-tool.js +412 -0
- package/dist/tools/source-tool.js +155 -0
- package/dist/tools/template-development-tool.js +271 -0
- package/dist/tools/template-overrides-tool.js +294 -0
- package/dist/tools/template-productivity-tool.js +319 -0
- package/dist/tools/template-tool.js +665 -0
- package/dist/tools/test-tool.js +183 -0
- package/dist/tools/webhook-tool.js +427 -0
- package/dist/tools/widget-tool.js +331 -0
- package/dist/tools/wysiwyg-tool.js +137 -0
- package/dist/types/scaffold.js +68 -0
- package/dist/utils/define-tool.js +37 -0
- package/dist/utils/find-tool.js +97 -0
- package/dist/utils/mcp-result.js +18 -0
- package/dist/utils/pagination.js +27 -0
- package/dist/utils/serialization.js +27 -0
- package/package.json +94 -0
|
@@ -0,0 +1,815 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scaffoldAddon = scaffoldAddon;
|
|
4
|
+
exports.scaffoldTemplate = scaffoldTemplate;
|
|
5
|
+
const serialization_js_1 = require("../utils/serialization.js");
|
|
6
|
+
function scaffoldAddon(opts) {
|
|
7
|
+
if (!/^[a-z][a-z0-9_]{1,63}$/.test(opts.name)) {
|
|
8
|
+
throw new Error('Техническое имя должно начинаться с латинской буквы и содержать 2–64 строчных букв, цифр или подчёркиваний');
|
|
9
|
+
}
|
|
10
|
+
const name = opts.name;
|
|
11
|
+
const Name = name.split('_').map(capitalize).join('');
|
|
12
|
+
const NAME = name.toUpperCase();
|
|
13
|
+
const version = opts.version || '1.0.0';
|
|
14
|
+
const author = opts.author || 'Author';
|
|
15
|
+
const author_url = opts.author_url || 'https://example.com';
|
|
16
|
+
const title = opts.title;
|
|
17
|
+
const description = opts.description || `Дополнение ${title}`;
|
|
18
|
+
const files = {};
|
|
19
|
+
// ── manifest.ru.ini — корень пакета ─────────────────────────────────────────
|
|
20
|
+
const versionMatch = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
|
|
21
|
+
if (!versionMatch) {
|
|
22
|
+
throw new Error('Версия должна быть указана в формате X.Y.Z, например 1.0.0');
|
|
23
|
+
}
|
|
24
|
+
const [, major, minor, build] = versionMatch;
|
|
25
|
+
files['[pkg] manifest.ru.ini'] = `[info]
|
|
26
|
+
title = ${(0, serialization_js_1.quoteIni)(title)}
|
|
27
|
+
description = ${(0, serialization_js_1.quoteIni)(description)}
|
|
28
|
+
image_hint =
|
|
29
|
+
|
|
30
|
+
[version]
|
|
31
|
+
major = ${major}
|
|
32
|
+
minor = ${minor}
|
|
33
|
+
build = ${build}
|
|
34
|
+
|
|
35
|
+
[author]
|
|
36
|
+
name = ${(0, serialization_js_1.quoteIni)(author)}
|
|
37
|
+
url = ${(0, serialization_js_1.quoteIni)(author_url)}
|
|
38
|
+
|
|
39
|
+
[install]
|
|
40
|
+
type = controller
|
|
41
|
+
name = ${name}`;
|
|
42
|
+
// ── install.sql ──────────────────────────────────────────────────────────────
|
|
43
|
+
files['[pkg] install.sql'] = `-- Замените cms_ на реальный префикс БД из system/config/config.php
|
|
44
|
+
CREATE TABLE IF NOT EXISTS \`cms_${name}_items\` (
|
|
45
|
+
\`id\` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
46
|
+
\`user_id\` int(10) unsigned NOT NULL DEFAULT '0',
|
|
47
|
+
\`title\` varchar(255) NOT NULL DEFAULT '',
|
|
48
|
+
\`text\` text,
|
|
49
|
+
\`date_pub\` datetime NOT NULL,
|
|
50
|
+
\`is_pub\` tinyint(1) unsigned NOT NULL DEFAULT '1',
|
|
51
|
+
PRIMARY KEY (\`id\`),
|
|
52
|
+
KEY \`user_id\` (\`user_id\`),
|
|
53
|
+
KEY \`is_pub\` (\`is_pub\`),
|
|
54
|
+
KEY \`date_pub\` (\`date_pub\`)
|
|
55
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`;
|
|
56
|
+
// ── Файлы контроллера ────────────────────────────────────────────────────────
|
|
57
|
+
const ctrl = `package/system/controllers/${name}`;
|
|
58
|
+
// ── model.php ────────────────────────────────────────────────────────────────
|
|
59
|
+
files[`${ctrl}/model.php`] = `<?php
|
|
60
|
+
|
|
61
|
+
class model${Name} extends cmsModel {
|
|
62
|
+
|
|
63
|
+
public function getPublished(int $limit = 10): array {
|
|
64
|
+
return $this->filterEqual('is_pub', 1)
|
|
65
|
+
->orderBy('date_pub', 'desc')
|
|
66
|
+
->limit($limit)
|
|
67
|
+
->get('${name}_items');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public function getByUser(int $user_id): array {
|
|
71
|
+
return $this->filterEqual('user_id', $user_id)
|
|
72
|
+
->orderBy('date_pub', 'desc')
|
|
73
|
+
->get('${name}_items');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
}`;
|
|
77
|
+
// ── frontend.php ─────────────────────────────────────────────────────────────
|
|
78
|
+
files[`${ctrl}/frontend.php`] = `<?php
|
|
79
|
+
/**
|
|
80
|
+
* @property \\model${Name} $model
|
|
81
|
+
*/
|
|
82
|
+
class ${name} extends cmsFrontend {
|
|
83
|
+
|
|
84
|
+
protected $useOptions = true;
|
|
85
|
+
|
|
86
|
+
// Экшены размещаются в actions/*.php
|
|
87
|
+
// Шаблоны — в /templates/{theme}/controllers/${name}/*.tpl.php
|
|
88
|
+
// Языковой файл — /system/languages/ru/controllers/${name}/${name}.php
|
|
89
|
+
|
|
90
|
+
}`;
|
|
91
|
+
// ── manifest.xml ─────────────────────────────────────────────────────────────
|
|
92
|
+
const hookEntries = (opts.hooks || [])
|
|
93
|
+
.map(h => ` <hook controller="${name}" name="${(0, serialization_js_1.escapeXml)(h)}" />`)
|
|
94
|
+
.join('\n');
|
|
95
|
+
files[`${ctrl}/manifest.xml`] = `<?xml version="1.0" encoding="utf-8"?>
|
|
96
|
+
<addon>
|
|
97
|
+
<name>${name}</name>
|
|
98
|
+
<title>${(0, serialization_js_1.escapeXml)(title)}</title>
|
|
99
|
+
<description>${(0, serialization_js_1.escapeXml)(description)}</description>
|
|
100
|
+
<version>${(0, serialization_js_1.escapeXml)(version)}</version>
|
|
101
|
+
<build>${build}</build>
|
|
102
|
+
<author>
|
|
103
|
+
<name>${(0, serialization_js_1.escapeXml)(author)}</name>
|
|
104
|
+
<url>${(0, serialization_js_1.escapeXml)(author_url)}</url>
|
|
105
|
+
<email></email>
|
|
106
|
+
</author>
|
|
107
|
+
<dependencies>
|
|
108
|
+
</dependencies>
|
|
109
|
+
<hooks>
|
|
110
|
+
${hookEntries || ' <!-- <hook controller="${name}" name="user_registered" /> -->'}
|
|
111
|
+
</hooks>
|
|
112
|
+
</addon>`;
|
|
113
|
+
// ── install.php / uninstall.php ───────────────────────────────────────────────
|
|
114
|
+
files[`${ctrl}/install.php`] = `<?php
|
|
115
|
+
|
|
116
|
+
class install${Name} extends cmsInstaller {
|
|
117
|
+
|
|
118
|
+
public function install() {
|
|
119
|
+
// SQL выполняется автоматически из install.sql при установке пакета.
|
|
120
|
+
// Здесь можно добавить дополнительную инициализацию:
|
|
121
|
+
// $this->addOption('${name}', 'perpage', 10);
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
}`;
|
|
126
|
+
files[`${ctrl}/uninstall.php`] = `<?php
|
|
127
|
+
|
|
128
|
+
class uninstall${Name} extends cmsInstaller {
|
|
129
|
+
|
|
130
|
+
public function uninstall() {
|
|
131
|
+
$this->db->query("DROP TABLE IF EXISTS \`{$this->db->prefix}${name}_items\`");
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
}`;
|
|
136
|
+
// ── actions/index.php ────────────────────────────────────────────────────────
|
|
137
|
+
files[`${ctrl}/actions/index.php`] = `<?php
|
|
138
|
+
|
|
139
|
+
class action${Name}Index extends cmsAction {
|
|
140
|
+
|
|
141
|
+
public function run() {
|
|
142
|
+
|
|
143
|
+
$page = $this->request->get('page', 1);
|
|
144
|
+
$perpage = $this->options['perpage'] ?? 10;
|
|
145
|
+
|
|
146
|
+
$total = $this->model->filterEqual('is_pub', 1)->getCount('${name}_items');
|
|
147
|
+
|
|
148
|
+
$items = $this->model
|
|
149
|
+
->filterEqual('is_pub', 1)
|
|
150
|
+
->orderBy('date_pub', 'desc')
|
|
151
|
+
->limitPage($page, $perpage)
|
|
152
|
+
->get('${name}_items');
|
|
153
|
+
|
|
154
|
+
$this->cms_template->setTitle(LANG_${NAME}_TITLE);
|
|
155
|
+
|
|
156
|
+
// Шаблон: /templates/{theme}/controllers/${name}/index.tpl.php
|
|
157
|
+
return $this->cms_template->render('index', [
|
|
158
|
+
'items' => $items,
|
|
159
|
+
'total' => $total,
|
|
160
|
+
'page' => (int) $page,
|
|
161
|
+
'perpage' => (int) $perpage,
|
|
162
|
+
]);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
}`;
|
|
166
|
+
// ── actions/view.php ─────────────────────────────────────────────────────────
|
|
167
|
+
files[`${ctrl}/actions/view.php`] = `<?php
|
|
168
|
+
|
|
169
|
+
class action${Name}View extends cmsAction {
|
|
170
|
+
|
|
171
|
+
public function run() {
|
|
172
|
+
|
|
173
|
+
$id = $this->request->get('id', 0);
|
|
174
|
+
$item = $this->model->getItemByField('${name}_items', 'id', (int) $id);
|
|
175
|
+
|
|
176
|
+
if (!$item || !$item['is_pub']) {
|
|
177
|
+
return cmsCore::error404();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
$item = cmsEventsManager::hook('${name}_before_item', $item);
|
|
181
|
+
|
|
182
|
+
$this->cms_template->setTitle($item['title']);
|
|
183
|
+
$this->cms_template->addBreadcrumb(LANG_${NAME}_TITLE, href_to('${name}'));
|
|
184
|
+
$this->cms_template->addBreadcrumb($item['title']);
|
|
185
|
+
|
|
186
|
+
// Шаблон: /templates/{theme}/controllers/${name}/view.tpl.php
|
|
187
|
+
return $this->cms_template->render('view', [
|
|
188
|
+
'item' => $item,
|
|
189
|
+
]);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
}`;
|
|
193
|
+
// ── routes.php — только для with_routes ─────────────────────────────────────
|
|
194
|
+
if (opts.type === 'with_routes') {
|
|
195
|
+
files[`${ctrl}/routes.php`] = generateRoutes(name);
|
|
196
|
+
}
|
|
197
|
+
// ── Языковой файл ────────────────────────────────────────────────────────────
|
|
198
|
+
// ВАЖНО: файл расположен ВНЕ папки контроллера!
|
|
199
|
+
files[`package/system/languages/ru/controllers/${name}/${name}.php`] = `<?php
|
|
200
|
+
// Файл: /system/languages/ru/controllers/${name}/${name}.php
|
|
201
|
+
// ВАЖНО: языковой файл расположен в /system/languages/, а НЕ внутри контроллера!
|
|
202
|
+
|
|
203
|
+
define('LANG_${NAME}_TITLE', ${(0, serialization_js_1.quotePhp)(title)});
|
|
204
|
+
define('LANG_${NAME}_ADD', 'Добавить');
|
|
205
|
+
define('LANG_${NAME}_EDIT', 'Редактировать');
|
|
206
|
+
define('LANG_${NAME}_DELETE', 'Удалить');
|
|
207
|
+
define('LANG_${NAME}_NOT_FOUND', 'Ничего не найдено');
|
|
208
|
+
|
|
209
|
+
// Константы для бэкенда (рекомендуется префикс _CP_):
|
|
210
|
+
define('LANG_${NAME}_CP_TITLE', '${title}');
|
|
211
|
+
define('LANG_${NAME}_CP_ITEMS', 'Элементы');
|
|
212
|
+
define('LANG_${NAME}_CP_ADD', 'Добавить элемент');`;
|
|
213
|
+
// ── Хуки ────────────────────────────────────────────────────────────────────
|
|
214
|
+
if (opts.hooks && opts.hooks.length > 0) {
|
|
215
|
+
for (const hookName of opts.hooks) {
|
|
216
|
+
const hookCamel = hookName
|
|
217
|
+
.split('_')
|
|
218
|
+
.map((w) => capitalize(w))
|
|
219
|
+
.join('');
|
|
220
|
+
files[`${ctrl}/hooks/${hookName}.php`] = `<?php
|
|
221
|
+
// Хук: ${hookName}
|
|
222
|
+
// Регистрация в manifest.xml: <hook controller="${name}" name="${hookName}" />
|
|
223
|
+
|
|
224
|
+
class on${Name}${hookCamel} extends cmsAction {
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* @param mixed $data Данные хука (тип зависит от конкретного хука)
|
|
228
|
+
* @return mixed Для filter-хуков ОБЯЗАТЕЛЬНО вернуть $data
|
|
229
|
+
*/
|
|
230
|
+
public function run($data) {
|
|
231
|
+
|
|
232
|
+
// Доступны стандартные свойства:
|
|
233
|
+
// $this->model — модель дополнения ${name}
|
|
234
|
+
// $this->cms_user — текущий пользователь
|
|
235
|
+
// $this->request — объект запроса
|
|
236
|
+
|
|
237
|
+
// Пример: загрузить другой контроллер:
|
|
238
|
+
// $content = cmsCore::getController('content');
|
|
239
|
+
|
|
240
|
+
// TODO: ваша логика
|
|
241
|
+
|
|
242
|
+
return $data; // ОБЯЗАТЕЛЬНО вернуть $data для filter-хуков!
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
}`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// ── Бэкенд (if with_admin) ───────────────────────────────────────────────────
|
|
249
|
+
if (opts.type === 'with_admin') {
|
|
250
|
+
// backend.php — только getBackendMenu() и before(), БЕЗ inline экшенов
|
|
251
|
+
files[`${ctrl}/backend.php`] = generateBackend(name, Name, NAME);
|
|
252
|
+
// backend/model.php — расширение модели для бэкенда
|
|
253
|
+
files[`${ctrl}/backend/model.php`] = generateBackendModel(name, Name);
|
|
254
|
+
// backend/actions/index.php — дашборд бэкенда
|
|
255
|
+
files[`${ctrl}/backend/actions/index.php`] = generateBackendActionIndex(name, Name, NAME);
|
|
256
|
+
// backend/actions/items.php — список с трейтом listgrid
|
|
257
|
+
files[`${ctrl}/backend/actions/items.php`] = generateBackendActionItems(name, Name, NAME);
|
|
258
|
+
// backend/actions/items_add.php — добавление/редактирование с трейтом formItem
|
|
259
|
+
files[`${ctrl}/backend/actions/items_add.php`] = generateBackendActionItemsAdd(name, Name, NAME);
|
|
260
|
+
// backend/grids/grid_items.php — ФУНКЦИЯ, не класс!
|
|
261
|
+
files[`${ctrl}/backend/grids/grid_items.php`] = generateGridItems(name, Name, NAME);
|
|
262
|
+
// backend/forms/form_item.php — форма с init($do)
|
|
263
|
+
files[`${ctrl}/backend/forms/form_item.php`] = generateFormItem(Name, NAME);
|
|
264
|
+
// backend/forms/form_options.php — настройки дополнения
|
|
265
|
+
files[`${ctrl}/backend/forms/form_options.php`] = generateFormOptions(Name);
|
|
266
|
+
}
|
|
267
|
+
// ── Виджет (if with_widget) ──────────────────────────────────────────────────
|
|
268
|
+
if (opts.type === 'with_widget') {
|
|
269
|
+
files[`${ctrl}/widgets/list/widget.php`] = generateWidget(name, Name);
|
|
270
|
+
files[`${ctrl}/widgets/list/options.form.php`] = generateWidgetOptionsForm(Name);
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
addon_name: name,
|
|
274
|
+
addon_class: Name,
|
|
275
|
+
type: opts.type,
|
|
276
|
+
files_count: Object.keys(files).length,
|
|
277
|
+
files,
|
|
278
|
+
installation_note: `Пакет для менеджера дополнений: manifest.ru.ini + install.sql + package/ → архив ${name}.zip`,
|
|
279
|
+
structure_notes: [
|
|
280
|
+
`backend.php содержит ТОЛЬКО getBackendMenu() — экшены в backend/actions/`,
|
|
281
|
+
`backend/grids/grid_items.php — ФУНКЦИЯ grid_items($controller), не класс`,
|
|
282
|
+
`Языковой файл: /system/languages/ru/controllers/${name}/${name}.php (ВНЕ контроллера!)`,
|
|
283
|
+
`Шаблоны фронтенда: /templates/{theme}/controllers/${name}/*.tpl.php`,
|
|
284
|
+
`Шаблоны бэкенда: /templates/admincoreui/controllers/${name}/*.tpl.php`,
|
|
285
|
+
],
|
|
286
|
+
next_steps: [
|
|
287
|
+
`1. Упаковать в zip: manifest.ru.ini + install.sql + package/ → ${name}.zip`,
|
|
288
|
+
`2. Установить: Панель управления → Расширения → Загрузить пакет`,
|
|
289
|
+
`3. Создать шаблоны в /templates/default/controllers/${name}/index.tpl.php и view.tpl.php`,
|
|
290
|
+
opts.type === 'with_admin'
|
|
291
|
+
? `4. Шаблоны бэкенда: /templates/admincoreui/controllers/${name}/index.tpl.php`
|
|
292
|
+
: `4. При необходимости добавить виджеты в widgets/`,
|
|
293
|
+
],
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function scaffoldTemplate(opts) {
|
|
297
|
+
if (!/^[a-z][a-z0-9_]{1,63}$/.test(opts.name)) {
|
|
298
|
+
throw new Error('Техническое имя шаблона должно начинаться с латинской буквы и содержать 2–64 допустимых символа');
|
|
299
|
+
}
|
|
300
|
+
const name = opts.name;
|
|
301
|
+
const files = {
|
|
302
|
+
'manifest.php': `<?php
|
|
303
|
+
return [
|
|
304
|
+
'title' => ${(0, serialization_js_1.quotePhp)(opts.title)},
|
|
305
|
+
'author' => [
|
|
306
|
+
'name' => ${(0, serialization_js_1.quotePhp)(opts.author || 'Author')},
|
|
307
|
+
'url' => 'https://example.com',
|
|
308
|
+
'help' => 'https://example.com/docs'
|
|
309
|
+
],
|
|
310
|
+
'properties' => [
|
|
311
|
+
'has_options' => true,
|
|
312
|
+
'has_profile_themes_support' => false,
|
|
313
|
+
'has_profile_themes_options' => false,
|
|
314
|
+
'is_dynamic_layout' => false,
|
|
315
|
+
'is_backend' => false,
|
|
316
|
+
'is_frontend' => true
|
|
317
|
+
]
|
|
318
|
+
];`,
|
|
319
|
+
'main.tpl.php': `<!DOCTYPE html>
|
|
320
|
+
<html lang="<?= LANG_CODE ?>">
|
|
321
|
+
<head>
|
|
322
|
+
<meta charset="utf-8">
|
|
323
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
324
|
+
<title><?= $this->title() ?></title>
|
|
325
|
+
<?= $this->head() ?>
|
|
326
|
+
<?= $this->linkCSS('css/main.css') ?>
|
|
327
|
+
</head>
|
|
328
|
+
<body>
|
|
329
|
+
|
|
330
|
+
<header class="site-header">
|
|
331
|
+
<?= $this->widgets('header') ?>
|
|
332
|
+
<?= $this->widgets('top') ?>
|
|
333
|
+
</header>
|
|
334
|
+
|
|
335
|
+
<?= $this->breadcrumbs() ?>
|
|
336
|
+
|
|
337
|
+
<div class="site-wrap">
|
|
338
|
+
<?php if ($this->hasWidgetsOn('left-top') || $this->hasWidgetsOn('left-bottom')): ?>
|
|
339
|
+
<aside class="sidebar sidebar-left">
|
|
340
|
+
<?= $this->widgets('left-top') ?>
|
|
341
|
+
<?= $this->widgets('left-bottom') ?>
|
|
342
|
+
</aside>
|
|
343
|
+
<?php endif ?>
|
|
344
|
+
|
|
345
|
+
<main class="site-main">
|
|
346
|
+
<?= $this->body() ?>
|
|
347
|
+
</main>
|
|
348
|
+
|
|
349
|
+
<?php if ($this->hasWidgetsOn('right-top')): ?>
|
|
350
|
+
<aside class="sidebar sidebar-right">
|
|
351
|
+
<?= $this->widgets('right-top') ?>
|
|
352
|
+
<?= $this->widgets('right-center') ?>
|
|
353
|
+
<?= $this->widgets('right-bottom') ?>
|
|
354
|
+
</aside>
|
|
355
|
+
<?php endif ?>
|
|
356
|
+
</div>
|
|
357
|
+
|
|
358
|
+
<footer class="site-footer">
|
|
359
|
+
<?= $this->widgets('footer') ?>
|
|
360
|
+
</footer>
|
|
361
|
+
|
|
362
|
+
<?= $this->bottom() ?>
|
|
363
|
+
</body>
|
|
364
|
+
</html>`,
|
|
365
|
+
'css/main.css': `/* ${opts.title} */
|
|
366
|
+
:root {
|
|
367
|
+
--color-primary: #4a90d9;
|
|
368
|
+
--color-text: #333;
|
|
369
|
+
--color-bg: #fff;
|
|
370
|
+
--color-border: #e0e0e0;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
* { box-sizing: border-box; }
|
|
374
|
+
body { font-family: sans-serif; color: var(--color-text); background: var(--color-bg); margin: 0; }
|
|
375
|
+
.site-wrap { display: flex; max-width: 1200px; margin: 0 auto; padding: 0 16px; gap: 24px; }
|
|
376
|
+
.site-main { flex: 1; min-width: 0; }
|
|
377
|
+
.sidebar { width: 240px; flex-shrink: 0; }`,
|
|
378
|
+
};
|
|
379
|
+
return {
|
|
380
|
+
template_name: name,
|
|
381
|
+
files,
|
|
382
|
+
placement: `/templates/${name}/`,
|
|
383
|
+
structure_notes: [
|
|
384
|
+
`Шаблон размещается в /templates/${name}/ (НЕ в /system/templates/)`,
|
|
385
|
+
`Для переопределения шаблонов контроллеров: controllers/{controller}/{action}.tpl.php`,
|
|
386
|
+
`Для бэкенда используется шаблон admincoreui (не фронтенд-шаблон)`,
|
|
387
|
+
`Переменные в .tpl.php: $this->title(), $this->body(), $this->widgets('position'), $this->breadcrumbs()`,
|
|
388
|
+
`Проверка виджетов: $this->hasWidgetsOn('right-top')`,
|
|
389
|
+
],
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
393
|
+
// Helpers
|
|
394
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
395
|
+
function capitalize(str) {
|
|
396
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
397
|
+
}
|
|
398
|
+
// backend.php — ТОЛЬКО getBackendMenu() + before(), без inline экшенов
|
|
399
|
+
function generateBackend(name, Name, NAME) {
|
|
400
|
+
return `<?php
|
|
401
|
+
/**
|
|
402
|
+
* @property \\modelBackend${Name} $model
|
|
403
|
+
*/
|
|
404
|
+
class backend${Name} extends cmsBackend {
|
|
405
|
+
|
|
406
|
+
// true = автоматически создать экшен 'options' для формы настроек
|
|
407
|
+
public $useDefaultOptionsAction = true;
|
|
408
|
+
|
|
409
|
+
// Загружать настройки дополнения
|
|
410
|
+
protected $useOptions = true;
|
|
411
|
+
|
|
412
|
+
public function __construct(cmsRequest $request) {
|
|
413
|
+
parent::__construct($request);
|
|
414
|
+
// Передать настройки в модель (если модели нужны опции):
|
|
415
|
+
// $this->model->setControllerOptions($this->options);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Вызывается перед каждым экшеном бэкенда
|
|
419
|
+
public function before($action_name) {
|
|
420
|
+
if (!parent::before($action_name)) {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Меню левой панели бэкенда
|
|
427
|
+
public function getBackendMenu() {
|
|
428
|
+
return [
|
|
429
|
+
[
|
|
430
|
+
'title' => LANG_${NAME}_CP_ITEMS,
|
|
431
|
+
'url' => href_to($this->root_url, 'items'),
|
|
432
|
+
'options' => ['icon' => 'list']
|
|
433
|
+
],
|
|
434
|
+
[
|
|
435
|
+
'title' => LANG_OPTIONS,
|
|
436
|
+
'url' => href_to($this->root_url, 'options'),
|
|
437
|
+
'options' => ['icon' => 'cog']
|
|
438
|
+
]
|
|
439
|
+
];
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
}`;
|
|
443
|
+
}
|
|
444
|
+
// backend/model.php — расширение модели для бэкенда
|
|
445
|
+
function generateBackendModel(name, Name) {
|
|
446
|
+
return `<?php
|
|
447
|
+
/**
|
|
448
|
+
* Расширение модели для нужд бэкенда.
|
|
449
|
+
* Добавляет JOIN-ы, агрегации и методы для бэкенд-экшенов.
|
|
450
|
+
*/
|
|
451
|
+
class modelBackend${Name} extends model${Name} {
|
|
452
|
+
|
|
453
|
+
// Статистика для дашборда
|
|
454
|
+
public function getStats(): array {
|
|
455
|
+
return [
|
|
456
|
+
'total' => $this->getCount('${name}_items'),
|
|
457
|
+
'published' => $this->filterEqual('is_pub', 1)->getCount('${name}_items'),
|
|
458
|
+
];
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
}`;
|
|
462
|
+
}
|
|
463
|
+
// backend/actions/index.php — дашборд
|
|
464
|
+
function generateBackendActionIndex(name, Name, _NAME) {
|
|
465
|
+
return `<?php
|
|
466
|
+
/**
|
|
467
|
+
* Дашборд бэкенда.
|
|
468
|
+
* URL: /admin/${name}/ или /admin/${name}/index
|
|
469
|
+
*
|
|
470
|
+
* @property \\modelBackend${Name} $model
|
|
471
|
+
*/
|
|
472
|
+
class action${Name}Index extends cmsAction {
|
|
473
|
+
|
|
474
|
+
public function run($do = false) {
|
|
475
|
+
|
|
476
|
+
// Делегация вложенным экшенам: /admin/${name}/index/something
|
|
477
|
+
if ($do) {
|
|
478
|
+
return $this->runAction('index_' . $do, array_slice($this->params, 1));
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
$stats = $this->model->getStats();
|
|
482
|
+
|
|
483
|
+
// Шаблон: /templates/admincoreui/controllers/${name}/index.tpl.php
|
|
484
|
+
return $this->cms_template->render([
|
|
485
|
+
'stats' => $stats,
|
|
486
|
+
]);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
}`;
|
|
490
|
+
}
|
|
491
|
+
// backend/actions/items.php — список с трейтом listgrid
|
|
492
|
+
function generateBackendActionItems(name, Name, NAME) {
|
|
493
|
+
return `<?php
|
|
494
|
+
/**
|
|
495
|
+
* Список элементов в бэкенде.
|
|
496
|
+
* Использует trait listgrid — не нужен метод run(), настройка через __construct().
|
|
497
|
+
* URL: /admin/${name}/items
|
|
498
|
+
*/
|
|
499
|
+
class action${Name}Items extends cmsAction {
|
|
500
|
+
|
|
501
|
+
use icms\\traits\\controllers\\actions\\listgrid;
|
|
502
|
+
|
|
503
|
+
public function __construct($controller, $params = []) {
|
|
504
|
+
|
|
505
|
+
parent::__construct($controller, $params);
|
|
506
|
+
|
|
507
|
+
// ОБЯЗАТЕЛЬНО: таблица и грид
|
|
508
|
+
$this->table_name = '${name}_items';
|
|
509
|
+
$this->grid_name = 'items'; // файл: backend/grids/grid_items.php
|
|
510
|
+
$this->title = LANG_${NAME}_CP_ITEMS;
|
|
511
|
+
|
|
512
|
+
// Кнопка "Добавить" в тулбаре
|
|
513
|
+
$this->tool_buttons = [
|
|
514
|
+
[
|
|
515
|
+
'class' => 'add',
|
|
516
|
+
'title' => LANG_${NAME}_CP_ADD,
|
|
517
|
+
'href' => $this->cms_template->href_to('items', 'add')
|
|
518
|
+
]
|
|
519
|
+
];
|
|
520
|
+
|
|
521
|
+
// Делегировать подэкшены: items/add, items/edit, items/delete
|
|
522
|
+
$this->external_action_prefix = 'items_';
|
|
523
|
+
|
|
524
|
+
// Дополнительные JOIN-ы к запросу:
|
|
525
|
+
// $this->list_callback = function (\\cmsModel $model) {
|
|
526
|
+
// return $model->joinLeft('users u', 'u.id = t.user_id', ['u.nickname as user_nickname']);
|
|
527
|
+
// };
|
|
528
|
+
|
|
529
|
+
// Постобработка данных:
|
|
530
|
+
// $this->items_callback = function ($items) { return $items; };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
}`;
|
|
534
|
+
}
|
|
535
|
+
// backend/actions/items_add.php — добавление/редактирование с трейтом formItem
|
|
536
|
+
function generateBackendActionItemsAdd(name, Name, NAME) {
|
|
537
|
+
return `<?php
|
|
538
|
+
/**
|
|
539
|
+
* Добавление / редактирование элемента в бэкенде.
|
|
540
|
+
* Trait formItem обрабатывает и 'add' (items/add) и 'edit' (items/edit/ID) — один файл!
|
|
541
|
+
* URL add: /admin/${name}/items/add
|
|
542
|
+
* URL edit: /admin/${name}/items/edit/{id}
|
|
543
|
+
* URL copy: /admin/${name}/items/add/{id}/1
|
|
544
|
+
*/
|
|
545
|
+
class action${Name}ItemsAdd extends cmsAction {
|
|
546
|
+
|
|
547
|
+
use icms\\traits\\controllers\\actions\\formItem;
|
|
548
|
+
|
|
549
|
+
public function __construct($controller, $params = []) {
|
|
550
|
+
|
|
551
|
+
parent::__construct($controller, $params);
|
|
552
|
+
|
|
553
|
+
$list_url = $this->cms_template->href_to('items');
|
|
554
|
+
|
|
555
|
+
// ОБЯЗАТЕЛЬНО:
|
|
556
|
+
$this->table_name = '${name}_items';
|
|
557
|
+
$this->form_name = 'item'; // файл: backend/forms/form_item.php
|
|
558
|
+
$this->success_url = $list_url;
|
|
559
|
+
|
|
560
|
+
// Заголовок страницы:
|
|
561
|
+
$this->title = [
|
|
562
|
+
'add' => LANG_${NAME}_CP_ADD,
|
|
563
|
+
'edit' => '{title}' // {title} заменяется значением поля title записи
|
|
564
|
+
];
|
|
565
|
+
|
|
566
|
+
// Хлебные крошки:
|
|
567
|
+
$this->breadcrumbs = [
|
|
568
|
+
[LANG_${NAME}_CP_ITEMS, $list_url],
|
|
569
|
+
isset($params[0]) ? '{title}' : LANG_${NAME}_CP_ADD
|
|
570
|
+
];
|
|
571
|
+
|
|
572
|
+
// Кнопки Сохранить / Отменить в тулбаре:
|
|
573
|
+
$this->use_default_tool_buttons = true;
|
|
574
|
+
|
|
575
|
+
// Дефолтные значения для новой записи:
|
|
576
|
+
$this->default_item = [
|
|
577
|
+
'is_pub' => 1,
|
|
578
|
+
'date_pub' => date('Y-m-d H:i:s')
|
|
579
|
+
];
|
|
580
|
+
|
|
581
|
+
// Коллбэки после сохранения:
|
|
582
|
+
// $this->add_callback = function($id, $data) {
|
|
583
|
+
// cmsCache::getInstance()->clean('${name}');
|
|
584
|
+
// };
|
|
585
|
+
// $this->update_callback = function($data) {
|
|
586
|
+
// cmsCache::getInstance()->clean('${name}');
|
|
587
|
+
// };
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
}`;
|
|
591
|
+
}
|
|
592
|
+
// backend/grids/grid_items.php — ФУНКЦИЯ, не класс!
|
|
593
|
+
function generateGridItems(name, _Name, _NAME) {
|
|
594
|
+
return `<?php
|
|
595
|
+
// ВАЖНО: грид — это ФУНКЦИЯ, а не класс!
|
|
596
|
+
// Имя функции = grid_ + значение $this->grid_name из экшена
|
|
597
|
+
|
|
598
|
+
function grid_items($controller) {
|
|
599
|
+
|
|
600
|
+
$options = [
|
|
601
|
+
'is_sortable' => true,
|
|
602
|
+
'is_filter' => true,
|
|
603
|
+
'is_pagination' => true,
|
|
604
|
+
'is_draggable' => false,
|
|
605
|
+
'is_selectable' => false,
|
|
606
|
+
'order_by' => 'id',
|
|
607
|
+
'order_to' => 'desc',
|
|
608
|
+
'show_id' => true
|
|
609
|
+
// Для drag&drop:
|
|
610
|
+
// 'is_draggable' => true,
|
|
611
|
+
// 'drag_save_url' => href_to('admin', 'reorder', '${name}_items'),
|
|
612
|
+
];
|
|
613
|
+
|
|
614
|
+
$columns = [
|
|
615
|
+
'title' => [
|
|
616
|
+
'title' => LANG_TITLE,
|
|
617
|
+
'filter' => 'like',
|
|
618
|
+
'href' => href_to($controller->root_url, 'items', ['edit', '{id}'])
|
|
619
|
+
],
|
|
620
|
+
'date_pub' => [
|
|
621
|
+
'title' => LANG_DATE_PUB,
|
|
622
|
+
'width' => 150,
|
|
623
|
+
'filter' => 'date',
|
|
624
|
+
'handler' => function ($value) {
|
|
625
|
+
return html_date_time($value);
|
|
626
|
+
}
|
|
627
|
+
],
|
|
628
|
+
'is_pub' => [
|
|
629
|
+
'title' => LANG_IS_PUB,
|
|
630
|
+
'width' => 60,
|
|
631
|
+
'flag' => true,
|
|
632
|
+
'flag_toggle' => href_to($controller->root_url, 'toggle_item', ['{id}', '${name}_items', 'is_pub'])
|
|
633
|
+
]
|
|
634
|
+
];
|
|
635
|
+
|
|
636
|
+
$actions = [
|
|
637
|
+
[
|
|
638
|
+
'title' => LANG_EDIT,
|
|
639
|
+
'icon' => 'pen',
|
|
640
|
+
'href' => href_to($controller->root_url, 'items', ['edit', '{id}'])
|
|
641
|
+
],
|
|
642
|
+
[
|
|
643
|
+
'title' => LANG_DELETE,
|
|
644
|
+
'class' => 'text-danger',
|
|
645
|
+
'icon' => 'times-circle',
|
|
646
|
+
'confirm' => LANG_DELETE_CONFIRM,
|
|
647
|
+
'href' => href_to($controller->root_url, 'items', ['delete', '{id}'])
|
|
648
|
+
]
|
|
649
|
+
];
|
|
650
|
+
|
|
651
|
+
return [
|
|
652
|
+
'options' => $options,
|
|
653
|
+
'columns' => $columns,
|
|
654
|
+
'actions' => $actions
|
|
655
|
+
];
|
|
656
|
+
}`;
|
|
657
|
+
}
|
|
658
|
+
// backend/forms/form_item.php
|
|
659
|
+
function generateFormItem(Name, _NAME) {
|
|
660
|
+
return `<?php
|
|
661
|
+
|
|
662
|
+
class form${Name}Item extends cmsForm {
|
|
663
|
+
|
|
664
|
+
// $do = 'add' или 'edit' (передаётся из trait formItem)
|
|
665
|
+
public function init($do) {
|
|
666
|
+
|
|
667
|
+
return [
|
|
668
|
+
'basic' => [
|
|
669
|
+
'title' => LANG_CP_BASIC,
|
|
670
|
+
'type' => 'fieldset',
|
|
671
|
+
'childs' => [
|
|
672
|
+
new fieldString('title', [
|
|
673
|
+
'title' => LANG_TITLE,
|
|
674
|
+
'rules' => [['required'], ['max_length', 255]]
|
|
675
|
+
]),
|
|
676
|
+
new fieldHtml('text', [
|
|
677
|
+
'title' => LANG_TEXT
|
|
678
|
+
]),
|
|
679
|
+
new fieldDate('date_pub', [
|
|
680
|
+
'title' => LANG_DATE_PUB,
|
|
681
|
+
'default' => date('Y-m-d H:i:s')
|
|
682
|
+
]),
|
|
683
|
+
new fieldCheckbox('is_pub', [
|
|
684
|
+
'title' => LANG_IS_PUB,
|
|
685
|
+
'default' => 1
|
|
686
|
+
])
|
|
687
|
+
]
|
|
688
|
+
]
|
|
689
|
+
];
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
}`;
|
|
693
|
+
}
|
|
694
|
+
// backend/forms/form_options.php
|
|
695
|
+
function generateFormOptions(Name) {
|
|
696
|
+
return `<?php
|
|
697
|
+
|
|
698
|
+
class form${Name}Options extends cmsForm {
|
|
699
|
+
|
|
700
|
+
public function init() {
|
|
701
|
+
return [
|
|
702
|
+
'basic' => [
|
|
703
|
+
'title' => LANG_CP_BASIC,
|
|
704
|
+
'type' => 'fieldset',
|
|
705
|
+
'childs' => [
|
|
706
|
+
new fieldNumber('perpage', [
|
|
707
|
+
'title' => LANG_LIST_LIMIT,
|
|
708
|
+
'default' => 10,
|
|
709
|
+
'rules' => [['required'], ['min', 1]]
|
|
710
|
+
]),
|
|
711
|
+
new fieldCheckbox('use_moderation', [
|
|
712
|
+
'title' => LANG_USE_MODERATION
|
|
713
|
+
])
|
|
714
|
+
]
|
|
715
|
+
]
|
|
716
|
+
];
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
}`;
|
|
720
|
+
}
|
|
721
|
+
// widgets/list/widget.php
|
|
722
|
+
function generateWidget(name, Name) {
|
|
723
|
+
return `<?php
|
|
724
|
+
/**
|
|
725
|
+
* Виджет списка элементов.
|
|
726
|
+
* Шаблон: /templates/{theme}/controllers/${name}/list.tpl.php
|
|
727
|
+
*/
|
|
728
|
+
class widget${Name}List extends cmsWidget {
|
|
729
|
+
|
|
730
|
+
// Отключить кэш для динамического контента:
|
|
731
|
+
// public $is_cacheable = false;
|
|
732
|
+
|
|
733
|
+
public function run() {
|
|
734
|
+
|
|
735
|
+
$limit = $this->getOption('limit', 5);
|
|
736
|
+
|
|
737
|
+
$controller = cmsCore::getController('${name}');
|
|
738
|
+
|
|
739
|
+
$items = $controller->model
|
|
740
|
+
->filterEqual('is_pub', 1)
|
|
741
|
+
->orderBy('date_pub', 'desc')
|
|
742
|
+
->limit($limit)
|
|
743
|
+
->get('${name}_items');
|
|
744
|
+
|
|
745
|
+
if (!$items) {
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
return [
|
|
750
|
+
'items' => cmsEventsManager::hook('${name}_before_list', $items),
|
|
751
|
+
'limit' => $limit
|
|
752
|
+
];
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
}`;
|
|
756
|
+
}
|
|
757
|
+
// widgets/list/options.form.php
|
|
758
|
+
// Класс: formWidget{Name}ListOptions (не formWidget{Name}List!)
|
|
759
|
+
function generateWidgetOptionsForm(Name) {
|
|
760
|
+
return `<?php
|
|
761
|
+
// Класс опций виджета: formWidget + PascalCase(controller) + PascalCase(widget) + Options
|
|
762
|
+
|
|
763
|
+
class formWidget${Name}ListOptions extends cmsForm {
|
|
764
|
+
|
|
765
|
+
public function init($options = false) {
|
|
766
|
+
return [
|
|
767
|
+
'woptions' => [
|
|
768
|
+
'type' => 'fieldset',
|
|
769
|
+
'title' => LANG_OPTIONS,
|
|
770
|
+
'childs' => [
|
|
771
|
+
new fieldNumber('options:limit', [
|
|
772
|
+
'title' => LANG_LIST_LIMIT,
|
|
773
|
+
'default' => 5,
|
|
774
|
+
'options' => ['is_abs' => true]
|
|
775
|
+
]),
|
|
776
|
+
new fieldList('options:is_pub', [
|
|
777
|
+
'title' => LANG_SHOW,
|
|
778
|
+
'default' => 1,
|
|
779
|
+
'items' => [1 => LANG_PUBLISHED_ONLY, 0 => LANG_ALL]
|
|
780
|
+
])
|
|
781
|
+
]
|
|
782
|
+
]
|
|
783
|
+
];
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
}`;
|
|
787
|
+
}
|
|
788
|
+
// routes.php
|
|
789
|
+
function generateRoutes(name) {
|
|
790
|
+
return `<?php
|
|
791
|
+
|
|
792
|
+
function routes_${name}() {
|
|
793
|
+
|
|
794
|
+
return [
|
|
795
|
+
// /myaddon/item-slug.html → action view
|
|
796
|
+
[
|
|
797
|
+
'pattern' => '/^([a-z0-9\\-_]+)\\.html$/i',
|
|
798
|
+
'action' => 'view',
|
|
799
|
+
1 => 'slug'
|
|
800
|
+
],
|
|
801
|
+
// /myaddon/page/2 → action index с page
|
|
802
|
+
[
|
|
803
|
+
'pattern' => '/^page\\/(\\d+)$/i',
|
|
804
|
+
'action' => 'index',
|
|
805
|
+
1 => 'page'
|
|
806
|
+
],
|
|
807
|
+
// /myaddon/ (главная)
|
|
808
|
+
[
|
|
809
|
+
'pattern' => '/^$/',
|
|
810
|
+
'action' => 'index'
|
|
811
|
+
]
|
|
812
|
+
];
|
|
813
|
+
|
|
814
|
+
}`;
|
|
815
|
+
}
|