Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/fluentform/app/Modules/Ai/AiFormBuilder.php
Keine Baseline-Datei – Diff nur gegen leer.
1
-
1
+
<?php
2
+
3
+
namespace FluentForm\App\Modules\Ai;
4
+
5
+
use Exception;
6
+
use FluentForm\App\Helpers\Helper;
7
+
use FluentForm\App\Models\Form;
8
+
use FluentForm\App\Models\FormMeta;
9
+
use FluentForm\App\Modules\Acl\Acl;
10
+
use FluentForm\App\Modules\Form\FormFieldsParser;
11
+
use FluentForm\App\Modules\Payments\PaymentHelper;
12
+
use FluentForm\App\Services\FluentConversational\Classes\Converter\Converter;
13
+
use FluentForm\App\Services\Form\FormService;
14
+
use FluentForm\Framework\Helpers\ArrayHelper as Arr;
15
+
use FluentForm\Framework\Support\Sanitizer;
16
+
17
+
class AiFormBuilder extends FormService
18
+
{
19
+
private $allDefaultFields = [];
20
+
21
+
public function __construct()
22
+
{
23
+
parent::__construct();
24
+
add_action('wp_ajax_fluentform_ai_create_form', [$this, 'buildForm'], 11, 0);
25
+
}
26
+
27
+
public function buildForm()
28
+
{
29
+
try {
30
+
Acl::verifyNonce();
31
+
$form = $this->generateForm($this->app->request->all());
32
+
$form = $this->prepareAndSaveForm($form);
33
+
wp_send_json_success([
34
+
'formId' => $form->id,
35
+
'redirect_url' => admin_url(
36
+
'admin.php?page=fluent_forms&form_id=' . $form->id . '&route=editor'
37
+
),
38
+
'message' => __('Successfully created a form.', 'fluentform'),
39
+
], 200);
40
+
} catch (Exception $e) {
41
+
wp_send_json_error([
42
+
'message' => $e->getMessage(),
43
+
], 422);
44
+
}
45
+
}
46
+
47
+
/**
48
+
* @param array $form
49
+
* @return Form|\FluentForm\Framework\Database\Query\Builder
50
+
* @throws Exception
51
+
*/
52
+
protected function prepareAndSaveForm($form)
53
+
{
54
+
$allFields = $this->getDefaultFields();
55
+
$fluentFormFields = [];
56
+
$fields = Arr::get($form, 'fields', []);
57
+
$isConversational = Arr::isTrue($form, 'is_conversational');
58
+
$customCss = Arr::get($form, 'custom_css', '');
59
+
$customJs = Arr::get($form, 'custom_js', '');
60
+
$hasStep = false;
61
+
$lastFieldIndex = count($fields) - 1;
62
+
63
+
$disableFields = array_keys($this->getDisabledComponents());
64
+
foreach ($fields as $index => $field) {
65
+
if (count($field) == 1) {
66
+
$field = reset($field);
67
+
}
68
+
if ($element = $this->resolveInput($field)) {
69
+
if (in_array($element, $disableFields)) {
70
+
continue;
71
+
}
72
+
if (!$hasStep && 'form_step' === $element) {
73
+
if (0 === $index || $lastFieldIndex === $index) {
74
+
continue;
75
+
}
76
+
$hasStep = true;
77
+
}
78
+
$fluentFormFields[] = $this->processField($element, $field, $allFields);
79
+
}
80
+
}
81
+
$fluentFormFields = array_filter($fluentFormFields);
82
+
if (!$fluentFormFields) {
83
+
throw new Exception(esc_html__('Empty form. Please try again!', 'fluentform'));
84
+
}
85
+
$title = Arr::get($form, 'title', '');
86
+
return $this->saveForm($fluentFormFields, $title, $hasStep, $isConversational, $customCss, $customJs);
87
+
}
88
+
89
+
/**
90
+
* @param array $args
91
+
* @return array response form fields
92
+
* @throws Exception
93
+
*/
94
+
protected function generateForm($args)
95
+
{
96
+
$aiModel = Arr::get($args, 'ai_model', 'default');
97
+
$isUsingChatGpt = Helper::hasPro() && 'chat_gpt' == $aiModel && class_exists('FluentFormPro\classes\Chat\ChatFormBuilder');
98
+
99
+
if ($isUsingChatGpt) {
100
+
(new \FluentFormPro\classes\Chat\ChatFormBuilder())->buildForm();
101
+
}
102
+
103
+
$paymentSetting = PaymentHelper::getPaymentSettings();
104
+
$queryArgs = [
105
+
'user_prompt' => $this->getUserPrompt($args),
106
+
'site_url' => site_url(),
107
+
'site_title' => get_bloginfo('name'),
108
+
'has_pro' => Helper::hasPro(),
109
+
'has_payment' => $paymentSetting['status'] == 'yes',
110
+
'request_id' => uniqid('ff_ai_')
111
+
];
112
+
113
+
$result = (new FluentFormAIAPI())->makeRequest($queryArgs);
114
+
115
+
if (is_wp_error($result)) {
116
+
throw new Exception(esc_html($result->get_error_message()));
117
+
}
118
+
119
+
$response = trim(Arr::get($result, 'response', ''), '"');
120
+
if (false !== preg_match('/```json(.*?)```/s', $response, $matches)) {
121
+
$response = trim($matches[1]);
122
+
}
123
+
124
+
$decoded = json_decode($response, true);
125
+
if (json_last_error() !== JSON_ERROR_NONE || empty($decoded) || empty($decoded['fields'])) {
126
+
throw new Exception(esc_html__('Invalid response: Please try again!', 'fluentform'));
127
+
}
128
+
return $decoded;
129
+
}
130
+
131
+
protected function getDefaultFields()
132
+
{
133
+
if ($this->allDefaultFields) {
134
+
return $this->allDefaultFields;
135
+
}
136
+
/**
137
+
* @var \FluentForm\App\Services\FormBuilder\Components
138
+
*/
139
+
$components = $this->app->make('components');
140
+
$this->app->doAction('fluentform/editor_init', $components);
141
+
$editorComponents = $components->toArray();
142
+
$general = Arr::get($editorComponents, 'general', []);
143
+
$advanced = Arr::get($editorComponents, 'advanced', []);
144
+
$container = Arr::get($editorComponents, 'container', []);
145
+
146
+
// Apply filter to get additional components
147
+
// The second parameter (true) is passed to prevent field loss when form ID is falsy loss some field
148
+
$editorComponents = apply_filters('fluentform/editor_components', [], true);
149
+
if ($generalExtra = Arr::get($editorComponents, 'general')) {
150
+
$generalExtra = array_column($generalExtra, null, 'element');
151
+
$general = array_merge($general, $generalExtra);
152
+
}
153
+
154
+
if ($advancedExtra = Arr::get($editorComponents, 'advanced')) {
155
+
$advancedExtra = array_column($advancedExtra, null, 'element');
156
+
$advanced = array_merge($advanced, $advancedExtra);
157
+
}
158
+
159
+
$payments = Arr::get($editorComponents, 'payments', []);
160
+
$payments = array_column($payments, null, 'element');
161
+
$this->allDefaultFields = array_merge($general, $payments, $advanced, ['container' => $container]);
162
+
return $this->allDefaultFields;
163
+
}
164
+
165
+
protected function processField($element, $field, $allFields)
166
+
{
167
+
if ('container' == $element) {
168
+
return $this->resolveContainerFields($field, $allFields);
169
+
}
170
+
171
+
$matchedField = Arr::get($allFields, $element);
172
+
if (!$matchedField) {
173
+
return [];
174
+
}
175
+
$formatField = $matchedField;
176
+
if ($settings = Arr::get($field, 'settings')) {
177
+
// Replace 'label' with 'admin_field_label' if 'label' is shorter
178
+
if (isset($settings['label']) && $adminFieldLabel = Arr::get($settings, 'admin_field_label')) {
179
+
if (strlen($settings['label']) < strlen($adminFieldLabel)) {
180
+
$settings['label'] = $adminFieldLabel;
181
+
}
182
+
}
183
+
$formatField['settings'] = wp_parse_args($settings, $matchedField['settings']);
184
+
}
185
+
if ($attributes = Arr::get($field, 'attributes')) {
186
+
$formatField['attributes'] = wp_parse_args($attributes, $matchedField['attributes']);
187
+
}
188
+
189
+
$formatField['uniqElKey'] = "el_" . uniqid();
190
+
191
+
if ('form_step' === $element) {
192
+
return $formatField;
193
+
}
194
+
195
+
if ($fieldName = Arr::get($field, 'attributes.name')) {
196
+
$formatField['attributes']['name'] = $fieldName;
197
+
}
198
+
199
+
if ($options = $this->getOptions(Arr::get($field, 'options'))) {
200
+
if (isset($formatField['settings']['advanced_options'])) {
201
+
$formatField['settings']['advanced_options'] = $options;
202
+
}
203
+
if ('ratings' == $element) {
204
+
$formatField['options'] = array_column($options, 'label', 'value');
205
+
}
206
+
}
207
+
208
+
if ('rangeslider' == $element) {
209
+
if ($min = Arr::get($field, 'min')) {
210
+
$formatField['attributes']['min'] = intval($min);
211
+
}
212
+
if ($max = intval(Arr::get($field, 'max', 10))) {
213
+
$formatField['attributes']['max'] = $max;
214
+
}
215
+
}
216
+
217
+
if (in_array($element, ['input_name', 'address']) && $fields = Arr::get($field, 'fields')) {
218
+
foreach ($formatField['fields'] as $name => &$field) {
219
+
if ($targetAttributes = Arr::get($fields, "$name.attributes")) {
220
+
$field['attributes'] = wp_parse_args($targetAttributes, $field['attributes']);
221
+
}
222
+
if ($targetSettings = Arr::get($fields, "$name.settings")) {
223
+
$field['settings'] = wp_parse_args($targetSettings, $field['settings']);
224
+
}
225
+
}
226
+
}
227
+
228
+
return $formatField;
229
+
}
230
+
231
+
protected function resolveInput($field)
232
+
{
233
+
if (!is_array($field)) {
234
+
return false;
235
+
}
236
+
$element = Arr::get($field, 'element');
237
+
$allElements = array_keys($this->getDefaultFields());
238
+
if (in_array($element, $allElements)) {
239
+
return $element;
240
+
}
241
+
242
+
$type = Arr::get($field, 'type');
243
+
if (!$type) {
244
+
return false;
245
+
}
246
+
247
+
$searchTags = fluentformLoadFile('Services/FormBuilder/ElementSearchTags.php');
248
+
$form = ['type' => ''];
249
+
$form = json_decode(json_encode($form));
250
+
$searchTags = apply_filters('fluentform/editor_element_search_tags', $searchTags, $form);
251
+
foreach ($searchTags as $inputKey => $tags) {
252
+
if (array_search($type, $tags) !== false) {
253
+
return $inputKey;
254
+
} else {
255
+
foreach ($tags as $tag) {
256
+
if (strpos($tag, $type) !== false) {
257
+
return $inputKey;
258
+
}
259
+
}
260
+
}
261
+
}
262
+
return false;
263
+
}
264
+
265
+
protected function getOptions($options = [])
266
+
{
267
+
$formattedOptions = [];
268
+
if (empty($options) || !is_array($options)) {
269
+
return $options;
270
+
}
271
+
foreach ($options as $key => $option) {
272
+
if (is_string($option) || is_numeric($option)) {
273
+
$value = $label = $option;
274
+
} elseif (is_array($option)) {
275
+
$label = Arr::get($option, 'label');
276
+
$value = Arr::get($option, 'value');
277
+
} else {
278
+
continue;
279
+
}
280
+
if (!$value || !$label) {
281
+
$value = $value ?? $label;
282
+
$label = $label ?? $value;
283
+
}
284
+
if (!$value || !$label) {
285
+
continue;
286
+
}
287
+
$formattedOptions[] = [
288
+
'label' => $label,
289
+
'value' => $value,
290
+
];
291
+
}
292
+
293
+
return $formattedOptions;
294
+
}
295
+
296
+
protected function getBlankFormConfig()
297
+
{
298
+
$attributes = ['type' => 'form', 'predefined' => 'blank_form'];
299
+
$customForm = Form::resolvePredefinedForm($attributes);
300
+
$customForm['form_fields'] = json_decode($customForm['form_fields'], true);
301
+
$customForm['form_fields']['submitButton'] = $customForm['form']['submitButton'];
302
+
$customForm['form_fields'] = json_encode($customForm['form_fields']);
303
+
return $customForm;
304
+
}
305
+
306
+
protected function saveForm($formattedInputs, $title, $isStepForm = false, $isConversational = false, $customCss = '', $customJs = '')
307
+
{
308
+
$customForm = $this->prepareCustomForm($formattedInputs, $isStepForm);
309
+
$data = Form::prepare($customForm);
310
+
311
+
$form = $this->model->create($data);
312
+
$form->title = $title ?: $form->title . ' (ChatGPT#' . $form->id . ')';
313
+
314
+
$formData = (object)$form->toArray();
315
+
if (FormFieldsParser::hasPaymentFields($formData)) {
316
+
$form->has_payment = 1;
317
+
}
318
+
319
+
if ($isConversational) {
320
+
$formMeta = FormMeta::prepare(['type' => 'form', 'predefined' => 'conversational'], $customForm);
321
+
$form->fill([
322
+
'form_fields' => Converter::convertExistingForm($form),
323
+
])->save();
324
+
} else {
325
+
$form->save();
326
+
$formMeta = FormMeta::prepare(['type' => 'form', 'predefined' => 'blank_form'], $customForm);
327
+
}
328
+
329
+
FormMeta::store($form, $formMeta);
330
+
331
+
if ($customCss = fluentformSanitizeCSS($customCss)) {
332
+
Helper::setFormMeta($form->id, '_custom_form_css', $customCss);
333
+
}
334
+
if ($customJs = fluentform_kses_js($customJs)) {
335
+
Helper::setFormMeta($form->id, '_custom_form_js', $customJs);
336
+
}
337
+
338
+
do_action('fluentform/inserted_new_form', $form->id, $data);
339
+
return $form;
340
+
}
341
+
342
+
protected function prepareCustomForm($formattedInputs, $isStepForm)
343
+
{
344
+
$formattedInputs = fluentFormSanitizer($formattedInputs);
345
+
$customForm = $this->getBlankFormConfig();
346
+
$fields = json_decode($customForm['form_fields'], true);
347
+
348
+
$fields['form_fields']['fields'] = $formattedInputs;
349
+
$fields['form_fields']['submitButton'] = Arr::get($customForm, 'form.submitButton');
350
+
351
+
if ($isStepForm) {
352
+
$fields['form_fields']['stepsWrapper'] = $this->getStepWrapper();
353
+
}
354
+
355
+
$customForm['form_fields'] = json_encode($fields['form_fields']);
356
+
357
+
return $customForm;
358
+
}
359
+
360
+
protected function resolveContainerFields($field, $allFields)
361
+
{
362
+
$columns = Arr::get($field, 'columns');
363
+
$columnsCount = count($columns);
364
+
if (!$columnsCount || $columnsCount > 6) {
365
+
return [];
366
+
}
367
+
$matchedField = Arr::get($allFields, 'container.container_' . $columnsCount . '_col');
368
+
if (!$matchedField) {
369
+
return [];
370
+
}
371
+
$columnWidth = round(100 / $columnsCount, 2);
372
+
foreach ($columns as &$column) {
373
+
$formatedFields = [];
374
+
$fields = Arr::get($column, 'fields', []);
375
+
$columnWidth = Arr::get($column, 'width', $columnWidth);
376
+
foreach ($fields as $colField) {
377
+
$element = Arr::get($colField, 'element');
378
+
if ($columnField = $this->processField($element, $colField, $allFields)) {
379
+
$formatedFields[] = $columnField;
380
+
}
381
+
}
382
+
if ($formatedFields) {
383
+
$column['fields'] = $formatedFields;
384
+
$column['width'] = $columnWidth;
385
+
}
386
+
}
387
+
$matchedField['columns'] = $columns;
388
+
return $matchedField;
389
+
}
390
+
391
+
/**
392
+
* @return array
393
+
*/
394
+
protected function getStepWrapper()
395
+
{
396
+
return [
397
+
'stepStart' => [
398
+
'element' => 'step_start',
399
+
'attributes' => [
400
+
'id' => '',
401
+
'class' => '',
402
+
],
403
+
'settings' => [
404
+
'progress_indicator' => 'progress-bar',
405
+
'step_titles' => [],
406
+
'disable_auto_focus' => 'no',
407
+
'enable_auto_slider' => 'no',
408
+
'enable_step_data_persistency' => 'no',
409
+
'enable_step_page_resume' => 'no',
410
+
],
411
+
'editor_options' => [
412
+
'title' => 'Start Paging'
413
+
],
414
+
],
415
+
'stepEnd' => [
416
+
'element' => 'step_end',
417
+
'attributes' => [
418
+
'id' => '',
419
+
'class' => '',
420
+
],
421
+
'settings' => [
422
+
'prev_btn' => [
423
+
'type' => 'default',
424
+
'text' => 'Previous',
425
+
'img_url' => ''
426
+
]
427
+
],
428
+
'editor_options' => [
429
+
'title' => 'End Paging'
430
+
],
431
+
]
432
+
];
433
+
}
434
+
435
+
private function getUserPrompt($args)
436
+
{
437
+
$startingQuery = "Create a form for ";
438
+
$query = Sanitizer::sanitizeTextField(Arr::get($args, 'query'));
439
+
if (empty($query)) {
440
+
throw new Exception(esc_html__('Query is empty!', 'fluentform'));
441
+
}
442
+
443
+
$additionalQuery = Sanitizer::sanitizeTextField(Arr::get($args, 'additional_query'));
444
+
445
+
if ($additionalQuery) {
446
+
$query .= "\n including questions for information like " . $additionalQuery . ".";
447
+
}
448
+
return $startingQuery . $query;
449
+
}
450
+
451
+
}
452
+