@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,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