@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,341 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Content filtering system scaffolding tool for InstantCMS
|
|
4
|
+
* Generates filter classes, forms, hooks, and optional saved filters
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.FilterTypeEnum = void 0;
|
|
8
|
+
exports.scaffoldFilter = scaffoldFilter;
|
|
9
|
+
const zod_1 = require("zod");
|
|
10
|
+
const scaffold_1 = require("../types/scaffold");
|
|
11
|
+
/**
|
|
12
|
+
* Available filter field types
|
|
13
|
+
*/
|
|
14
|
+
exports.FilterTypeEnum = zod_1.z.enum([
|
|
15
|
+
'text',
|
|
16
|
+
'select',
|
|
17
|
+
'multiselect',
|
|
18
|
+
'checkbox',
|
|
19
|
+
'range',
|
|
20
|
+
'date',
|
|
21
|
+
'daterange',
|
|
22
|
+
]);
|
|
23
|
+
/**
|
|
24
|
+
* Normalizes filter field with defaults
|
|
25
|
+
*/
|
|
26
|
+
function normalizeField(f) {
|
|
27
|
+
return {
|
|
28
|
+
field: f.field,
|
|
29
|
+
type: f.type,
|
|
30
|
+
label: f.label || f.field,
|
|
31
|
+
options: f.options || [],
|
|
32
|
+
placeholder: f.placeholder || '',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Generates the main filter class
|
|
37
|
+
*/
|
|
38
|
+
function generateFilterClass(name, Name, fields) {
|
|
39
|
+
const filterInit = fields
|
|
40
|
+
.map(f => {
|
|
41
|
+
return ` '${f.field}' => [
|
|
42
|
+
'type' => '${f.type}',
|
|
43
|
+
'label' => '${f.label}',
|
|
44
|
+
'options' => [${(f.options || []).map(o => `'${o.value}' => '${o.label}'`).join(', ')}],
|
|
45
|
+
]`;
|
|
46
|
+
})
|
|
47
|
+
.join(',\n');
|
|
48
|
+
const applyConditions = fields
|
|
49
|
+
.map(f => {
|
|
50
|
+
switch (f.type) {
|
|
51
|
+
case 'text':
|
|
52
|
+
return ` if (!empty($filters['${f.field}'])) {
|
|
53
|
+
$where[] = "${f.field} LIKE '%" . db::escapeLike($filters['${f.field}']) . "%'";
|
|
54
|
+
}`;
|
|
55
|
+
case 'select':
|
|
56
|
+
return ` if (!empty($filters['${f.field}'])) {
|
|
57
|
+
$where[] = "${f.field} = '" . db::escape($filters['${f.field}']) . "'";
|
|
58
|
+
}`;
|
|
59
|
+
case 'multiselect':
|
|
60
|
+
return ` if (!empty($filters['${f.field}']) && is_array($filters['${f.field}'])) {
|
|
61
|
+
$ids = array_map('intval', $filters['${f.field}']);
|
|
62
|
+
$where[] = "${f.field} IN (" . implode(',', $ids) . ")";
|
|
63
|
+
}`;
|
|
64
|
+
case 'checkbox':
|
|
65
|
+
return ` if (isset($filters['${f.field}'])) {
|
|
66
|
+
$where[] = "${f.field} = " . (int)$filters['${f.field}'];
|
|
67
|
+
}`;
|
|
68
|
+
case 'range':
|
|
69
|
+
return ` if (!empty($filters['${f.field}_from'])) {
|
|
70
|
+
$where[] = "${f.field} >= " . (float)$filters['${f.field}_from'];
|
|
71
|
+
}
|
|
72
|
+
if (!empty($filters['${f.field}_to'])) {
|
|
73
|
+
$where[] = "${f.field} <= " . (float)$filters['${f.field}_to'];
|
|
74
|
+
}`;
|
|
75
|
+
case 'date':
|
|
76
|
+
return ` if (!empty($filters['${f.field}'])) {
|
|
77
|
+
$where[] = "DATE(${f.field}) = '" . db::escape($filters['${f.field}']) . "'";
|
|
78
|
+
}`;
|
|
79
|
+
case 'daterange':
|
|
80
|
+
return ` if (!empty($filters['${f.field}_from'])) {
|
|
81
|
+
$where[] = "${f.field} >= '" . db::escape($filters['${f.field}_from']) . "'";
|
|
82
|
+
}
|
|
83
|
+
if (!empty($filters['${f.field}_to'])) {
|
|
84
|
+
$where[] = "${f.field} <= '" . db::escape($filters['${f.field}_to']) . "'";
|
|
85
|
+
}`;
|
|
86
|
+
default:
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
.join('\n');
|
|
91
|
+
return `<?php
|
|
92
|
+
// InstantCMS 2. ${name}/filters.php
|
|
93
|
+
|
|
94
|
+
class ${Name}Filter {
|
|
95
|
+
private static $instance = null;
|
|
96
|
+
private $filters = [];
|
|
97
|
+
|
|
98
|
+
private function __construct() {
|
|
99
|
+
$this->filters = $this->getDefaultFilters();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public static function getInstance() {
|
|
103
|
+
if (self::$instance === null) {
|
|
104
|
+
self::$instance = new self();
|
|
105
|
+
}
|
|
106
|
+
return self::$instance;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public function getDefaultFilters() {
|
|
110
|
+
return [
|
|
111
|
+
${filterInit}
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public function apply($model, $filters = []) {
|
|
116
|
+
if (empty($filters)) {
|
|
117
|
+
return $model;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
$where = [];
|
|
121
|
+
${applyConditions}
|
|
122
|
+
|
|
123
|
+
if (!empty($where)) {
|
|
124
|
+
$model->where(implode(' AND ', $where));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return $model;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public function getFromRequest($request) {
|
|
131
|
+
$filters = [];
|
|
132
|
+
foreach ($this->filters as $field => $config) {
|
|
133
|
+
$value = $request->get($field);
|
|
134
|
+
if ($value !== null && $value !== '') {
|
|
135
|
+
$filters[$field] = $value;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return $filters;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public function validate($filters) {
|
|
142
|
+
$errors = [];
|
|
143
|
+
|
|
144
|
+
foreach ($filters as $field => $value) {
|
|
145
|
+
if (!isset($this->filters[$field])) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
$config = $this->filters[$field];
|
|
150
|
+
|
|
151
|
+
switch ($config['type']) {
|
|
152
|
+
case 'text':
|
|
153
|
+
if (strlen($value) > 255) {
|
|
154
|
+
$errors[$field] = 'Слишком длинное значение';
|
|
155
|
+
}
|
|
156
|
+
break;
|
|
157
|
+
case 'select':
|
|
158
|
+
if (!isset($config['options'][$value])) {
|
|
159
|
+
$errors[$field] = 'Недопустимое значение';
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
case 'multiselect':
|
|
163
|
+
if (!is_array($value)) {
|
|
164
|
+
$errors[$field] = 'Ожидался массив';
|
|
165
|
+
}
|
|
166
|
+
break;
|
|
167
|
+
case 'range':
|
|
168
|
+
case 'daterange':
|
|
169
|
+
if (!is_numeric($value) && !strtotime($value)) {
|
|
170
|
+
$errors[$field] = 'Недопустимое значение';
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return $errors;
|
|
177
|
+
}
|
|
178
|
+
}`;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Generates the filter form class
|
|
182
|
+
*/
|
|
183
|
+
function generateFilterForm(name, Name, fields) {
|
|
184
|
+
const formFields = fields
|
|
185
|
+
.map(f => {
|
|
186
|
+
const placeholder = f.placeholder ? `, 'placeholder' => '${f.placeholder}'` : '';
|
|
187
|
+
switch (f.type) {
|
|
188
|
+
case 'text':
|
|
189
|
+
return ` $form->addField('${f.field}', new fieldString('${f.label}', [${placeholder}]));`;
|
|
190
|
+
case 'select': {
|
|
191
|
+
const opts = (f.options || []).length > 0
|
|
192
|
+
? `['options' => [${(f.options || []).map(o => `'${o.value}' => '${o.label}'`).join(', ')}]]`
|
|
193
|
+
: '';
|
|
194
|
+
return ` $form->addField('${f.field}', new fieldList('${f.label}', ${opts}));`;
|
|
195
|
+
}
|
|
196
|
+
case 'multiselect': {
|
|
197
|
+
const multiOpts = (f.options || []).length > 0
|
|
198
|
+
? `['options' => [${(f.options || []).map(o => `'${o.value}' => '${o.label}'`).join(', ')}]]`
|
|
199
|
+
: '';
|
|
200
|
+
return ` $form->addField('${f.field}', new fieldList('${f.label}', ['is_multiple' => true${multiOpts ? ', ' + multiOpts : ''}]));`;
|
|
201
|
+
}
|
|
202
|
+
case 'checkbox':
|
|
203
|
+
return ` $form->addField('${f.field}', new fieldCheckbox('${f.label}'));`;
|
|
204
|
+
case 'range':
|
|
205
|
+
return ` $form->addField('${f.field}_from', new fieldNumber('${f.label} (от)'));
|
|
206
|
+
$form->addField('${f.field}_to', new fieldNumber('${f.label} (до)'));`;
|
|
207
|
+
case 'date':
|
|
208
|
+
return ` $form->addField('${f.field}', new fieldDate('${f.label}'));`;
|
|
209
|
+
case 'daterange':
|
|
210
|
+
return ` $form->addField('${f.field}_from', new fieldDate('${f.label} (от)'));
|
|
211
|
+
$form->addField('${f.field}_to', new fieldDate('${f.label} (до)'));`;
|
|
212
|
+
default:
|
|
213
|
+
return '';
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
.join('\n');
|
|
217
|
+
return `<?php
|
|
218
|
+
// InstantCMS 2. ${name}/filter.form.php
|
|
219
|
+
|
|
220
|
+
class ${Name}FilterForm {
|
|
221
|
+
public function __construct($form) {
|
|
222
|
+
${formFields}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
public static function create($form) {
|
|
226
|
+
return new self($form);
|
|
227
|
+
}
|
|
228
|
+
}`;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Generates hook handlers for filter integration
|
|
232
|
+
*/
|
|
233
|
+
function generateFilterHooks(name, Name, _fields) {
|
|
234
|
+
return `<?php
|
|
235
|
+
// InstantCMS 2. system/hooks/${name}/filter.hooks.php
|
|
236
|
+
|
|
237
|
+
class on${Name}FilterHook {
|
|
238
|
+
public function onBeforeLoadModel($model) {
|
|
239
|
+
$request = cmsCore::getInstance()->request;
|
|
240
|
+
$filters = ${Name}Filter::getInstance()->getFromRequest($request);
|
|
241
|
+
|
|
242
|
+
if (!empty($filters)) {
|
|
243
|
+
${Name}Filter::getInstance()->apply($model, $filters);
|
|
244
|
+
|
|
245
|
+
// Сохраняем в сессию
|
|
246
|
+
cmsUser::sessionPut('${name}_filters', $filters);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
public function onBeforeRender($controller) {
|
|
251
|
+
$filters = cmsUser::sessionGet('${name}_filters', []);
|
|
252
|
+
$controller->renderTemplate('filter', [
|
|
253
|
+
'filters' => ${Name}Filter::getInstance()->getDefaultFilters(),
|
|
254
|
+
'values' => $filters,
|
|
255
|
+
]);
|
|
256
|
+
}
|
|
257
|
+
}`;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Generates saved filters feature
|
|
261
|
+
*/
|
|
262
|
+
function generateSavedFilters(name, Name, _fields) {
|
|
263
|
+
return `<?php
|
|
264
|
+
// InstantCMS 2. ${name}/saved_filters.php
|
|
265
|
+
|
|
266
|
+
class ${Name}SavedFilters {
|
|
267
|
+
public static function save($user_id, $name, $filters) {
|
|
268
|
+
$model = cmsModel::getInstance();
|
|
269
|
+
|
|
270
|
+
$model->insert('saved_filters', [
|
|
271
|
+
'user_id' => $user_id,
|
|
272
|
+
'name' => $name,
|
|
273
|
+
'filters' => json_encode($filters),
|
|
274
|
+
'created_at' => date('Y-m-d H:i:s'),
|
|
275
|
+
]);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
public static function load($user_id, $filter_id) {
|
|
279
|
+
$model = cmsModel::getInstance();
|
|
280
|
+
return $model->getItemById('saved_filters', $filter_id, false, function ($item) {
|
|
281
|
+
$item['filters'] = json_decode($item['filters'], true);
|
|
282
|
+
return $item;
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
public static function getAll($user_id) {
|
|
287
|
+
$model = cmsModel::getInstance();
|
|
288
|
+
return $model->get('saved_filters', function ($item) {
|
|
289
|
+
$item['filters'] = json_decode($item['filters'], true);
|
|
290
|
+
return $item;
|
|
291
|
+
}, [
|
|
292
|
+
'user_id' => $user_id,
|
|
293
|
+
]);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
public static function delete($user_id, $filter_id) {
|
|
297
|
+
$model = cmsModel::getInstance();
|
|
298
|
+
return $model->delete('saved_filters', $filter_id, ['user_id' => $user_id]);
|
|
299
|
+
}
|
|
300
|
+
}`;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Generates a complete filtering system for an InstantCMS addon
|
|
304
|
+
*
|
|
305
|
+
* @param opts - Configuration options for the filter system
|
|
306
|
+
* @returns Object containing generated files and metadata
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```typescript
|
|
310
|
+
* const result = scaffoldFilter({
|
|
311
|
+
* addon_name: 'catalog',
|
|
312
|
+
* fields: [
|
|
313
|
+
* { field: 'price', type: 'range', label: 'Цена' },
|
|
314
|
+
* { field: 'category_id', type: 'select', label: 'Категория' }
|
|
315
|
+
* ],
|
|
316
|
+
* options: { use_ajax: true, save_filters: true }
|
|
317
|
+
* });
|
|
318
|
+
* ```
|
|
319
|
+
*/
|
|
320
|
+
function scaffoldFilter(opts) {
|
|
321
|
+
const { lowercase, UpperCamelCase } = (0, scaffold_1.normalizeAddonName)(opts.addon_name);
|
|
322
|
+
const files = {};
|
|
323
|
+
const filterFields = opts.fields.map(normalizeField);
|
|
324
|
+
files[`${lowercase}/filters.php`] = generateFilterClass(lowercase, UpperCamelCase, filterFields);
|
|
325
|
+
files[`${lowercase}/filter.form.php`] = generateFilterForm(lowercase, UpperCamelCase, filterFields);
|
|
326
|
+
files[`system/hooks/${lowercase}/filter.hooks.php`] = generateFilterHooks(lowercase, UpperCamelCase, filterFields);
|
|
327
|
+
if (opts.options?.save_filters) {
|
|
328
|
+
files[`${lowercase}/saved_filters.php`] = generateSavedFilters(lowercase, UpperCamelCase, filterFields);
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
addon_name: lowercase,
|
|
332
|
+
filters_count: filterFields.length,
|
|
333
|
+
options: opts.options || {},
|
|
334
|
+
files,
|
|
335
|
+
filters: filterFields.map(f => ({
|
|
336
|
+
field: f.field,
|
|
337
|
+
type: f.type,
|
|
338
|
+
label: f.label,
|
|
339
|
+
})),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scaffoldForm = scaffoldForm;
|
|
4
|
+
function scaffoldForm(opts) {
|
|
5
|
+
const name = opts.addon_name.toLowerCase().replace(/[^a-z0-9_]/g, '_');
|
|
6
|
+
const Name = name.split('_').map(capitalize).join('');
|
|
7
|
+
const formName = opts.form_name || 'item';
|
|
8
|
+
const FormName = formName.split('_').map(capitalize).join('');
|
|
9
|
+
const files = {};
|
|
10
|
+
const ctrl = `package/system/controllers/${name}`;
|
|
11
|
+
const formFile = `${ctrl}/backend/forms/form_${formName}.php`;
|
|
12
|
+
files[formFile] = generateForm(opts.fields, Name, FormName, opts.options);
|
|
13
|
+
files[`${ctrl}/manifest.xml`] = generateManifest(name, Name);
|
|
14
|
+
if (opts.options?.use_separate_save) {
|
|
15
|
+
files[`${ctrl}/backend/forms/form_${formName}_save.php`] = generateFormSave(Name, FormName);
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
addon_name: name,
|
|
19
|
+
form_name: formName,
|
|
20
|
+
form_class: `form${Name}${FormName}`,
|
|
21
|
+
file_path: formFile,
|
|
22
|
+
files,
|
|
23
|
+
form_fields: opts.fields.map(f => ({
|
|
24
|
+
name: f.name,
|
|
25
|
+
type: f.type,
|
|
26
|
+
class: getFieldClass(f.type),
|
|
27
|
+
})),
|
|
28
|
+
structure_notes: [
|
|
29
|
+
`Форма: ${formFile}`,
|
|
30
|
+
`Класс: form${Name}${FormName}`,
|
|
31
|
+
`Используйте в экшене: $this->getForm('${formName}')`,
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function capitalize(str) {
|
|
36
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
37
|
+
}
|
|
38
|
+
function generateForm(fields, Name, FormName, options) {
|
|
39
|
+
let formCode = `<?php
|
|
40
|
+
|
|
41
|
+
class form${Name}${FormName} extends cmsForm {
|
|
42
|
+
|
|
43
|
+
public function init($do = 'add') {
|
|
44
|
+
|
|
45
|
+
$is_edit = ($do === 'edit');
|
|
46
|
+
|
|
47
|
+
return [`;
|
|
48
|
+
if (options?.use_tabs) {
|
|
49
|
+
formCode += generateTabsForm(fields);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
formCode += generateBasicForm(fields);
|
|
53
|
+
}
|
|
54
|
+
formCode += `
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
`;
|
|
59
|
+
if (options?.use_separate_save) {
|
|
60
|
+
formCode += generateSaveMethod(Name, FormName);
|
|
61
|
+
}
|
|
62
|
+
formCode += `}
|
|
63
|
+
`;
|
|
64
|
+
return formCode;
|
|
65
|
+
}
|
|
66
|
+
function generateBasicForm(fields) {
|
|
67
|
+
const basicFields = fields.filter(f => !f.is_system);
|
|
68
|
+
const systemFields = fields.filter(f => f.is_system);
|
|
69
|
+
let code = `
|
|
70
|
+
'basic' => [
|
|
71
|
+
'title' => LANG_CP_BASIC,
|
|
72
|
+
'type' => 'fieldset',
|
|
73
|
+
'childs' => [`;
|
|
74
|
+
for (const field of basicFields) {
|
|
75
|
+
const fieldClass = getFieldClass(field.type);
|
|
76
|
+
const fieldTitle = field.title || capitalize(field.name.replace(/_/g, ' '));
|
|
77
|
+
let fieldOptions = { title: fieldTitle };
|
|
78
|
+
if (field.rules && field.rules.length > 0) {
|
|
79
|
+
fieldOptions.rules = field.rules.map(r => {
|
|
80
|
+
if (typeof r === 'string') {
|
|
81
|
+
const [rule, ...args] = r.split(':');
|
|
82
|
+
return [rule, ...args.map(a => (isNaN(Number(a)) ? a : Number(a)))];
|
|
83
|
+
}
|
|
84
|
+
return r;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else if (field.type === 'varchar') {
|
|
88
|
+
fieldOptions.rules = [['max_length', 255]];
|
|
89
|
+
}
|
|
90
|
+
if (field.options) {
|
|
91
|
+
fieldOptions = { ...fieldOptions, ...field.options };
|
|
92
|
+
}
|
|
93
|
+
const optionsStr = formatFieldOptions(fieldOptions);
|
|
94
|
+
code += `
|
|
95
|
+
new ${fieldClass}('${field.name}', ${optionsStr}),`;
|
|
96
|
+
}
|
|
97
|
+
code += `
|
|
98
|
+
],
|
|
99
|
+
],`;
|
|
100
|
+
if (systemFields.length > 0) {
|
|
101
|
+
code += `
|
|
102
|
+
'system' => [
|
|
103
|
+
'title' => LANG_SYSTEM,
|
|
104
|
+
'type' => 'fieldset',
|
|
105
|
+
'childs' => [`;
|
|
106
|
+
for (const field of systemFields) {
|
|
107
|
+
const fieldClass = getFieldClass(field.type);
|
|
108
|
+
const fieldTitle = field.title || capitalize(field.name.replace(/_/g, ' '));
|
|
109
|
+
const fieldOptions = formatFieldOptions({ title: fieldTitle, ...field.options });
|
|
110
|
+
code += `
|
|
111
|
+
new ${fieldClass}('${field.name}', ${fieldOptions}),`;
|
|
112
|
+
}
|
|
113
|
+
code += `
|
|
114
|
+
],
|
|
115
|
+
],`;
|
|
116
|
+
}
|
|
117
|
+
return code;
|
|
118
|
+
}
|
|
119
|
+
function generateTabsForm(fields) {
|
|
120
|
+
const tabGroups = {};
|
|
121
|
+
for (const field of fields) {
|
|
122
|
+
const group = field.options?.tab || 'basic';
|
|
123
|
+
if (!tabGroups[group]) {
|
|
124
|
+
tabGroups[group] = [];
|
|
125
|
+
}
|
|
126
|
+
tabGroups[group].push(field);
|
|
127
|
+
}
|
|
128
|
+
let code = '';
|
|
129
|
+
for (const [tabName, tabFields] of Object.entries(tabGroups)) {
|
|
130
|
+
const tabTitle = tabName === 'basic' ? LANG_CP_BASIC : capitalize(tabName.replace(/_/g, ' '));
|
|
131
|
+
code += `
|
|
132
|
+
'${tabName}' => [
|
|
133
|
+
'title' => '${tabTitle}',
|
|
134
|
+
'type' => 'fieldset',
|
|
135
|
+
'childs' => [`;
|
|
136
|
+
for (const field of tabFields) {
|
|
137
|
+
const fieldClass = getFieldClass(field.type);
|
|
138
|
+
const fieldTitle = field.title || capitalize(field.name.replace(/_/g, ' '));
|
|
139
|
+
let fieldOptions = { title: fieldTitle };
|
|
140
|
+
if (field.rules && field.rules.length > 0) {
|
|
141
|
+
fieldOptions.rules = field.rules;
|
|
142
|
+
}
|
|
143
|
+
else if (field.type === 'varchar') {
|
|
144
|
+
fieldOptions.rules = [['max_length', 255]];
|
|
145
|
+
}
|
|
146
|
+
if (field.options) {
|
|
147
|
+
fieldOptions = { ...fieldOptions, ...field.options };
|
|
148
|
+
}
|
|
149
|
+
const optionsStr = formatFieldOptions(fieldOptions);
|
|
150
|
+
code += `
|
|
151
|
+
new ${fieldClass}('${field.name}', ${optionsStr}),`;
|
|
152
|
+
}
|
|
153
|
+
code += `
|
|
154
|
+
],
|
|
155
|
+
],`;
|
|
156
|
+
}
|
|
157
|
+
return code;
|
|
158
|
+
}
|
|
159
|
+
function generateSaveMethod(Name, _FormName) {
|
|
160
|
+
return `
|
|
161
|
+
|
|
162
|
+
public function save($item, $do, $is_update) {
|
|
163
|
+
|
|
164
|
+
if ($is_update) {
|
|
165
|
+
cmsEventsManager::hook('${Name.toLowerCase()}_before_update', $item, $this->request);
|
|
166
|
+
} else {
|
|
167
|
+
cmsEventsManager::hook('${Name.toLowerCase()}_before_add', $item, $this->request);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return $item;
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
function generateFormSave(Name, FormName) {
|
|
175
|
+
return `<?php
|
|
176
|
+
|
|
177
|
+
class form${Name}${FormName}Save extends cmsForm {
|
|
178
|
+
|
|
179
|
+
public function init() {
|
|
180
|
+
return [
|
|
181
|
+
'basic' => [
|
|
182
|
+
'title' => LANG_CP_BASIC,
|
|
183
|
+
'type' => 'fieldset',
|
|
184
|
+
'childs' => [
|
|
185
|
+
new fieldCheckbox('is_approved', [
|
|
186
|
+
'title' => LANG_APPROVED,
|
|
187
|
+
'default' => 1,
|
|
188
|
+
]),
|
|
189
|
+
new fieldNumber('sorder', [
|
|
190
|
+
'title' => LANG_SORT_ORDER,
|
|
191
|
+
'default' => 0,
|
|
192
|
+
]),
|
|
193
|
+
],
|
|
194
|
+
],
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
}`;
|
|
199
|
+
}
|
|
200
|
+
function generateManifest(name, Name) {
|
|
201
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
202
|
+
<addon>
|
|
203
|
+
<name>${name}</name>
|
|
204
|
+
<title>${Name}</title>
|
|
205
|
+
<version>1.0.0</version>
|
|
206
|
+
<files>
|
|
207
|
+
<file>backend/forms/form_item.php</file>
|
|
208
|
+
</files>
|
|
209
|
+
</addon>`;
|
|
210
|
+
}
|
|
211
|
+
function getFieldClass(type) {
|
|
212
|
+
const map = {
|
|
213
|
+
varchar: 'fieldString',
|
|
214
|
+
text: 'fieldText',
|
|
215
|
+
html: 'fieldHtml',
|
|
216
|
+
int: 'fieldNumber',
|
|
217
|
+
tinyint: 'fieldNumber',
|
|
218
|
+
decimal: 'fieldNumber',
|
|
219
|
+
float: 'fieldNumber',
|
|
220
|
+
bool: 'fieldCheckbox',
|
|
221
|
+
boolean: 'fieldCheckbox',
|
|
222
|
+
date: 'fieldDate',
|
|
223
|
+
datetime: 'fieldDate',
|
|
224
|
+
timestamp: 'fieldDate',
|
|
225
|
+
time: 'fieldTime',
|
|
226
|
+
select: 'fieldList',
|
|
227
|
+
enum: 'fieldList',
|
|
228
|
+
radio: 'fieldRadio',
|
|
229
|
+
checkbox: 'fieldCheckbox',
|
|
230
|
+
file: 'fieldFile',
|
|
231
|
+
image: 'fieldImage',
|
|
232
|
+
images: 'fieldImages',
|
|
233
|
+
video: 'fieldVideo',
|
|
234
|
+
audio: 'fieldAudio',
|
|
235
|
+
user: 'fieldUser',
|
|
236
|
+
users: 'fieldUsers',
|
|
237
|
+
parent: 'fieldParent',
|
|
238
|
+
slug: 'fieldSlug',
|
|
239
|
+
tags: 'fieldTags',
|
|
240
|
+
price: 'fieldPrice',
|
|
241
|
+
theme: 'fieldTheme',
|
|
242
|
+
};
|
|
243
|
+
return map[type] || 'fieldString';
|
|
244
|
+
}
|
|
245
|
+
function formatFieldOptions(options) {
|
|
246
|
+
const lines = [];
|
|
247
|
+
for (const [key, value] of Object.entries(options)) {
|
|
248
|
+
if (value === undefined) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (typeof value === 'string') {
|
|
252
|
+
lines.push(`'${key}' => '${value}'`);
|
|
253
|
+
}
|
|
254
|
+
else if (typeof value === 'boolean') {
|
|
255
|
+
lines.push(`'${key}' => ${value}`);
|
|
256
|
+
}
|
|
257
|
+
else if (typeof value === 'number') {
|
|
258
|
+
lines.push(`'${key}' => ${value}`);
|
|
259
|
+
}
|
|
260
|
+
else if (Array.isArray(value)) {
|
|
261
|
+
lines.push(`'${key}' => ${JSON.stringify(value)}`);
|
|
262
|
+
}
|
|
263
|
+
else if (typeof value === 'object' && value !== null) {
|
|
264
|
+
lines.push(`'${key}' => ${JSON.stringify(value)}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (lines.length === 0) {
|
|
268
|
+
return '{}';
|
|
269
|
+
}
|
|
270
|
+
return `[\n ${lines.join(',\n ')}\n ]`;
|
|
271
|
+
}
|
|
272
|
+
const LANG_CP_BASIC = 'LANG_CP_BASIC';
|
|
273
|
+
const LANG_SYSTEM = 'LANG_SYSTEM'; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
274
|
+
const LANG_APPROVED = 'LANG_APPROVED'; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
275
|
+
const LANG_SORT_ORDER = 'LANG_SORT_ORDER'; // eslint-disable-line @typescript-eslint/no-unused-vars
|