@reldens/cms 0.21.0 → 0.23.0

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.
@@ -101,6 +101,10 @@
101
101
  <input type="checkbox" name="install-entity-access" id="install-entity-access" checked/>
102
102
  <label for="install-entity-access">Install entity access control rules</label>
103
103
  </div>
104
+ <div class="input-box input-checkbox install-dynamic-forms">
105
+ <input type="checkbox" name="install-dynamic-forms" id="install-dynamic-forms" checked/>
106
+ <label for="install-dynamic-forms">Install dynamic forms system</label>
107
+ </div>
104
108
  <div class="input-box submit-container">
105
109
  <input id="install-submit-button" type="submit" value="Install"/>
106
110
  <img class="install-loading hidden" src="/install-assets/img/loading.gif"/>
@@ -461,7 +461,7 @@ class RouterContents
461
461
  }
462
462
  }
463
463
  return await this.adminContentsRender(
464
- this.adminFilesContents.fields.view[this.propertyType(resourceProperty, templateType)],
464
+ this.adminFilesContents.fields.view[this.propertyType(resourceProperty, templateType, fieldValue)],
465
465
  {fieldName, fieldValue, fieldOriginalValue, target: ' target="_blank"'}
466
466
  );
467
467
  }
@@ -469,7 +469,7 @@ class RouterContents
469
469
  generatePropertyRenderedValueWithLabel(entity, propertyKey, resourceProperty)
470
470
  {
471
471
  let fieldValue = (0 === entity[propertyKey] ? '0' : entity[propertyKey] || '');
472
- if(sc.isObject(fieldValue) && 'json' === resourceProperty.dbType){
472
+ if((sc.isObject(fieldValue) || sc.isArray(fieldValue)) && 'json' === resourceProperty.dbType){
473
473
  fieldValue = sc.toJsonString(fieldValue);
474
474
  }
475
475
  let fieldName = propertyKey;
@@ -512,7 +512,7 @@ class RouterContents
512
512
  if('null' === fieldValue){
513
513
  fieldValue = '';
514
514
  }
515
- if(sc.isObject(fieldValue) && 'json' === resourceProperty.dbType){
515
+ if((sc.isObject(fieldValue) || sc.isArray(fieldValue)) && 'json' === resourceProperty.dbType){
516
516
  fieldValue = sc.toJsonString(fieldValue);
517
517
  }
518
518
  if('boolean' === resourceProperty.type){
@@ -562,7 +562,7 @@ class RouterContents
562
562
  return await relationEntityRepository.loadAll();
563
563
  }
564
564
 
565
- propertyType(resourceProperty, templateType)
565
+ propertyType(resourceProperty, templateType, fieldValue)
566
566
  {
567
567
  let propertyType = sc.get(resourceProperty, 'type', 'text');
568
568
  if('reference' === propertyType && 'edit' === templateType){
@@ -573,11 +573,18 @@ class RouterContents
573
573
  return 'file';
574
574
  }
575
575
  if('view' === templateType){
576
+ if(!fieldValue || '' === fieldValue){
577
+ return 'text';
578
+ }
576
579
  let multiple = resourceProperty.isArray ? 's' : '';
577
- if('image' === resourceProperty.allowedTypes){
578
- return resourceProperty.allowedTypes + multiple;
580
+ let allowedTypes = sc.get(resourceProperty, 'allowedTypes', '');
581
+ if('' !== allowedTypes){
582
+ let templateName = allowedTypes + multiple;
583
+ if(sc.hasOwn(this.adminFilesContents.fields.view, templateName)){
584
+ return templateName;
585
+ }
579
586
  }
580
- if('text' === resourceProperty.allowedTypes){
587
+ if('text' === allowedTypes){
581
588
  return 'link'+multiple
582
589
  }
583
590
  return 'text';
@@ -0,0 +1,228 @@
1
+ /**
2
+ *
3
+ * Reldens - CMS - DynamicFormRenderer
4
+ *
5
+ */
6
+
7
+ const { Logger, sc } = require('@reldens/utils');
8
+ const { FileHandler } = require('@reldens/server-utils');
9
+
10
+ class DynamicFormRenderer
11
+ {
12
+
13
+ constructor(props)
14
+ {
15
+ this.renderEngine = sc.get(props, 'renderEngine', false);
16
+ this.getPartials = sc.get(props, 'getPartials', false);
17
+ this.projectRoot = sc.get(props, 'projectRoot', './');
18
+ this.templatesPath = FileHandler.joinPaths(this.projectRoot, 'templates');
19
+ this.defaultDomain = sc.get(props, 'defaultDomain', 'default');
20
+ this.loadedTemplates = {};
21
+ this.events = sc.get(props, 'events', false);
22
+ if(!this.events){
23
+ Logger.error('EventsManager not provided to DynamicFormRenderer - forms functionality disabled');
24
+ this.isDisabled = true;
25
+ }
26
+ }
27
+
28
+ async renderForm(formConfig, fieldsToRender, domain, req, attributes = {}, systemVariables = {}, enhancedData = {})
29
+ {
30
+ if(this.isDisabled){
31
+ return '';
32
+ }
33
+ await this.events.emit('reldens.dynamicFormRenderer.beforeFieldsRender', {
34
+ formConfig,
35
+ fieldsToRender,
36
+ domain,
37
+ req,
38
+ attributes,
39
+ systemVariables,
40
+ enhancedData
41
+ });
42
+ let formFields = await this.renderFormFields(fieldsToRender, domain, req);
43
+ await this.events.emit('reldens.dynamicFormRenderer.afterFieldsRender', {
44
+ formConfig,
45
+ fieldsToRender,
46
+ formFields,
47
+ domain,
48
+ req,
49
+ attributes,
50
+ systemVariables,
51
+ enhancedData
52
+ });
53
+ let formTemplate = await this.loadFormTemplate('form', domain);
54
+ if(!formTemplate){
55
+ Logger.error('Form template not found');
56
+ return '';
57
+ }
58
+ let messageData = this.parseFormMessages(req, attributes);
59
+ return this.renderEngine.render(formTemplate, Object.assign({}, enhancedData, {
60
+ formKey: formConfig.form_key,
61
+ formFields,
62
+ submitUrl: sc.get(attributes, 'submitUrl', '/dynamic-form'),
63
+ successRedirect: sc.get(attributes, 'successRedirect', '/'),
64
+ errorRedirect: sc.get(attributes, 'errorRedirect', '/'),
65
+ honeypotFieldName: sc.get(attributes, 'honeypotFieldName', 'website_url'),
66
+ submitButtonText: sc.get(attributes, 'submitButtonText', 'Submit'),
67
+ cssClass: sc.get(attributes, 'cssClass', 'dynamic-form'),
68
+ showSuccessMessage: messageData.showSuccessMessage,
69
+ successMessage: messageData.successMessage,
70
+ showErrorMessage: messageData.showErrorMessage,
71
+ errorMessage: messageData.errorMessage,
72
+ systemVariables
73
+ }), this.getPartialsForDomain(domain));
74
+ }
75
+
76
+ parseFormMessages(req, attributes)
77
+ {
78
+ let queryParams = sc.get(req, 'query', {});
79
+ let formSuccess = sc.get(queryParams, 'form-success', '');
80
+ let formError = sc.get(queryParams, 'form-error', '');
81
+ let formKey = sc.get(queryParams, 'form-key', '');
82
+ let showSuccessMessage = '1' === formSuccess && '' !== formKey;
83
+ let showErrorMessage = '' !== formError && '' !== formKey;
84
+ let successMessage = sc.get(attributes, 'successMessage', 'Form submitted successfully!');
85
+ let errorMessage = sc.get(attributes, 'errorMessage', formError || 'There was an error submitting the form.');
86
+ return {
87
+ showSuccessMessage,
88
+ successMessage,
89
+ showErrorMessage,
90
+ errorMessage
91
+ };
92
+ }
93
+
94
+ async renderFormFields(fieldsToRender, domain, req, submittedValues = {}, errors = {})
95
+ {
96
+ if(!sc.isArray(fieldsToRender) || 0 === fieldsToRender.length){
97
+ return '';
98
+ }
99
+ let renderedFields = '';
100
+ for(let field of fieldsToRender){
101
+ if(!sc.isObject(field)){
102
+ continue;
103
+ }
104
+ let fieldHtml = await this.renderFormField(field, domain, submittedValues, errors);
105
+ if(fieldHtml){
106
+ renderedFields += fieldHtml;
107
+ }
108
+ }
109
+ return renderedFields;
110
+ }
111
+
112
+ async renderFormField(field, domain, submittedValues = {}, errors = {})
113
+ {
114
+ let fieldType = sc.get(field, 'type', 'text');
115
+ let fieldTemplate = await this.loadFormTemplate('field_'+fieldType, domain);
116
+ if(!fieldTemplate){
117
+ fieldTemplate = await this.loadFormTemplate('field_text', domain);
118
+ }
119
+ if(!fieldTemplate){
120
+ Logger.error('Field template not found for type: '+fieldType);
121
+ return '';
122
+ }
123
+ return this.renderEngine.render(
124
+ fieldTemplate,
125
+ this.buildFieldTemplateData(field, submittedValues, errors),
126
+ this.getPartialsForDomain(domain)
127
+ );
128
+ }
129
+
130
+ buildFieldTemplateData(field, submittedValues = {}, errors = {})
131
+ {
132
+ let fieldName = sc.get(field, 'name', '');
133
+ let fieldType = sc.get(field, 'type', 'text');
134
+ let fieldLabel = sc.get(field, 'label', fieldName);
135
+ let fieldValue = sc.get(submittedValues, fieldName, sc.get(field, 'defaultValue', ''));
136
+ let isRequired = sc.get(field, 'required', false);
137
+ let fieldError = sc.get(errors, fieldName, '');
138
+ let options = sc.get(field, 'options', []);
139
+ if(sc.isArray(options)){
140
+ options = options.map(option => {
141
+ if(sc.isObject(option)){
142
+ return {
143
+ value: sc.get(option, 'value', ''),
144
+ label: sc.get(option, 'label', sc.get(option, 'value', '')),
145
+ selected: sc.get(option, 'value', '') === fieldValue
146
+ };
147
+ }
148
+ return {
149
+ value: option,
150
+ label: option,
151
+ selected: option === fieldValue
152
+ };
153
+ });
154
+ }
155
+ return {
156
+ field,
157
+ fieldName,
158
+ fieldType,
159
+ fieldLabel,
160
+ fieldValue,
161
+ isRequired,
162
+ fieldError,
163
+ hasError: '' !== fieldError,
164
+ requiredClass: isRequired ? 'required' : '',
165
+ errorClass: '' !== fieldError ? 'error' : '',
166
+ options,
167
+ placeholder: sc.get(field, 'placeholder', ''),
168
+ helpText: sc.get(field, 'helpText', ''),
169
+ maxLength: sc.get(field, 'maxLength', ''),
170
+ pattern: sc.get(field, 'pattern', ''),
171
+ min: sc.get(field, 'min', ''),
172
+ max: sc.get(field, 'max', ''),
173
+ step: sc.get(field, 'step', '')
174
+ };
175
+ }
176
+
177
+ async loadFormTemplate(templateName, domain)
178
+ {
179
+ let cacheKey = templateName+'_'+domain;
180
+ if(sc.hasOwn(this.loadedTemplates, cacheKey)){
181
+ return this.loadedTemplates[cacheKey];
182
+ }
183
+ let templatePath = this.findFormTemplate(templateName, domain);
184
+ if(!templatePath){
185
+ Logger.warning('Form template not found: '+templateName+' for domain: '+domain);
186
+ return false;
187
+ }
188
+ let templateContent = await FileHandler.readFile(templatePath);
189
+ if(!templateContent){
190
+ Logger.error('Failed to read form template: '+templatePath);
191
+ return false;
192
+ }
193
+ this.loadedTemplates[cacheKey] = templateContent;
194
+ return templateContent;
195
+ }
196
+
197
+ findFormTemplate(templateName, domain)
198
+ {
199
+ let filename = templateName+'.html';
200
+ let domainTemplatePath = FileHandler.joinPaths(this.templatesPath, 'domains', domain, 'cms_forms', filename);
201
+ if(FileHandler.exists(domainTemplatePath)){
202
+ return domainTemplatePath;
203
+ }
204
+ if(this.defaultDomain && domain !== this.defaultDomain){
205
+ let defaultDomainPath = FileHandler.joinPaths(this.templatesPath, 'domains', this.defaultDomain, 'cms_forms', filename);
206
+ if(FileHandler.exists(defaultDomainPath)){
207
+ return defaultDomainPath;
208
+ }
209
+ }
210
+ let rootTemplatePath = FileHandler.joinPaths(this.templatesPath, 'cms_forms', filename);
211
+ if(FileHandler.exists(rootTemplatePath)){
212
+ return rootTemplatePath;
213
+ }
214
+ return false;
215
+ }
216
+
217
+ getPartialsForDomain(domain)
218
+ {
219
+ if(!this.getPartials){
220
+ Logger.error('getPartials function not provided to DynamicFormRenderer');
221
+ return {};
222
+ }
223
+ return this.getPartials(domain);
224
+ }
225
+
226
+ }
227
+
228
+ module.exports.DynamicFormRenderer = DynamicFormRenderer;
@@ -0,0 +1,135 @@
1
+ /**
2
+ *
3
+ * Reldens - CMS - DynamicFormRequestHandler
4
+ *
5
+ */
6
+
7
+ const { Logger, sc } = require('@reldens/utils');
8
+
9
+ class DynamicFormRequestHandler
10
+ {
11
+
12
+ constructor(props)
13
+ {
14
+ this.dynamicForm = sc.get(props, 'dynamicForm', false);
15
+ this.contentRenderer = sc.get(props, 'contentRenderer', false);
16
+ this.requestProcessor = sc.get(props, 'requestProcessor', false);
17
+ this.cacheManager = sc.get(props, 'cacheManager', false);
18
+ this.enableJsonResponse = sc.get(props, 'enableJsonResponse', false);
19
+ this.events = sc.get(props, 'events', false);
20
+ if(!this.events){
21
+ Logger.error('EventsManager not provided to DynamicFormRequestHandler - forms functionality disabled');
22
+ this.isDisabled = true;
23
+ }
24
+ }
25
+
26
+ async handleFormSubmission(req, res)
27
+ {
28
+ if(this.isDisabled){
29
+ return this.handleBadRequest(res, 'Forms functionality disabled');
30
+ }
31
+ try {
32
+ let formKey = sc.get(req.body, 'formKey', '');
33
+ let submittedValues = sc.get(req.body, 'submittedValues', {});
34
+ if(!formKey || !submittedValues){
35
+ return this.handleBadRequest(res, 'Missing formKey or submittedValues');
36
+ }
37
+ await this.events.emit('reldens.dynamicFormRequestHandler.beforeValidation', {
38
+ formKey,
39
+ submittedValues,
40
+ req,
41
+ res
42
+ });
43
+ let validation = await this.dynamicForm.validateFormSubmission(formKey, submittedValues, req);
44
+ if(!validation.isValid){
45
+ return this.handleValidationError(req, res, validation.error, formKey);
46
+ }
47
+ let preparedValues = this.dynamicForm.prepareSubmittedValues(
48
+ submittedValues,
49
+ validation.formConfig.fields_schema
50
+ );
51
+ await this.events.emit('reldens.dynamicFormRequestHandler.beforeSave', {
52
+ formKey,
53
+ preparedValues,
54
+ formConfig: validation.formConfig,
55
+ req,
56
+ res
57
+ });
58
+ let submissionResult = await this.dynamicForm.saveFormSubmission(
59
+ validation.formConfig,
60
+ preparedValues
61
+ );
62
+ if(!submissionResult){
63
+ return this.handleValidationError(req, res, 'Failed to save form submission', formKey);
64
+ }
65
+ await this.events.emit('reldens.dynamicFormRequestHandler.afterSave', {
66
+ formKey,
67
+ submissionResult,
68
+ formConfig: validation.formConfig,
69
+ req,
70
+ res
71
+ });
72
+ return this.handleSuccessResponse(req, res, formKey, submissionResult);
73
+ } catch(error) {
74
+ Logger.error('Form submission handling error: '+error.message);
75
+ return this.handleBadRequest(res, 'Internal server error');
76
+ }
77
+ }
78
+
79
+ handleBadRequest(res, message)
80
+ {
81
+ if(this.enableJsonResponse){
82
+ return res.status(400).json({
83
+ success: false,
84
+ error: message
85
+ });
86
+ }
87
+ return res.status(400).send('Bad Request: '+message);
88
+ }
89
+
90
+ handleValidationError(req, res, error, formKey)
91
+ {
92
+ if(this.enableJsonResponse){
93
+ return res.status(400).json({
94
+ success: false,
95
+ error: error,
96
+ formKey: formKey
97
+ });
98
+ }
99
+ return res.redirect(this.buildErrorRedirectPath(req, error, formKey));
100
+ }
101
+
102
+ handleSuccessResponse(req, res, formKey, submissionResult)
103
+ {
104
+ if(this.enableJsonResponse){
105
+ return res.json({
106
+ success: true,
107
+ message: 'Form submitted successfully',
108
+ formKey: formKey,
109
+ submissionId: submissionResult.id
110
+ });
111
+ }
112
+ return res.redirect(this.buildSuccessRedirectPath(sc.get(req.body, 'successRedirect', '/'), formKey));
113
+ }
114
+
115
+ buildErrorRedirectPath(req, error, formKey)
116
+ {
117
+ let baseRedirect = sc.get(req.body, 'errorRedirect', '/');
118
+ let finalRedirect = sc.get(req.headers, 'referer', baseRedirect) || baseRedirect;
119
+ return finalRedirect
120
+ +(finalRedirect.includes('?') ? '&' : '?')
121
+ +'form-error='+encodeURIComponent(error)
122
+ +'&form-key='+encodeURIComponent(formKey);
123
+ }
124
+
125
+ buildSuccessRedirectPath(successRedirect, formKey)
126
+ {
127
+ return successRedirect
128
+ +(successRedirect.includes('?') ? '&' : '?')
129
+ +'form-success=1'
130
+ +'&form-key='+encodeURIComponent(formKey);
131
+ }
132
+
133
+ }
134
+
135
+ module.exports.DynamicFormRequestHandler = DynamicFormRequestHandler;