PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.4
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.4
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
fluentform / app / Modules / Ai / AiFormBuilder.php

AiFormBuilder.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.4, at app/Modules/Ai/AiFormBuilder.php

755 lines 25.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentForm\App\Modules\Ai;
4
5 defined('ABSPATH') or die;
6
7 use Exception;
8 use FluentForm\App\Helpers\Helper;
9 use FluentForm\App\Models\Form;
10 use FluentForm\App\Models\FormMeta;
11 use FluentForm\App\Modules\Acl\Acl;
12 use FluentForm\App\Modules\Form\FormFieldsParser;
13 use FluentForm\App\Modules\Payments\PaymentHelper;
14 use FluentForm\App\Services\FluentConversational\Classes\Converter\Converter;
15 use FluentForm\App\Services\Form\FormService;
16 use FluentForm\Framework\Helpers\ArrayHelper as Arr;
17 use FluentForm\Framework\Support\Sanitizer;
18
19 class AiFormBuilder extends FormService
20 {
21 private $allDefaultFields = [];
22
23 public function __construct()
24 {
25 parent::__construct();
26 add_action('wp_ajax_fluentform_ai_create_form', [$this, 'buildForm'], 11, 0);
27 }
28
29 public function buildForm()
30 {
31 try {
32 Acl::verify('fluentform_forms_manager');
33 $form = $this->generateForm($this->app->request->all());
34 $form = $this->prepareAndSaveForm($form);
35 wp_send_json_success([
36 'formId' => $form->id,
37 'redirect_url' => admin_url(
38 'admin.php?page=fluent_forms&form_id=' . $form->id . '&route=editor'
39 ),
40 'message' => __('Successfully created a form.', 'fluentform'),
41 ], 200);
42 } catch (Exception $e) {
43 wp_send_json_error([
44 'message' => $e->getMessage(),
45 ], 422);
46 }
47 }
48
49 /**
50 * @param array $form
51 * @return Form|\FluentForm\Framework\Database\Query\Builder
52 * @throws Exception
53 */
54 protected function prepareAndSaveForm($form)
55 {
56 $allFields = $this->getDefaultFields();
57 $fluentFormFields = [];
58 $fields = Arr::get($form, 'fields', []);
59 $isConversational = Arr::isTrue($form, 'is_conversational');
60 $customCss = Arr::get($form, 'custom_css', '');
61 $hasStep = false;
62 $lastFieldIndex = count($fields) - 1;
63
64 $disableFields = array_keys($this->getDisabledComponents());
65 foreach ($fields as $index => $field) {
66 if (count($field) == 1) {
67 $field = reset($field);
68 }
69 if ($element = $this->resolveInput($field)) {
70 if (in_array($element, $disableFields)) {
71 continue;
72 }
73 if (!$hasStep && 'form_step' === $element) {
74 if (0 === $index || $lastFieldIndex === $index) {
75 continue;
76 }
77 $hasStep = true;
78 }
79 $fluentFormFields[] = $this->processField($element, $field, $allFields);
80 }
81 }
82 $fluentFormFields = array_filter($fluentFormFields);
83 if (!$fluentFormFields) {
84 throw new Exception(esc_html__('Empty form. Please try again!', 'fluentform'));
85 }
86 $title = Arr::get($form, 'title', '');
87 return $this->saveForm($fluentFormFields, $title, $hasStep, $isConversational, $customCss);
88 }
89
90 /**
91 * @param array $args
92 * @return array response form fields
93 * @throws Exception
94 */
95 protected function generateForm($args)
96 {
97 $aiModel = Arr::get($args, 'ai_model', 'default');
98 $isUsingChatGpt = Helper::hasPro() && 'chat_gpt' == $aiModel && class_exists('FluentFormPro\classes\Chat\ChatFormBuilder');
99
100 if ($isUsingChatGpt) {
101 (new \FluentFormPro\classes\Chat\ChatFormBuilder())->buildForm();
102 }
103
104 $paymentSetting = PaymentHelper::getPaymentSettings();
105 $queryArgs = [
106 'user_prompt' => $this->getUserPrompt($args),
107 'site_url' => site_url(),
108 'site_title' => get_bloginfo('name'),
109 'site_locale' => determine_locale(),
110 'has_pro' => Helper::hasPro(),
111 'has_payment' => $paymentSetting['status'] == 'yes',
112 'request_id' => uniqid('ff_ai_')
113 ];
114
115 $result = (new FluentFormAIAPI())->makeRequest($queryArgs);
116
117 if (is_wp_error($result)) {
118 throw new Exception(esc_html($result->get_error_message()));
119 }
120
121 $response = trim(Arr::get($result, 'response', ''), '"');
122 if (false !== preg_match('/```json(.*?)```/s', $response, $matches)) {
123 $response = trim($matches[1]);
124 }
125
126 $decoded = json_decode($response, true);
127 if (json_last_error() !== JSON_ERROR_NONE || empty($decoded) || empty($decoded['fields'])) {
128 throw new Exception(esc_html__('Invalid response: Please try again!', 'fluentform'));
129 }
130 return $this->applyPromptHints($decoded, $args);
131 }
132
133 protected function getDefaultFields()
134 {
135 if ($this->allDefaultFields) {
136 return $this->allDefaultFields;
137 }
138 /**
139 * @var \FluentForm\App\Services\FormBuilder\Components
140 */
141 $components = $this->app->make('components');
142 $this->app->doAction('fluentform/editor_init', $components);
143 $editorComponents = $components->toArray();
144 $general = Arr::get($editorComponents, 'general', []);
145 $advanced = Arr::get($editorComponents, 'advanced', []);
146 $container = Arr::get($editorComponents, 'container', []);
147
148 // Apply filter to get additional components
149 // The second parameter (true) is passed to prevent field loss when form ID is falsy loss some field
150 $editorComponents = apply_filters('fluentform/editor_components', [], true);
151 if ($generalExtra = Arr::get($editorComponents, 'general')) {
152 $generalExtra = array_column($generalExtra, null, 'element');
153 $general = array_merge($general, $generalExtra);
154 }
155
156 if ($advancedExtra = Arr::get($editorComponents, 'advanced')) {
157 $advancedExtra = array_column($advancedExtra, null, 'element');
158 $advanced = array_merge($advanced, $advancedExtra);
159 }
160
161 $payments = Arr::get($editorComponents, 'payments', []);
162 $payments = array_column($payments, null, 'element');
163 $this->allDefaultFields = array_merge($general, $payments, $advanced, ['container' => $container]);
164 return $this->allDefaultFields;
165 }
166
167 protected function processField($element, $field, $allFields)
168 {
169 if ('container' == $element) {
170 return $this->resolveContainerFields($field, $allFields);
171 }
172
173 $matchedField = Arr::get($allFields, $element);
174 if (!$matchedField) {
175 return [];
176 }
177 $formatField = $matchedField;
178 if ($settings = Arr::get($field, 'settings')) {
179 // Replace 'label' with 'admin_field_label' if 'label' is shorter
180 if (isset($settings['label']) && $adminFieldLabel = Arr::get($settings, 'admin_field_label')) {
181 if (strlen($settings['label']) < strlen($adminFieldLabel)) {
182 $settings['label'] = $adminFieldLabel;
183 }
184 }
185 $formatField['settings'] = wp_parse_args($settings, $matchedField['settings']);
186 }
187 if ($attributes = Arr::get($field, 'attributes')) {
188 $formatField['attributes'] = wp_parse_args($attributes, $matchedField['attributes']);
189 }
190
191 $formatField['uniqElKey'] = "el_" . uniqid();
192
193 if ('form_step' === $element) {
194 return $formatField;
195 }
196
197 if ($fieldName = Arr::get($field, 'attributes.name')) {
198 $formatField['attributes']['name'] = $fieldName;
199 }
200
201 if ($options = $this->getOptions(Arr::get($field, 'options'))) {
202 if (isset($formatField['settings']['advanced_options'])) {
203 $formatField['settings']['advanced_options'] = $options;
204 }
205 if ('ratings' == $element) {
206 $formatField['options'] = array_column($options, 'label', 'value');
207 }
208 }
209
210 if ('rangeslider' == $element) {
211 if ($min = Arr::get($field, 'min')) {
212 $formatField['attributes']['min'] = intval($min);
213 }
214 if ($max = intval(Arr::get($field, 'max', 10))) {
215 $formatField['attributes']['max'] = $max;
216 }
217 }
218
219 if (in_array($element, ['input_name', 'address']) && $fields = Arr::get($field, 'fields')) {
220 foreach ($formatField['fields'] as $name => &$field) {
221 if ($targetAttributes = Arr::get($fields, "$name.attributes")) {
222 $field['attributes'] = wp_parse_args($targetAttributes, $field['attributes']);
223 }
224 if ($targetSettings = Arr::get($fields, "$name.settings")) {
225 $field['settings'] = wp_parse_args($targetSettings, $field['settings']);
226 }
227 }
228 }
229
230 return $formatField;
231 }
232
233 protected function resolveInput($field)
234 {
235 if (!is_array($field)) {
236 return false;
237 }
238 $element = Arr::get($field, 'element');
239 $allElements = array_keys($this->getDefaultFields());
240 if (in_array($element, $allElements)) {
241 return $element;
242 }
243
244 $type = Arr::get($field, 'type');
245 if (!$type) {
246 return false;
247 }
248
249 $searchTags = fluentformLoadFile('Services/FormBuilder/ElementSearchTags.php');
250 $form = ['type' => ''];
251 $form = json_decode(json_encode($form));
252 $searchTags = apply_filters('fluentform/editor_element_search_tags', $searchTags, $form);
253 foreach ($searchTags as $inputKey => $tags) {
254 if (array_search($type, $tags) !== false) {
255 return $inputKey;
256 } else {
257 foreach ($tags as $tag) {
258 if (strpos($tag, $type) !== false) {
259 return $inputKey;
260 }
261 }
262 }
263 }
264 return false;
265 }
266
267 protected function getOptions($options = [])
268 {
269 $formattedOptions = [];
270 if (empty($options) || !is_array($options)) {
271 return $options;
272 }
273 foreach ($options as $key => $option) {
274 if (is_string($option) || is_numeric($option)) {
275 $value = $label = $option;
276 } elseif (is_array($option)) {
277 $label = Arr::get($option, 'label');
278 $value = Arr::get($option, 'value');
279 } else {
280 continue;
281 }
282 if (!$value || !$label) {
283 $value = $value ?? $label;
284 $label = $label ?? $value;
285 }
286 if (!$value || !$label) {
287 continue;
288 }
289 $formattedOptions[] = [
290 'label' => $label,
291 'value' => $value,
292 ];
293 }
294
295 return $formattedOptions;
296 }
297
298 protected function getBlankFormConfig()
299 {
300 $attributes = ['type' => 'form', 'predefined' => 'blank_form'];
301 $customForm = Form::resolvePredefinedForm($attributes);
302 $customForm['form_fields'] = json_decode($customForm['form_fields'], true);
303 $customForm['form_fields']['submitButton'] = $customForm['form']['submitButton'];
304 $customForm['form_fields'] = json_encode($customForm['form_fields']);
305 return $customForm;
306 }
307
308 protected function saveForm($formattedInputs, $title, $isStepForm = false, $isConversational = false, $customCss = '')
309 {
310 $customForm = $this->prepareCustomForm($formattedInputs, $isStepForm);
311 $data = Form::prepare($customForm);
312
313 $form = $this->model->create($data);
314 $form->title = $title ?: $form->title . ' (ChatGPT#' . $form->id . ')';
315
316 $formData = (object)$form->toArray();
317 if (FormFieldsParser::hasPaymentFields($formData)) {
318 $form->has_payment = 1;
319 }
320
321 if ($isConversational) {
322 $formMeta = FormMeta::prepare(['type' => 'form', 'predefined' => 'conversational'], $customForm);
323 $form->fill([
324 'form_fields' => Converter::convertExistingForm($form),
325 ])->save();
326 } else {
327 $form->save();
328 $formMeta = FormMeta::prepare(['type' => 'form', 'predefined' => 'blank_form'], $customForm);
329 }
330
331 FormMeta::store($form, $formMeta);
332
333 if ($customCss = fluentformSanitizeCSS($customCss)) {
334 Helper::setFormMeta($form->id, '_custom_form_css', $customCss);
335 }
336
337 do_action('fluentform/inserted_new_form', $form->id, $data);
338 return $form;
339 }
340
341 protected function prepareCustomForm($formattedInputs, $isStepForm)
342 {
343 $formattedInputs = fluentFormSanitizer($formattedInputs);
344 $customForm = $this->getBlankFormConfig();
345 $fields = json_decode($customForm['form_fields'], true);
346
347 $fields['form_fields']['fields'] = $formattedInputs;
348 $fields['form_fields']['submitButton'] = Arr::get($customForm, 'form.submitButton');
349
350 if ($isStepForm) {
351 $fields['form_fields']['stepsWrapper'] = $this->getStepWrapper();
352 }
353
354 $customForm['form_fields'] = json_encode($fields['form_fields']);
355
356 return $customForm;
357 }
358
359 protected function resolveContainerFields($field, $allFields)
360 {
361 $columns = Arr::get($field, 'columns');
362 $columnsCount = count($columns);
363 if (!$columnsCount || $columnsCount > 6) {
364 return [];
365 }
366 $matchedField = Arr::get($allFields, 'container.container_' . $columnsCount . '_col');
367 if (!$matchedField) {
368 return [];
369 }
370 $columnWidth = round(100 / $columnsCount, 2);
371 foreach ($columns as &$column) {
372 $formatedFields = [];
373 $fields = Arr::get($column, 'fields', []);
374 $columnWidth = Arr::get($column, 'width', $columnWidth);
375 foreach ($fields as $colField) {
376 $element = Arr::get($colField, 'element');
377 if ($columnField = $this->processField($element, $colField, $allFields)) {
378 $formatedFields[] = $columnField;
379 }
380 }
381 if ($formatedFields) {
382 $column['fields'] = $formatedFields;
383 $column['width'] = $columnWidth;
384 }
385 }
386 $matchedField['columns'] = $columns;
387 return $matchedField;
388 }
389
390 /**
391 * @return array
392 */
393 protected function getStepWrapper()
394 {
395 return [
396 'stepStart' => [
397 'element' => 'step_start',
398 'attributes' => [
399 'id' => '',
400 'class' => '',
401 ],
402 'settings' => [
403 'progress_indicator' => 'progress-bar',
404 'step_titles' => [],
405 'disable_auto_focus' => 'no',
406 'enable_auto_slider' => 'no',
407 'enable_step_data_persistency' => 'no',
408 'enable_step_page_resume' => 'no',
409 ],
410 'editor_options' => [
411 'title' => 'Start Paging'
412 ],
413 ],
414 'stepEnd' => [
415 'element' => 'step_end',
416 'attributes' => [
417 'id' => '',
418 'class' => '',
419 ],
420 'settings' => [
421 'prev_btn' => [
422 'type' => 'default',
423 'text' => 'Previous',
424 'img_url' => ''
425 ]
426 ],
427 'editor_options' => [
428 'title' => 'End Paging'
429 ],
430 ]
431 ];
432 }
433
434 private function getUserPrompt($args)
435 {
436 $startingQuery = "Create a form for ";
437 $query = Sanitizer::sanitizeTextField(Arr::get($args, 'query'));
438 if (empty($query)) {
439 throw new Exception(esc_html__('Query is empty!', 'fluentform'));
440 }
441
442 // Validate query length to prevent abuse (max 2000 characters)
443 if (strlen($query) > 2000) {
444 throw new Exception(esc_html__('Query is too long. Please limit your prompt to 2000 characters.', 'fluentform'));
445 }
446
447 $additionalQuery = Sanitizer::sanitizeTextField(Arr::get($args, 'additional_query'));
448
449 // Validate additional query length (max 1000 characters)
450 if ($additionalQuery && strlen($additionalQuery) > 1000) {
451 throw new Exception(esc_html__('Additional query is too long. Please limit to 1000 characters.', 'fluentform'));
452 }
453
454 if ($additionalQuery) {
455 $query .= "\n including questions for information like " . $additionalQuery . ".";
456 }
457 return $startingQuery . $query . $this->getPromptContractInstructions();
458 }
459
460 private function getPromptContractInstructions()
461 {
462 return "\n\nReturn strict JSON only. The user's instructions may be written in any language. "
463 . "Preserve labels and help text in the user's original language, but always return machine-readable field settings. "
464 . "For every field, explicitly set whether it is required in settings.validation_rules.required.value when the prompt marks it as required or optional. "
465 . "If the prompt lists allowed upload extensions, map them into settings.validation_rules.allowed_file_types.value. "
466 . "If the prompt asks for a multi-step form with sections, include form_step elements between sections.";
467 }
468
469 private function applyPromptHints(array $form, array $args)
470 {
471 $query = (string) Arr::get($args, 'query', '');
472 $additionalQuery = (string) Arr::get($args, 'additional_query', '');
473 $hints = $this->extractPromptFieldHints(trim($query . "\n" . $additionalQuery));
474
475 if (!$hints) {
476 return $form;
477 }
478
479 $hintIndex = 0;
480 $form['fields'] = $this->applyHintsToFields(Arr::get($form, 'fields', []), $hints, $hintIndex);
481
482 return $form;
483 }
484
485 private function applyHintsToFields(array $fields, array $hints, &$hintIndex)
486 {
487 foreach ($fields as &$field) {
488 if (!is_array($field)) {
489 continue;
490 }
491
492 if (count($field) === 1) {
493 $field = reset($field);
494 }
495
496 $element = Arr::get($field, 'element');
497
498 if ('container' === $element) {
499 $columns = Arr::get($field, 'columns', []);
500 foreach ($columns as &$column) {
501 $column['fields'] = $this->applyHintsToFields(Arr::get($column, 'fields', []), $hints, $hintIndex);
502 }
503 $field['columns'] = $columns;
504 continue;
505 }
506
507 if (in_array($element, ['form_step', 'section_break'])) {
508 continue;
509 }
510
511 $hint = Arr::get($hints, $hintIndex);
512 $hintIndex++;
513
514 if (!$hint) {
515 continue;
516 }
517
518 $field = $this->mergeFieldHint($field, $hint);
519 }
520
521 return $fields;
522 }
523
524 private function mergeFieldHint(array $field, array $hint)
525 {
526 if (array_key_exists('required', $hint)) {
527 Arr::set($field, 'settings.validation_rules.required.value', (bool) $hint['required']);
528 }
529
530 if (!empty($hint['help_message'])) {
531 Arr::set($field, 'settings.help_message', $hint['help_message']);
532 }
533
534 if (!empty($hint['allowed_file_types']) && in_array(Arr::get($field, 'element'), ['input_file', 'input_image'])) {
535 Arr::set($field, 'settings.validation_rules.allowed_file_types.value', array_values(array_unique($hint['allowed_file_types'])));
536 }
537
538 return $field;
539 }
540
541 private function extractPromptFieldHints($prompt)
542 {
543 if (!$prompt) {
544 return [];
545 }
546
547 $lines = preg_split('/\R/u', $prompt);
548 $hints = [];
549
550 foreach ($lines as $line) {
551 $line = trim(wp_strip_all_tags($line));
552 $line = preg_replace('/^[\-\*\d\.\)\s]+/u', '', $line);
553
554 if (!$line || !$this->looksLikeFieldLine($line)) {
555 continue;
556 }
557
558 $attributesText = '';
559 if (preg_match('/\(([^()]*)\)/u', $line, $matches)) {
560 $attributesText = trim($matches[1]);
561 }
562
563 $hint = [];
564 $normalizedLine = function_exists('mb_strtolower') ? mb_strtolower($line, 'UTF-8') : strtolower($line);
565
566 if ($this->containsAnyPhrase($normalizedLine, $this->getRequiredKeywords())) {
567 $hint['required'] = true;
568 } elseif ($this->containsAnyPhrase($normalizedLine, $this->getOptionalKeywords())) {
569 $hint['required'] = false;
570 }
571
572 $helpMessage = $this->extractHelpMessage($attributesText);
573 if ($helpMessage) {
574 $hint['help_message'] = $helpMessage;
575 }
576
577 if ($this->containsAnyPhrase($normalizedLine, $this->getFileUploadKeywords())) {
578 $allowedTypes = $this->extractAllowedFileTypes($line);
579 if ($allowedTypes) {
580 $hint['allowed_file_types'] = $allowedTypes;
581 }
582 }
583
584 $hints[] = $hint;
585 }
586
587 return $hints;
588 }
589
590 private function looksLikeFieldLine($line)
591 {
592 if (preg_match('/^(create|erstelle|crée|crear|criar|crea|maak|utw[]rz|oluştur|创建|作成)\b/ui', $line)) {
593 return false;
594 }
595
596 if (preg_match('/^(section|abschnitt|secci[]n|sectione|seção|sectie|sekcja|b[]l[]m|章节|セクション)\b/ui', $line)) {
597 return false;
598 }
599
600 if (false === strpos($line, '(')) {
601 return false;
602 }
603
604 preg_match('/\(([^()]*)\)/u', $line, $matches);
605 $attributesText = isset($matches[1]) ? $matches[1] : '';
606
607 return $this->containsAnyPhrase(
608 function_exists('mb_strtolower') ? mb_strtolower($attributesText, 'UTF-8') : strtolower($attributesText),
609 $this->getFieldDescriptorKeywords()
610 );
611 }
612
613 private function extractHelpMessage($attributesText)
614 {
615 if (!$attributesText) {
616 return '';
617 }
618
619 if (preg_match('/(?:note|hint|hinweis|remarque|nota|nota bene|opmerking|uwaga|not|说明|備考)\s*:\s*["“]?(.+?)["”]?$/ui', $attributesText, $matches)) {
620 return trim($matches[1], " \t\n\r\0\x0B\"'“”");
621 }
622
623 return '';
624 }
625
626 private function extractAllowedFileTypes($line)
627 {
628 preg_match_all('/\b(jpe?g|gif|png|tiff?|bmp|webp|svg|pdf|docx?|xlsx?|xls|csv|zip|rar|txt)\b/ui', $line, $matches);
629
630 if (empty($matches[1])) {
631 return [];
632 }
633
634 $types = array_map(function ($type) {
635 $type = strtolower($type);
636
637 if ('jpeg' === $type) {
638 return 'jpg';
639 }
640
641 if ('tif' === $type) {
642 return 'tiff';
643 }
644
645 return $type;
646 }, $matches[1]);
647
648 return array_values(array_unique($types));
649 }
650
651 private function containsAnyPhrase($text, array $phrases)
652 {
653 foreach ($phrases as $phrase) {
654 if (false !== strpos($text, $phrase)) {
655 return true;
656 }
657 }
658
659 return false;
660 }
661
662 private function getRequiredKeywords()
663 {
664 return [
665 'required',
666 'mandatory',
667 'pflichtfeld',
668 'erforderlich',
669 'obligatoire',
670 'requis',
671 'obligatorio',
672 'obrigatório',
673 'obbligatorio',
674 'verplicht',
675 'wymagane',
676 'zorunlu',
677 '�
678 ',
679 '�
680 '
681 ];
682 }
683
684 private function getOptionalKeywords()
685 {
686 return [
687 'optional',
688 'facultatif',
689 'facoltativo',
690 'opcjonalne',
691 'isteğe bağlı',
692 'opcional',
693 'opcionales',
694 '选填',
695 '任意'
696 ];
697 }
698
699 private function getFileUploadKeywords()
700 {
701 return [
702 'file upload',
703 'upload',
704 'datei',
705 'datei upload',
706 'foto upload',
707 'image upload',
708 'upload de fichier',
709 'carga de archivo',
710 'subida de archivo',
711 'carregamento de arquivo',
712 'caricamento file',
713 'bestandsupload',
714 'yükleme',
715 '上传'
716 ];
717 }
718
719 private function getFieldDescriptorKeywords()
720 {
721 return [
722 'text',
723 'textarea',
724 'email',
725 'date',
726 'file',
727 'upload',
728 'phone',
729 'number',
730 'radio',
731 'checkbox',
732 'select',
733 'dropdown',
734 'image',
735 'foto',
736 'bild',
737 'datei',
738 'telefon',
739 'mobil',
740 'correo',
741 'texte',
742 'texto',
743 'testo',
744 'fecha',
745 'fichier',
746 'archivo',
747 'caricamento',
748 'ficheiro',
749 'bestand',
750 'yükleme',
751 '上传'
752 ];
753 }
754 }
755