n8n-nodes-oxsr-technical-utils 3.4.0 → 3.4.2

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.
@@ -58,13 +58,20 @@ function buildRestUrls(base, namespace, endpoint) {
58
58
  `${normalizedBase}/index.php?rest_route=${encodedRoute}`,
59
59
  ];
60
60
  }
61
+ function buildBaseCandidates(base) {
62
+ const normalized = base.replace(/\/+$/g, '');
63
+ const out = [normalized];
64
+ if (/^http:\/\//i.test(normalized)) {
65
+ out.push(normalized.replace(/^http:\/\//i, 'https://'));
66
+ }
67
+ return Array.from(new Set(out));
68
+ }
61
69
  const AUTO_RESOURCE_VALUES = [
62
70
  'siteHealth',
63
71
  'users',
64
72
  'database',
65
73
  'logs',
66
74
  'metrics',
67
- 'theme',
68
75
  'content',
69
76
  'security',
70
77
  'performance',
@@ -84,7 +91,6 @@ const AUTO_OPERATIONS_BY_RESOURCE = {
84
91
  database: ['getDbSize', 'getAutoload'],
85
92
  logs: ['getErrorLog'],
86
93
  metrics: ['getMetricsOverview', 'getTopContentMetrics'],
87
- theme: ['getThemeCustomization', 'updateThemeCustomization'],
88
94
  content: [
89
95
  'getPosts',
90
96
  'getOldDrafts',
@@ -97,6 +103,11 @@ const AUTO_OPERATIONS_BY_RESOURCE = {
97
103
  'deleteOrphanMedia',
98
104
  'getContentSource',
99
105
  'updateContentSource',
106
+ 'getMenus',
107
+ 'createMenu',
108
+ 'assignMenu',
109
+ 'addMenuLink',
110
+ 'addMenuCategories',
100
111
  ],
101
112
  security: [
102
113
  'getFileChecksums',
@@ -125,6 +136,9 @@ const AUTO_OPERATIONS_BY_RESOURCE = {
125
136
  'getComments',
126
137
  'moderateComment',
127
138
  'deleteComment',
139
+ 'getRedirects',
140
+ 'createRedirect',
141
+ 'deleteRedirect',
128
142
  ],
129
143
  updates: ['getAvailableUpdates', 'runPluginUpdate', 'runAllPluginsUpdate', 'runCoreUpdate', 'getUpdatesHistory'],
130
144
  dbAdvanced: ['optimizeDb', 'repairDb', 'getOrphanedMeta', 'cleanOrphanedMeta', 'runSelectQuery'],
@@ -160,65 +174,80 @@ async function callPlugin(siteUrl, apiKey, method, endpoint, params, body) {
160
174
  throw new Error('El sitio no es accesible o la URL es incorrecta');
161
175
  }
162
176
  const namespaces = ['oxsr-monitor/v1', 'oxsr/v1'];
177
+ const baseCandidates = buildBaseCandidates(base);
163
178
  let lastNoRouteMessage = '';
164
179
  const controller = new AbortController();
165
180
  const timeout = setTimeout(() => controller.abort(), 15000);
166
181
  try {
167
- for (let i = 0; i < namespaces.length; i++) {
168
- const namespace = namespaces[i];
169
- const restUrls = buildRestUrls(base, namespace, ep);
170
- for (let j = 0; j < restUrls.length; j++) {
171
- const rawUrl = restUrls[j];
172
- const url = new URL(rawUrl);
173
- if ((method === 'GET' || method === 'DELETE') && params) {
174
- for (const [k, v] of Object.entries(params)) {
175
- if (v === undefined || v === null || v === '')
182
+ for (let b = 0; b < baseCandidates.length; b++) {
183
+ const baseCandidate = baseCandidates[b];
184
+ for (let i = 0; i < namespaces.length; i++) {
185
+ const namespace = namespaces[i];
186
+ const restUrls = buildRestUrls(baseCandidate, namespace, ep);
187
+ for (let j = 0; j < restUrls.length; j++) {
188
+ const rawUrl = restUrls[j];
189
+ const url = new URL(rawUrl);
190
+ if ((method === 'GET' || method === 'DELETE') && params) {
191
+ for (const [k, v] of Object.entries(params)) {
192
+ if (v === undefined || v === null || v === '')
193
+ continue;
194
+ url.searchParams.set(k, String(v));
195
+ }
196
+ }
197
+ const response = await fetch(url.toString(), {
198
+ method,
199
+ headers: {
200
+ 'X-OXSR-Key': apiKey,
201
+ 'Content-Type': 'application/json',
202
+ },
203
+ body: method === 'POST' || method === 'DELETE' ? JSON.stringify(body || {}) : undefined,
204
+ signal: controller.signal,
205
+ });
206
+ const text = await response.text();
207
+ let json = null;
208
+ try {
209
+ json = text ? JSON.parse(text) : null;
210
+ }
211
+ catch {
212
+ json = null;
213
+ }
214
+ if (!response.ok) {
215
+ const pluginMsg = (typeof (json === null || json === void 0 ? void 0 : json.message) === 'string' && json.message) ||
216
+ (typeof ((_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.message) === 'string' && json.data.message) ||
217
+ (text || '').trim() ||
218
+ `HTTP ${response.status}`;
219
+ if (response.status === 401)
220
+ throw new Error('API Key inválida o no configurada en el plugin');
221
+ if (response.status === 429)
222
+ throw new Error('Rate limit alcanzado, espera 60 segundos');
223
+ if (response.status === 403) {
224
+ const normalizedForbidden = pluginMsg.toLowerCase();
225
+ const requiresHttps = normalizedForbidden.includes('https');
226
+ const hasMoreBases = b < baseCandidates.length - 1;
227
+ if (requiresHttps && hasMoreBases) {
228
+ continue;
229
+ }
230
+ if (requiresHttps) {
231
+ throw new Error('El sitio requiere HTTPS. Configura la credencial Site URL con https://');
232
+ }
233
+ throw new Error(`Acceso denegado (403): ${pluginMsg}`);
234
+ }
235
+ const isNoRoute = isNoRouteResponse(response.status, json, text);
236
+ const hasMoreUrls = j < restUrls.length - 1;
237
+ const hasMoreNamespaces = i < namespaces.length - 1;
238
+ const hasMoreBases = b < baseCandidates.length - 1;
239
+ if (isNoRoute && (hasMoreUrls || hasMoreNamespaces || hasMoreBases)) {
240
+ lastNoRouteMessage = pluginMsg;
176
241
  continue;
177
- url.searchParams.set(k, String(v));
242
+ }
243
+ throw new Error(pluginMsg);
178
244
  }
245
+ return json !== null && json !== void 0 ? json : { success: true };
179
246
  }
180
- const response = await fetch(url.toString(), {
181
- method,
182
- headers: {
183
- 'X-OXSR-Key': apiKey,
184
- 'Content-Type': 'application/json',
185
- },
186
- body: method === 'POST' || method === 'DELETE' ? JSON.stringify(body || {}) : undefined,
187
- signal: controller.signal,
188
- });
189
- const text = await response.text();
190
- let json = null;
191
- try {
192
- json = text ? JSON.parse(text) : null;
193
- }
194
- catch {
195
- json = null;
196
- }
197
- if (!response.ok) {
198
- const pluginMsg = (typeof (json === null || json === void 0 ? void 0 : json.message) === 'string' && json.message) ||
199
- (typeof ((_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.message) === 'string' && json.data.message) ||
200
- (text || '').trim() ||
201
- `HTTP ${response.status}`;
202
- if (response.status === 401)
203
- throw new Error('API Key inválida o no configurada en el plugin');
204
- if (response.status === 429)
205
- throw new Error('Rate limit alcanzado, espera 60 segundos');
206
- if (response.status === 403)
207
- throw new Error('El sitio requiere HTTPS o acceso denegado');
208
- const isNoRoute = isNoRouteResponse(response.status, json, text);
209
- const hasMoreUrls = j < restUrls.length - 1;
210
- const hasMoreNamespaces = i < namespaces.length - 1;
211
- if (isNoRoute && (hasMoreUrls || hasMoreNamespaces)) {
212
- lastNoRouteMessage = pluginMsg;
247
+ if (i < namespaces.length - 1) {
248
+ if (lastNoRouteMessage) {
213
249
  continue;
214
250
  }
215
- throw new Error(pluginMsg);
216
- }
217
- return json !== null && json !== void 0 ? json : { success: true };
218
- }
219
- if (i < namespaces.length - 1) {
220
- if (lastNoRouteMessage) {
221
- continue;
222
251
  }
223
252
  }
224
253
  }
@@ -276,7 +305,6 @@ class OXSRTechnicalUtils {
276
305
  { name: 'Database', value: 'database', description: 'Tamaño de base de datos y autoload' },
277
306
  { name: 'Logs & Errors', value: 'logs', description: 'Errores PHP y análisis de log' },
278
307
  { name: 'Metrics', value: 'metrics', description: 'Visitas, interacciones y rendimiento de contenido' },
279
- { name: 'Tema', value: 'theme', description: 'Personalización visual del tema: colores, fuentes y layout' },
280
308
  { name: 'Content', value: 'content', description: 'Posts, borradores y ALT faltante' },
281
309
  { name: 'Security', value: 'security', description: 'Checksums y configuración de registro' },
282
310
  { name: 'Performance', value: 'performance', description: 'Cron y revisiones' },
@@ -315,7 +343,6 @@ class OXSRTechnicalUtils {
315
343
  { name: 'Database', value: 'database' },
316
344
  { name: 'Logs & Errors', value: 'logs' },
317
345
  { name: 'Metrics', value: 'metrics' },
318
- { name: 'Tema', value: 'theme' },
319
346
  { name: 'Content', value: 'content' },
320
347
  { name: 'Security', value: 'security' },
321
348
  { name: 'Performance', value: 'performance' },
@@ -404,20 +431,6 @@ class OXSRTechnicalUtils {
404
431
  { name: 'Get Top Content Metrics', value: 'getTopContentMetrics' },
405
432
  ],
406
433
  },
407
- {
408
- displayName: 'AI Operation',
409
- name: 'autoOperation',
410
- type: 'options',
411
- noDataExpression: false,
412
- displayOptions: { show: { resource: ['auto'], operation: ['autoDecide'], autoResource: ['theme'] } },
413
- default: 'getThemeCustomization',
414
- required: true,
415
- description: 'Operación automática para Tema',
416
- options: [
417
- { name: 'Get Theme Customization', value: 'getThemeCustomization' },
418
- { name: 'Update Theme Customization', value: 'updateThemeCustomization' },
419
- ],
420
- },
421
434
  {
422
435
  displayName: 'AI Operation',
423
436
  name: 'autoOperation',
@@ -439,6 +452,11 @@ class OXSRTechnicalUtils {
439
452
  { name: 'Delete Orphan Media', value: 'deleteOrphanMedia' },
440
453
  { name: 'Get Source (Raw)', value: 'getContentSource' },
441
454
  { name: 'Update Source (Raw)', value: 'updateContentSource' },
455
+ { name: 'Get Menus', value: 'getMenus' },
456
+ { name: 'Create Menu', value: 'createMenu' },
457
+ { name: 'Assign Menu', value: 'assignMenu' },
458
+ { name: 'Add Menu Link', value: 'addMenuLink' },
459
+ { name: 'Add Menu Categories', value: 'addMenuCategories' },
442
460
  ],
443
461
  },
444
462
  {
@@ -530,6 +548,9 @@ class OXSRTechnicalUtils {
530
548
  { name: 'Get Comments', value: 'getComments' },
531
549
  { name: 'Moderate Comment', value: 'moderateComment' },
532
550
  { name: 'Delete Comment', value: 'deleteComment' },
551
+ { name: 'Get Redirects', value: 'getRedirects' },
552
+ { name: 'Create Redirect', value: 'createRedirect' },
553
+ { name: 'Delete Redirect', value: 'deleteRedirect' },
533
554
  ],
534
555
  },
535
556
  {
@@ -641,7 +662,6 @@ class OXSRTechnicalUtils {
641
662
  'database',
642
663
  'logs',
643
664
  'metrics',
644
- 'theme',
645
665
  'content',
646
666
  'security',
647
667
  'performance',
@@ -883,6 +903,69 @@ class OXSRTechnicalUtils {
883
903
  required: true,
884
904
  description: 'ID del post o página para leer o actualizar su código fuente',
885
905
  },
906
+ {
907
+ displayName: 'Menu ID',
908
+ name: 'menuId',
909
+ type: 'number',
910
+ displayOptions: { show: { resource: ['content'], operation: ['assignMenu', 'addMenuLink', 'addMenuCategories'] } },
911
+ default: 0,
912
+ required: true,
913
+ description: 'ID numérico del menú',
914
+ },
915
+ {
916
+ displayName: 'Menu Name',
917
+ name: 'menuName',
918
+ type: 'string',
919
+ displayOptions: { show: { resource: ['content'], operation: ['createMenu'] } },
920
+ default: '',
921
+ required: true,
922
+ description: 'Nombre del menú a crear',
923
+ },
924
+ {
925
+ displayName: 'Menu Location',
926
+ name: 'menuLocation',
927
+ type: 'string',
928
+ displayOptions: { show: { resource: ['content'], operation: ['createMenu', 'assignMenu'] } },
929
+ default: '',
930
+ required: false,
931
+ description: 'Ubicación del tema para asignar el menú',
932
+ },
933
+ {
934
+ displayName: 'Menu Item Title',
935
+ name: 'menuItemTitle',
936
+ type: 'string',
937
+ displayOptions: { show: { resource: ['content'], operation: ['addMenuLink'] } },
938
+ default: '',
939
+ required: true,
940
+ description: 'Texto visible del enlace de menú',
941
+ },
942
+ {
943
+ displayName: 'Menu Item URL',
944
+ name: 'menuItemUrl',
945
+ type: 'string',
946
+ displayOptions: { show: { resource: ['content'], operation: ['addMenuLink'] } },
947
+ default: '',
948
+ required: true,
949
+ description: 'URL destino del enlace de menú',
950
+ },
951
+ {
952
+ displayName: 'Menu Parent Item ID',
953
+ name: 'menuParentItemId',
954
+ type: 'number',
955
+ displayOptions: { show: { resource: ['content'], operation: ['addMenuLink', 'addMenuCategories'] } },
956
+ default: 0,
957
+ required: false,
958
+ description: 'ID del item padre en el menú (0 para raíz)',
959
+ },
960
+ {
961
+ displayName: 'Menu Category IDs CSV',
962
+ name: 'menuCategoryIdsCsv',
963
+ type: 'string',
964
+ displayOptions: { show: { resource: ['content'], operation: ['addMenuCategories'] } },
965
+ default: '',
966
+ required: true,
967
+ description: 'IDs de categorías separados por coma para añadir al menú',
968
+ },
886
969
  {
887
970
  displayName: 'Title',
888
971
  name: 'title',
@@ -976,58 +1059,6 @@ class OXSRTechnicalUtils {
976
1059
  required: false,
977
1060
  description: 'Genera SEO title/meta description automáticamente si están vacíos',
978
1061
  },
979
- {
980
- displayName: 'Theme Primary Color',
981
- name: 'themePrimaryColor',
982
- type: 'string',
983
- displayOptions: { show: { resource: ['theme'], operation: ['updateThemeCustomization'] } },
984
- default: '',
985
- required: false,
986
- description: 'Color principal del tema en formato HEX',
987
- },
988
- {
989
- displayName: 'Theme Secondary Color',
990
- name: 'themeSecondaryColor',
991
- type: 'string',
992
- displayOptions: { show: { resource: ['theme'], operation: ['updateThemeCustomization'] } },
993
- default: '',
994
- required: false,
995
- description: 'Color secundario del tema en formato HEX',
996
- },
997
- {
998
- displayName: 'Theme Body Font',
999
- name: 'themeBodyFont',
1000
- type: 'string',
1001
- displayOptions: { show: { resource: ['theme'], operation: ['updateThemeCustomization'] } },
1002
- default: '',
1003
- required: false,
1004
- description: 'Fuente principal de texto del tema',
1005
- },
1006
- {
1007
- displayName: 'Theme Heading Font',
1008
- name: 'themeHeadingFont',
1009
- type: 'string',
1010
- displayOptions: { show: { resource: ['theme'], operation: ['updateThemeCustomization'] } },
1011
- default: '',
1012
- required: false,
1013
- description: 'Fuente para títulos y encabezados',
1014
- },
1015
- {
1016
- displayName: 'Theme Layout Preset',
1017
- name: 'themeLayoutPreset',
1018
- type: 'options',
1019
- displayOptions: { show: { resource: ['theme'], operation: ['updateThemeCustomization'] } },
1020
- default: '',
1021
- required: false,
1022
- options: [
1023
- { name: 'No Change', value: '' },
1024
- { name: 'Classic', value: 'classic' },
1025
- { name: 'Boxed', value: 'boxed' },
1026
- { name: 'Fullwidth', value: 'fullwidth' },
1027
- { name: 'Magazine', value: 'magazine' },
1028
- ],
1029
- description: 'Preset de layout global del tema',
1030
- },
1031
1062
  {
1032
1063
  displayName: 'Status (Write)',
1033
1064
  name: 'statusWrite',
@@ -1264,6 +1295,48 @@ class OXSRTechnicalUtils {
1264
1295
  required: false,
1265
1296
  description: 'Si se activa, elimina permanentemente el comentario',
1266
1297
  },
1298
+ {
1299
+ displayName: 'Redirect ID',
1300
+ name: 'redirectId',
1301
+ type: 'string',
1302
+ displayOptions: { show: { resource: ['settings'], operation: ['deleteRedirect'] } },
1303
+ default: '',
1304
+ required: true,
1305
+ description: 'Identificador único de la redirección',
1306
+ },
1307
+ {
1308
+ displayName: 'Redirect Source URL',
1309
+ name: 'redirectSourceUrl',
1310
+ type: 'string',
1311
+ displayOptions: { show: { resource: ['settings'], operation: ['createRedirect'] } },
1312
+ default: '',
1313
+ required: true,
1314
+ description: 'URL origen o ruta origen a redirigir',
1315
+ },
1316
+ {
1317
+ displayName: 'Redirect Target URL',
1318
+ name: 'redirectTargetUrl',
1319
+ type: 'string',
1320
+ displayOptions: { show: { resource: ['settings'], operation: ['createRedirect'] } },
1321
+ default: '',
1322
+ required: true,
1323
+ description: 'URL destino de la redirección',
1324
+ },
1325
+ {
1326
+ displayName: 'Redirect Status Code',
1327
+ name: 'redirectStatusCode',
1328
+ type: 'options',
1329
+ displayOptions: { show: { resource: ['settings'], operation: ['createRedirect'] } },
1330
+ default: 301,
1331
+ required: false,
1332
+ options: [
1333
+ { name: '301 Permanent', value: 301 },
1334
+ { name: '302 Temporary', value: 302 },
1335
+ { name: '307 Temporary', value: 307 },
1336
+ { name: '308 Permanent', value: 308 },
1337
+ ],
1338
+ description: 'Código HTTP de la redirección',
1339
+ },
1267
1340
  {
1268
1341
  displayName: 'Cron Hook',
1269
1342
  name: 'cronHook',
@@ -1569,7 +1642,7 @@ class OXSRTechnicalUtils {
1569
1642
  else if (resource === 'actions' || resource === 'webhooks') {
1570
1643
  json = (await (0, actions_1.execute)(context, { siteUrl, apiKey }));
1571
1644
  }
1572
- else if (resource === 'settings' || resource === 'theme') {
1645
+ else if (resource === 'settings') {
1573
1646
  json = (await (0, settings_1.execute)(context, { siteUrl, apiKey }));
1574
1647
  }
1575
1648
  else if (resource === 'updates') {