Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/fluentform/app/Modules/Ai/AiFormBuilder.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
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 +