PluginProbe
Repeater Fields for Gravity Forms / 3.2.2
Repeater Fields for Gravity Forms v3.2.2
3.2.2 3.2.1 3.2.0 3.1.1 3.1.0 3.0.4 3.0.3 3.0.2 3.0.1 3.0.0 2.5.1 2.5.0 2.4.6 trunk 2.0.5 2.0.9 2.1.0 2.3.2 2.3.7 2.4.1 2.4.3 2.4.4 2.4.5
repeater-for-gravity-forms / fields / repeater_field.php

repeater_field.php in Repeater Fields for Gravity Forms 3.2.2, at fields/repeater_field.php

1,047 lines 34.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // If Gravity Forms isn't loaded, bail.
3 if (!class_exists('GFForms')) {
4 die();
5 }
6 /**
7 * Class GF_Field_Phone
8 *
9 * Handles the behavior of Phone fields.
10 *
11 * @since Unknown
12 */
13 class Superaddons_GFRepeater_Field extends GF_Field
14 {
15 private $lead_id = null;
16 /**
17 * Defines the field type.
18 *
19 * @since Unknown
20 * @access public
21 *
22 * @var string The field type.
23 */
24 public $type = 'repeater_end';
25 public $list_fields_validate = array();
26 /**
27 * Defines the field title to be used in the form editor.
28 *
29 * @since Unknown
30 * @access public
31 *
32 * @used-by GFCommon::get_field_type_title()
33 *
34 * @return string The field title. Translatable and escaped.
35 */
36 public function get_form_editor_field_title()
37 {
38 return esc_attr__('Repeater end', 'repeater-for-gravity-forms');
39 }
40 public function get_form_editor_button()
41 {
42 return array(
43 'group' => 'advanced_fields',
44 'text' => $this->get_form_editor_field_title()
45 );
46 }
47 /**
48 * Defines the field settings available within the field editor.
49 *
50 * @since Unknown
51 * @access public
52 *
53 * @return array The field settings available for the field.
54 */
55 function get_form_editor_field_settings()
56 {
57 return array(
58 'conditional_logic_field_setting',
59 'prepopulate_field_setting',
60 'error_message_setting',
61 'label_setting',
62 'field_field_repeater_end_text_setting',
63 'field_field_repeater_initial_rows_setting',
64 'field_field_repeater_max_setting',
65 'label_placement_setting',
66 'admin_label_setting',
67 'size_setting',
68 'rules_setting',
69 'visibility_setting',
70 'duplicate_setting',
71 'placeholder_setting',
72 'description_setting',
73 'css_class_setting',
74 );
75 }
76 /**
77 * Defines if conditional logic is supported in this field type.
78 *
79 * @since Unknown
80 * @access public
81 *
82 * @used-by GFFormDetail::inline_scripts()
83 * @used-by GFFormSettings::output_field_scripts()
84 *
85 * @return bool true
86 */
87 public function is_conditional_logic_supported()
88 {
89 return false;
90 }
91 /**
92 * Returns the field input.
93 *
94 * @since Unknown
95 * @access public
96 *
97 * @used-by GFCommon::get_field_input()
98 * @uses GF_Field::is_entry_detail()
99 * @uses GF_Field::is_form_editor()
100 * @uses GF_Field_Phone::$failed_validation
101 * @uses GF_Field_Phone::get_phone_format()
102 * @uses GFFormsModel::is_html5_enabled()
103 * @uses GF_Field::get_field_placeholder_attribute()
104 * @uses GF_Field_Phone::$isRequired
105 * @uses GF_Field::get_tabindex()
106 *
107 * @param array $form The Form Object.
108 * @param string $value The value of the input. Defaults to empty string.
109 * @param null|array $entry The Entry Object. Defaults to null.
110 *
111 * @return string The HTML markup for the field.
112 */
113 public function get_field_input($form, $value = '', $entry = null)
114 {
115 if (is_array($value)) {
116 $value = '';
117 }
118 $id = intval($this->id);
119
120 // If resuming draft submission via Save & Continue and $value doesn't have saved_values,
121 // attempt to restore from draft $entry if available
122 if (empty($value) || (is_string($value) && strpos($value, 'saved_values') === false)) {
123 if (!empty($entry) && is_array($entry) && !empty($entry[$id])) {
124 $entry_data = maybe_unserialize($entry[$id]);
125 if (is_array($entry_data) && !empty($entry_data)) {
126 $decoded_value = !empty($value) ? json_decode($value, true) : array();
127 if (!is_array($decoded_value)) {
128 $decoded_value = array();
129 }
130 $row_ids = array_keys($entry_data);
131 $decoded_value['count'] = count($row_ids);
132 $decoded_value['id'] = $row_ids;
133 $saved_rows = array();
134 foreach ($entry_data as $row_id => $row_inputs) {
135 if (is_array($row_inputs)) {
136 foreach ($row_inputs as $in_name => $in_val) {
137 if (is_array($in_val)) {
138 foreach ($in_val as $sub_k => $sub_v) {
139 $saved_rows[$row_id]['input_' . str_replace('.', '_', $sub_k) . '__' . $row_id] = $sub_v;
140 }
141 } else {
142 $saved_rows[$row_id][$in_name] = $in_val;
143 }
144 }
145 }
146 }
147 $decoded_value['saved_values'] = $saved_rows;
148 $value = wp_json_encode($decoded_value);
149 }
150 }
151 }
152
153 $is_entry_detail = $this->is_entry_detail();
154 $is_form_editor = $this->is_form_editor();
155 $form_id = $form['id'];
156 $field_id = $is_entry_detail || $is_form_editor || $form_id == 0 ? "input_$id" : 'input_' . $form_id . "_$id";
157 $size = $this->size;
158 $disabled_text = $is_form_editor ? "disabled='disabled'" : '';
159 $class_suffix = $is_entry_detail ? '_admin' : '';
160 $class = $size . $class_suffix . " gf-field-repeater-data hidden";
161 $class = esc_attr($class);
162 $instruction_div = '';
163 $html_input_type = 'hidden';
164 $placeholder_attribute = $this->get_field_placeholder_attribute();
165 $required_attribute = $this->isRequired ? 'aria-required="true"' : '';
166 $invalid_attribute = $this->failed_validation ? 'aria-invalid="true"' : 'aria-invalid="false"';
167 $aria_describedby = $this->get_aria_describedby();
168 $tabindex = $this->get_tabindex();
169 $repeater_add_button = $this->field_repeater_end_text;
170 if ($repeater_add_button == "") {
171 $repeater_add_button = esc_attr__("Add more", "gravityforms-repeater");
172 }
173 $initial_rows = $this->repeater_initial_rows;
174 if ($initial_rows == "") {
175 $initial_rows = 1;
176 }
177 $limit = $this->repeater_max;
178 if ($limit == "" or $limit < 1) {
179 $limit = 9999;
180 }
181 $limit = apply_filters("yeeaddons_gf_repeater_limit", 5, $limit);
182 $initial_rows = apply_filters("yeeaddons_gf_repeater_initial_rows", 1, $initial_rows);
183 $input = "<input data-initial_rows_map_check='" . esc_attr($this->repeater_initial_rows_map) . "' data-initial_rows_map='input_{$form_id}_" . esc_attr($this->repeater_initial_rows_map) . "' data-map_id='field_" . $form_id . "_" . $id . "' name='input_{$id}' id='{$field_id}' type='{$html_input_type}' value='" . esc_attr($value) . "' class='{$class}' {$tabindex} {$placeholder_attribute} {$required_attribute} {$invalid_attribute} {$disabled_text}/>";
184 $html = '<div data-initial_rows_map_check="' . esc_attr($this->repeater_initial_rows_map) . '" class="repeater-field-warp-item-data" data-initial_rows="' . $initial_rows . '" data-limit="' . $limit . '" data-initial_rows_map="input_' . $form_id . '_' . esc_attr($this->repeater_initial_rows_map) . '" data-map_id="field_' . $form_id . '_' . $id . '">
185 <div class="repeater-field-warp-item">
186 </div>
187 <div class="repeater-field-footer"><a href="#"" class="gf-repeater-field-button-add" >' . $repeater_add_button . '</a></div>
188 ' . $input . '
189 <input type="hidden" class="gf-field-repeater-data-html hidden" />
190 </div>';
191 if (is_admin()) {
192 $html = '<hr>End Repeater<hr>';
193 return sprintf("<div class='ginput_container1'>%s</div>", $html);
194 } else {
195 return sprintf("<div class='ginput_container'>%s</div>", $html);
196 }
197 }
198 public static function save_draft_submission_values($submitted_values, $form)
199 {
200 if (empty($form['fields']) || !is_array($form['fields'])) {
201 return $submitted_values;
202 }
203
204 foreach ($form['fields'] as $field) {
205 if ($field->type !== 'repeater_end') {
206 continue;
207 }
208
209 $raw_value = rgar($submitted_values, $field->id);
210 if (empty($raw_value)) {
211 $raw_value = rgpost('input_' . $field->id);
212 }
213
214 if (empty($raw_value)) {
215 continue;
216 }
217
218 $repeater_data = is_array($raw_value) ? $raw_value : json_decode($raw_value, true);
219 if (!is_array($repeater_data) || empty($repeater_data['id']) || !is_array($repeater_data['id'])) {
220 continue;
221 }
222
223 // Capture all $_POST values for each repeater row
224 $saved_rows = array();
225 foreach ($repeater_data['id'] as $id_rand) {
226 $row_data = array();
227 foreach ($_POST as $post_key => $post_val) {
228 if (strpos($post_key, '__' . $id_rand) !== false) {
229 $row_data[$post_key] = wp_unslash($post_val);
230 }
231 }
232
233 // Also check if any file was uploaded in this row
234 $transient_keys = array();
235 $unique_id = rgpost('gform_unique_id');
236 if (!empty($unique_id)) {
237 $transient_keys[] = 'gf_repeater_files_' . $unique_id;
238 }
239 $transient_keys[] = 'gf_repeater_files_' . $form['id'];
240 $transient_keys[] = 'gf_repeater_files_1';
241
242 foreach ($transient_keys as $t_key) {
243 $transient_files = get_transient($t_key);
244 if (is_array($transient_files)) {
245 foreach ($transient_files as $tk => $tv) {
246 if (strpos($tk, '__' . $id_rand) !== false && !empty($tv)) {
247 $row_data[$tk] = $tv;
248 }
249 }
250 }
251 }
252
253 $saved_rows[$id_rand] = $row_data;
254 }
255
256 $repeater_data['saved_values'] = $saved_rows;
257 $submitted_values[$field->id] = wp_json_encode($repeater_data);
258 }
259
260 return $submitted_values;
261 }
262 public static function remove_validation($form)
263 {
264 $zo = false;
265 $zo_datas = array();
266 foreach ($form["fields"] as $field) {
267 $type = $field->type;
268 if ($type == "repeater_start") {
269 $zo = true;
270 continue;
271 }
272 if ($type == "repeater_end") {
273 $zo = false;
274 continue;
275 }
276 if ($zo) {
277 if (!isset($field->repeater_validate)) {
278 $field->repeater_validate = array("isRequired" => $field->isRequired);
279 }
280 $field->isRequired = false;
281 $field->validateState = false;
282 }
283 }
284 return $form;
285 }
286 function validate($value, $form)
287 {
288 $datas = json_decode($value, true);
289 $list_fields_validate = array();
290 $failedValidation = false;
291 foreach ($form["fields"] as $field) {
292 if (is_array($field->repeater_validate)) {
293 foreach ($field->repeater_validate as $k => $v) {
294 if ($k == "isRequired" && $v == true) {
295 $list_fields_validate["input_" . $field->id] = $field->type;
296 }
297 }
298 }
299 }
300 if (isset($datas["id"]) && is_array($datas["id"])) {
301 // Check if file upload is attempted without the Pro version activated
302 if (!class_exists('Yeeaddons_Upgrade_GF_Repeater_Plugin')) {
303 $is_fileupload_attempt = false;
304 foreach ($datas["id"] as $id) {
305 foreach ($datas["fields"] as $field) {
306 if ($field == "") {
307 continue;
308 }
309 $field = str_replace('[]', '', $field);
310 $input_name = $field . "__" . $id;
311 // Standard file upload
312 if (isset($_FILES[$input_name]) && !empty($_FILES[$input_name]['name']) && (is_array($_FILES[$input_name]['name']) ? !empty(array_filter($_FILES[$input_name]['name'])) : true)) {
313 $is_fileupload_attempt = true;
314 break 2;
315 }
316 // Multi-file upload (Ajax/plupload)
317 if (strpos($field, 'gform_multifile_upload_') === 0 && !empty($_POST[$input_name])) {
318 $is_fileupload_attempt = true;
319 break 2;
320 }
321 }
322 }
323 if ($is_fileupload_attempt) {
324 $this->failed_validation = true;
325 $this->validation_message = esc_html__('File upload in repeater is only supported in the Pro version.', 'repeater-for-gravity-forms');
326 return;
327 }
328 }
329
330 foreach ($datas["id"] as $id) {
331 foreach ($datas["fields"] as $field) {
332 if ($field == "") {
333 continue;
334 }
335 $field = str_replace('[]', '', $field);
336 if (isset($list_fields_validate[$field])) {
337 $getInputData = rgpost($field . "__" . $id);
338 switch ($list_fields_validate[$field]) {
339 case "name":
340 $first_name = rgpost($field . "_3__" . $id);
341 $last_name = rgpost($field . "_6__" . $id);
342 if (empty($first_name) || empty($last_name)) {
343 $failedValidation = true;
344 }
345 break;
346 case 'address':
347 $address = rgpost($field . "_1__" . $id);
348 if (empty($address)) {
349 $failedValidation = true;
350 }
351 break;
352 case 'fileupload':
353 if (isset($_FILES[$field . "__" . $id]) && $_FILES[$field . "__" . $id]['name'][0]) {
354 //file uploaded
355 } else {
356 $failedValidation = true;
357 }
358 break;
359 case 'radio':
360 if (rgblank($getInputData)) {
361 $failedValidation = true;
362 } elseif ($getInputData === 'gf_other_choice') {
363 $other_val = rgpost($field . "_other__" . $id);
364 if (rgblank($other_val)) {
365 $failedValidation = true;
366 }
367 }
368 break;
369 default:
370 if (is_array($getInputData)) {
371 foreach ($getInputData as $vl) {
372 if (rgblank($vl)) {
373 $failedValidation = true;
374 }
375 }
376 } else {
377 if (rgblank($getInputData)) {
378 $failedValidation = true;
379 }
380 }
381 break;
382 }
383 }
384 }
385 }
386 if ($failedValidation) {
387 $this->failed_validation = true;
388 if ($this->errorMessage) {
389 $this->validation_message = $this->errorMessage;
390 } else {
391 $this->validation_message = esc_html__('This field is required.', 'gravityforms');
392 }
393 return;
394 } else {
395 $this->failed_validation = false;
396 }
397 }
398 }
399 function custom_validation($form)
400 {
401 $dataRepeater = array();
402 $zo = false;
403 $zo_datas = array();
404 foreach ($form["fields"] as $field) {
405 $type = $field->type;
406 if ($type == "repeater_start") {
407 $zo = true;
408 continue;
409 }
410 if ($zo) {
411 $zo_datas[$field->id] = array("isRequired" => $field->isRequired);
412 $field->isRequired = false;
413 }
414 if ($type == "repeater_end") {
415 $zo = false;
416 $zo_datas = array();
417 continue;
418 }
419 }
420 return $form;
421 }
422 /**
423 * Gets the value of the submitted field.
424 *
425 * @since Unknown
426 * @access public
427 *
428 * @used-by GFFormsModel::get_field_value()
429 * @uses GF_Field::get_value_submission()
430 * @uses GF_Field_Phone::sanitize_entry_value()
431 *
432 * @param array $field_values The dynamic population parameter names with their corresponding values to be populated.
433 * @param bool $get_from_post_global_var Whether to get the value from the $_POST array as opposed to $field_values. Defaults to true.
434 *
435 * @return array|string
436 */
437 public function get_value_submission($field_values, $get_from_post_global_var = true)
438 {
439 $value = parent::get_value_submission($field_values, $get_from_post_global_var);
440 $value = $this->sanitize_entry_value($value, $this->formId);
441 return $value;
442 }
443 /**
444 * Sanitizes the entry value.
445 *
446 * @since Unknown
447 * @access public
448 *
449 * @used-by GF_Field_Phone::get_value_save_entry()
450 * @used-by GF_Field_Phone::get_value_submission()
451 *
452 * @param string $value The value to be sanitized.
453 * @param int $form_id The form ID of the submitted item.
454 *
455 * @return string The sanitized value.
456 */
457 public function sanitize_entry_value($value, $form_id)
458 {
459 $value = is_array($value) ? array_map('sanitize_text_field', $value) : sanitize_text_field($value);
460 return $value;
461 }
462 /**
463 * Gets the field value when an entry is being saved.
464 *
465 * @since Unknown
466 * @access public
467 *
468 * @used-by GFFormsModel::prepare_value()
469 * @uses GF_Field_Phone::sanitize_entry_value()
470 * @uses GF_Field_Phone::$phoneFormat
471 *
472 * @param string $value The input value.
473 * @param array $form The Form Object.
474 * @param string $input_name The input name.
475 * @param int $lead_id The Entry ID.
476 * @param array $lead The Entry Object.
477 *
478 * @return string The field value.
479 */
480 public function get_value_save_entry($value, $form, $input_name, $lead_id, $lead)
481 {
482 $this->lead_id = $lead_id;
483 $datas = json_decode($value, true);
484 if (!is_array($datas) || !isset($datas["id"]) || !is_array($datas["id"])) {
485 return maybe_serialize(array());
486 }
487
488 $values = array();
489 $form_id = $this->formId;
490 $get_form = GFFormsModel::get_form_meta_by_id($form_id);
491 $form = $get_form[0];
492 $fields = array();
493 if (isset($datas["fields"]) && is_array($datas["fields"])) {
494 foreach ($datas["fields"] as $f) {
495 if (!is_string($f) || empty($f)) {
496 continue;
497 }
498 $datas_f = explode(".", $f);
499 $fields[] = $datas_f[0];
500 }
501 }
502 $fields = array_unique($fields);
503 foreach ($datas["id"] as $id_rand) {
504 $datas_step = array();
505 foreach ($fields as $field) {
506 $field = str_replace("[]", "", $field);
507 $inputs = $this->get_inputs($form, $field);
508 if (is_array($inputs)) {
509 $getInputData = array();
510 foreach ($inputs as $child_input) {
511 $getInputName = "input_" . $child_input["id"] . "__" . $id_rand;
512 $vl = rgpost(str_replace('.', '_', strval($getInputName)));
513 if ($vl !== "" && $vl !== false && $vl !== null) {
514 $child_field_type = $this->get_type($form, "input_" . $child_input["id"], $form_id);
515 if (is_string($vl)) {
516 if ($child_field_type === 'textarea') {
517 $vl = sanitize_textarea_field($vl);
518 } else {
519 $vl = sanitize_text_field($vl);
520 }
521 }
522 $getInputData[$child_input["id"]] = $vl;
523 }
524 }
525 $datas_step[$getInputName] = $getInputData;
526 } else {
527 $getInputName = $field . "__" . $id_rand;
528 $saved_url = '';
529
530 // 1. Check transient saved across multi-step pages by gform_unique_id or form_id
531 $unique_id = rgpost('gform_unique_id');
532 $transient_keys = array();
533 if (!empty($unique_id)) {
534 $transient_keys[] = 'gf_repeater_files_' . $unique_id;
535 }
536 $transient_keys[] = 'gf_repeater_files_' . $form_id;
537 $transient_keys[] = 'gf_repeater_files_1';
538
539 foreach ($transient_keys as $t_key) {
540 $transient_files = get_transient($t_key);
541 if (is_array($transient_files)) {
542 if (!empty($transient_files[$getInputName])) {
543 $saved_url = $transient_files[$getInputName];
544 break;
545 }
546 // Also check alternative key name format
547 foreach ($transient_files as $tk => $tv) {
548 if (strpos($tk, '__' . $id_rand) !== false && !empty($tv)) {
549 $clean_f = str_replace('gform_multifile_upload_' . $form_id . '_', '', $field);
550 $clean_f = str_replace('input_', '', $clean_f);
551 if (strpos($tk, '_' . $clean_f . '__') !== false || strpos($tk, 'input_' . $clean_f . '__') !== false) {
552 $saved_url = $tv;
553 break 2;
554 }
555 }
556 }
557 }
558 }
559
560
561 // 2. Check if $_POST contains a direct URL
562 if (empty($saved_url)) {
563 $post_val = rgpost(str_replace('.', '_', strval($getInputName)));
564 if (!empty($post_val) && is_string($post_val) && strpos($post_val, 'http') === 0) {
565 $saved_url = $post_val;
566 }
567 }
568
569 // 3. Check gform_uploaded_files (GF native temp file store)
570 if (empty($saved_url)) {
571 $uploaded_files_json = rgpost('gform_uploaded_files');
572 if (!empty($uploaded_files_json)) {
573 $uploaded_files = json_decode(stripslashes($uploaded_files_json), true);
574 if (is_array($uploaded_files) && isset($uploaded_files[$getInputName])) {
575 $file_info = $uploaded_files[$getInputName];
576 if (is_string($file_info) && strpos($file_info, 'http') === 0) {
577 $saved_url = $file_info;
578 }
579 }
580 }
581 }
582
583 // 4. Check $_FILES if uploaded on the current submit step
584 if (empty($saved_url) && isset($_FILES[$getInputName]) && !empty($_FILES[$getInputName]['name']) && (is_array($_FILES[$getInputName]['name']) ? !empty(array_filter($_FILES[$getInputName]['name'])) : ($_FILES[$getInputName]['error'] === UPLOAD_ERR_OK))) {
585 $res = apply_filters("yeeaddons_gf_repeater_file", "", $form_id, $_FILES[$getInputName]);
586 if ($res && $res !== "Upgrade to pro version") {
587 $saved_url = $res;
588 }
589 }
590
591 // 5. Fallback to $_POST value if available
592 if (empty($saved_url)) {
593 $saved_url = rgpost(str_replace('.', '_', strval($getInputName)));
594 }
595
596 // Sanitize theo kiểu trường trước khi lưu vào DB
597 $field_type = $this->get_type($form, $field, $form_id);
598 if (is_string($saved_url)) {
599 if ($field_type === 'textarea') {
600 $saved_url = sanitize_textarea_field($saved_url);
601 } elseif ($field_type === 'fileupload' || filter_var($saved_url, FILTER_VALIDATE_URL) !== false) {
602 $saved_url = esc_url_raw($saved_url);
603 } else {
604 $saved_url = sanitize_text_field($saved_url);
605 }
606 }
607
608 $datas_step[$getInputName] = $saved_url;
609 }
610 }
611 $values[$id_rand] = $datas_step;
612 }
613 return maybe_serialize($values);
614 }
615 public function upload_file($form_id, $file)
616 {
617 $target = GFFormsModel::get_file_upload_path($form_id, $file['name']);
618 if (!$target) {
619 return 'FAILED (Upload folder could not be created.)';
620 }
621 if (move_uploaded_file($file['tmp_name'], $target['path'])) {
622 $this->set_permissions($target['path']);
623 return $target['url'];
624 } else {
625 return 'FAILED (Temporary file could not be copied.)';
626 }
627 }
628 function set_permissions($path)
629 {
630 GFFormsModel::set_permissions($path);
631 }
632 public function get_value_entry_list($value, $entry, $field_id, $columns, $form)
633 {
634 if (empty($value)) {
635 return '';
636 } else {
637 $dataArray = GFFormsModel::unserialize($value);
638 $arrayCount = count($dataArray);
639 if ($arrayCount > 1) {
640 $returnText = $arrayCount . ' entries';
641 } else {
642 $returnText = $arrayCount . ' entry';
643 }
644 return $returnText;
645 }
646 }
647 function get_datas_field($format = "html", $value = '', $form_id = "")
648 {
649 global $wpdb;
650 $dataArray = GFFormsModel::unserialize($value);
651 $get_form = GFFormsModel::get_form_meta_by_id($form_id);
652 $form = $get_form[0];
653 if (isset($_GET['lid'])) {
654 $result = GFAPI::get_entry(absint($_GET['lid']));
655 } else {
656 $table = $wpdb->prefix . "gf_entry";
657 $mylink = $wpdb->get_row("SELECT id FROM $table ORDER BY id DESC");
658 $result = ($mylink && isset($mylink->id)) ? GFAPI::get_entry($mylink->id) : array();
659 }
660 if (!is_array($result) || is_wp_error($result)) {
661 $result = array();
662 }
663 if (!is_array($dataArray) || empty($dataArray)) {
664 return '';
665 }
666 if ($format === 'html') {
667 $html = '<ol>';
668 foreach ($dataArray as $step_datas) {
669 $html .= '<li><ul>';
670 foreach ($step_datas as $name => $vl) {
671 $type = $this->get_type($form, $name, $form_id);
672 $lb = $this->get_custom_field_label($form, $name, $type, false, $form_id);
673 if (is_array($vl)) {
674 switch ($type) {
675 case "address":
676 $vl_data = "";
677 foreach ($vl as $k => $v) {
678 $child_lb = $this->get_custom_field_label($form, "input_" . $k, $type, true);
679 $vl_data .= esc_html($child_lb) . ": " . esc_html($v) . "<br>";
680 }
681 $html .= '<li>' . esc_html($lb) . ": <br>" . $vl_data . "</li>";
682 break;
683 default:
684 $vl_data = implode(", ", array_map('esc_html', $vl));
685 $html .= '<li>' . esc_html($lb) . ": " . $vl_data . "</li>";
686 break;
687 }
688 } else {
689 $vl_data = $vl;
690
691 // Helper functions kiểm tra và render file/hình ảnh
692 $encode_url = function ($url) {
693 return preg_replace_callback('/[^\x20-\x7E]/u', function ($match) {
694 return rawurlencode($match[0]);
695 }, trim($url));
696 };
697
698 $is_valid_url = function ($url) use ($encode_url) {
699 if (empty($url))
700 return false;
701 $encoded = $encode_url($url);
702 return filter_var($encoded, FILTER_VALIDATE_URL) !== false;
703 };
704
705 $is_image_url = function ($url) {
706 if (!is_string($url) || empty($url))
707 return false;
708 $clean_path = parse_url($url, PHP_URL_PATH);
709 if (!$clean_path) {
710 $clean_path = $url;
711 }
712 $ext = strtolower(pathinfo($clean_path, PATHINFO_EXTENSION));
713 return in_array($ext, array('jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico', 'avif', 'heic'), true);
714 };
715
716 $render_file_or_image = function ($file_url, $display_name = '') use ($is_image_url) {
717 $file_url = trim($file_url);
718 if (empty($file_url))
719 return '';
720 $filename = !empty($display_name) ? $display_name : basename($file_url);
721
722 if ($is_image_url($file_url)) {
723 return '<a href="' . esc_url($file_url) . '" target="_blank" class="repeater-image-preview" style="display:inline-block; margin: 4px 6px 4px 0;">'
724 . '<img src="' . esc_url($file_url) . '" alt="' . esc_attr($filename) . '" style="max-width: 150px; max-height: 150px; object-fit: contain; border-radius: 4px; border: 1px solid #e2e8f0; display: inline-block; vertical-align: middle;" />'
725 . '</a>';
726 } else {
727 return '<a href="' . esc_url($file_url) . '" download>' . esc_html($filename) . '</a>';
728 }
729 };
730
731 switch ($type) {
732 case "fileupload":
733 if ($vl_data === "Upgrade to pro version") {
734 $html .= '<li>' . esc_html($lb) . ": " . esc_html($vl_data) . "</li>";
735 break;
736 }
737
738 // Tách chuỗi theo dấu phẩy phòng trường hợp có nhiều file
739 $parts = array_filter(array_map('trim', explode(",", $vl_data)));
740 $content = array();
741
742 // TRƯỜNG HỢP 1: Tất cả các phần tử đều là URL hợp lệ
743 $all_are_urls = !empty($parts);
744 foreach ($parts as $part) {
745 if (!$is_valid_url($part)) {
746 $all_are_urls = false;
747 break;
748 }
749 }
750
751 if ($all_are_urls) {
752 foreach ($parts as $part) {
753 $filename = basename($part);
754 $content[] = $render_file_or_image($part, $filename);
755 }
756 $html .= '<li>' . esc_html($lb) . ': ' . implode(" ", $content) . "</li>";
757 break;
758 }
759
760 // TRƯỜNG HỢP 2: Chuỗi chứa tên file hoặc URL không hợp lệ -> Tìm trong $result
761 $main_name = explode("__", $name);
762 $main_name = explode("_", $main_name[0]);
763 $main_name_id = isset($main_name[4]) ? $main_name[4] : (isset($main_name[1]) ? $main_name[1] : null);
764
765 if ($main_name_id && isset($result[$main_name_id])) {
766 $raw_uploads = $result[$main_name_id];
767 $data_uploads = is_array($raw_uploads) ? $raw_uploads : json_decode($raw_uploads, true);
768
769 if (!is_array($data_uploads)) {
770 $data_uploads = array_filter(array_map('trim', explode(",", (string) $raw_uploads)));
771 }
772
773 foreach ($parts as $n) {
774 $clean_n = sanitize_file_name($n);
775 $name_s = preg_quote(pathinfo($clean_n, PATHINFO_FILENAME), '/');
776
777 foreach ($data_uploads as $upload_file) {
778 // Regex khớp chính xác tên file gốc hoặc tên file có đánh số thứ tự
779 if (preg_match('/' . $name_s . '(\d+)?\./i', $upload_file)) {
780 $content[] = $render_file_or_image($upload_file, $clean_n);
781 break;
782 }
783 }
784 }
785 }
786
787 $html .= '<li>' . esc_html($lb) . ': ' . (!empty($content) ? implode(" ", $content) : esc_html($vl_data)) . "</li>";
788 break;
789
790 default:
791 if (is_string($vl_data) && $is_valid_url($vl_data) && $is_image_url($vl_data)) {
792 $html .= '<li>' . esc_html($lb) . ': ' . $render_file_or_image($vl_data) . "</li>";
793 } else {
794 $html .= '<li>' . esc_html($lb) . ": " . esc_html($vl_data) . "</li>";
795 }
796 break;
797 }
798 }
799 }
800 $html .= '</ul></li>';
801 }
802 $html .= '</ol>';
803 $html = apply_filters("yeeadons_gravity_forms_repeater_html", $html, $dataArray, $form);
804 return $html;
805 } else {
806 $text = '';
807 foreach ($dataArray as $step_datas) {
808 if (!is_array($step_datas)) {
809 continue;
810 }
811 foreach ($step_datas as $name => $vl) {
812 $type = $this->get_type($form, $name, $form_id);
813 $lb = $this->get_custom_field_label($form, $name, $type, false, $form_id);
814 $lb = esc_html($lb);
815
816 if (is_array($vl)) {
817 switch ($type) {
818 case "address":
819 $vl_data = "";
820 foreach ($vl as $k => $v) {
821 $child_lb = $this->get_custom_field_label($form, "input_" . $k, $type, true);
822 $vl_data .= esc_html($child_lb) . ": " . esc_html($v) . "\n";
823 }
824 $text .= $lb . " : \n" . $vl_data;
825 break;
826 default:
827 $vl_escaped = array_map('esc_html', $vl);
828 $text .= $lb . ": " . implode(", ", $vl_escaped) . "\n";
829 break;
830 }
831 } else {
832 $vl_data = $vl;
833 switch ($type) {
834 case "fileupload":
835 if ($vl_data === "Upgrade to pro version") {
836 $text .= $lb . ": " . esc_html($vl_data) . "\n";
837 break;
838 }
839 $parts = array_filter(array_map('trim', explode(",", $vl_data)));
840 $content = array();
841
842 $all_are_urls = !empty($parts);
843 foreach ($parts as $part) {
844 if (filter_var($part, FILTER_VALIDATE_URL) === false) {
845 $all_are_urls = false;
846 break;
847 }
848 }
849
850 if ($all_are_urls) {
851 foreach ($parts as $part) {
852 $filename = basename($part);
853 $content[] = esc_html($filename) . ' (' . esc_url($part) . ')';
854 }
855 $text .= $lb . ': ' . implode(", ", $content) . "\n";
856 } else {
857 $main_name = explode("__", $name);
858 $main_name = explode("_", $main_name[0]);
859 $main_name_id = isset($main_name[4]) ? $main_name[4] : (isset($main_name[1]) ? $main_name[1] : null);
860
861 if ($main_name_id && isset($result[$main_name_id])) {
862 $raw_uploads = $result[$main_name_id];
863 $data_uploads = is_array($raw_uploads) ? $raw_uploads : json_decode($raw_uploads, true);
864 if (!is_array($data_uploads)) {
865 $data_uploads = array_filter(array_map('trim', explode(",", (string)$raw_uploads)));
866 }
867
868 foreach ($parts as $n) {
869 $clean_n = sanitize_file_name($n);
870 $name_s = preg_quote(pathinfo($clean_n, PATHINFO_FILENAME), '/');
871 foreach ($data_uploads as $upload_file) {
872 if (preg_match('/' . $name_s . '(\d+)?\./i', $upload_file)) {
873 $content[] = esc_html($clean_n) . ' (' . esc_url($upload_file) . ')';
874 break;
875 }
876 }
877 }
878 }
879 $text .= $lb . ': ' . (!empty($content) ? implode(", ", $content) : esc_html($vl_data)) . "\n";
880 }
881 break;
882
883 default:
884 $text .= $lb . ": " . esc_html($vl_data) . "\n";
885 break;
886 }
887 }
888 }
889 $text .= "\n";
890 }
891 $text = apply_filters("yeeadons_gravity_forms_repeater_text", $text, $dataArray, $form);
892 return $text;
893 }
894 }
895 public function get_value_export($entry, $input_id = '', $use_text = false, $is_csv = false)
896 {
897 //Export doesn’t require encoding, but field data may require some manipulation or formatting before it is exported
898 $form_id = $entry["form_id"];
899 $return = $this->get_datas_field("text", rgar($entry, $input_id), $form_id);
900 return $return;
901 }
902 public function get_value_merge_tag($value, $input_id, $entry, $form, $modifier, $raw_value, $url_encode, $esc_html, $format, $nl2br)
903 {
904 if (empty($value)) {
905 return '';
906 }
907 $form_id = isset($form['id']) ? absint($form['id']) : null;
908 $return = $this->get_datas_field($format, $value, $form_id);
909 return $return;
910 }
911 public function get_value_entry_detail($value, $currency = '', $use_text = false, $format = 'html', $media = 'screen')
912 {
913 global $wpdb;
914 if (empty($value)) {
915 return '';
916 } else {
917 $form_id = $this->formId;
918 $return = $this->get_datas_field($format, $value, $form_id);
919 return $return;
920 }
921 }
922 function get_custom_field_label($form, $name = '', $type = '', $child = false, $form_id = '')
923 {
924 if (is_array($form)) {
925 if (!array_key_exists('fields', $form)) {
926 return false;
927 }
928 } else {
929 return false;
930 }
931 $name = str_replace("gform_multifile_upload_" . $form_id, "input", $name);
932 $names = explode("__", $name);
933 $names = explode("_", $names[0]);
934 $name = isset($names[1]) ? $names[1] : $names[0];
935 foreach ($form['fields'] as $field_key => $field_value) {
936 if (is_array($field_value->inputs)) {
937 foreach ($field_value->inputs as $children_id) {
938 if ($children_id["id"] == $name) {
939 if ($child) {
940 return $children_id["label"];
941 } else {
942 return $field_value->label;
943 }
944 }
945 }
946 } else {
947 if ($field_value->id == $name) {
948 return $field_value->label;
949 }
950 }
951 }
952 return "";
953 }
954 function get_inputs($form, $name = '')
955 {
956 if (is_array($form)) {
957 if (!array_key_exists('fields', $form)) {
958 return false;
959 }
960 } else {
961 return false;
962 }
963 $names = explode("__", $name);
964 $names = explode("_", $names[0]);
965 $name = isset($names[1]) ? $names[1] : $names[0];
966 foreach ($form['fields'] as $field_key => $field_value) {
967 if ($field_value->id == $name) {
968 if ($field_value->type == "date") {
969 return $name;
970 }
971 if (is_array($field_value->inputs)) {
972 return $field_value->inputs;
973 }
974 }
975 }
976 return false;
977 }
978 function get_type($form, $name = '', $form_id = "")
979 {
980 if (is_array($form)) {
981 if (!array_key_exists('fields', $form)) {
982 return false;
983 }
984 } else {
985 return false;
986 }
987 $name = str_replace("gform_multifile_upload_" . $form_id, "input", $name);
988 $names = explode("__", $name);
989 $names = explode("_", $names[0]);
990 $name = isset($names[1]) ? $names[1] : $names[0];
991 foreach ($form['fields'] as $field_key => $field_value) {
992 if (is_array($field_value->inputs)) {
993 foreach ($field_value->inputs as $children_id) {
994 if ($children_id["id"] == $name) {
995 return $field_value->type;
996 }
997 }
998 } else {
999 if ($field_value->id == $name) {
1000 return $field_value->type;
1001 }
1002 }
1003 }
1004 return "";
1005 }
1006
1007 public static function remove_child_fields($form)
1008 {
1009 if (!is_admin()) {
1010 return $form;
1011 }
1012
1013 $page = isset($_GET['page']) ? $_GET['page'] : '';
1014 $gf_page = isset($_GET['gf_page']) ? $_GET['gf_page'] : '';
1015
1016 $target_pages = array('gf_entries', 'gf_export');
1017 $target_gf_pages = array('select_columns', 'print-entry');
1018
1019 if (in_array($page, $target_pages) || in_array($gf_page, $target_gf_pages)) {
1020 $fields = array();
1021 $zo = false;
1022 foreach ($form["fields"] as $field) {
1023 $type = $field->type;
1024 if ($type == "repeater_start") {
1025 $zo = true;
1026 $fields[] = $field;
1027 continue;
1028 }
1029 if ($type == "repeater_end") {
1030 $zo = false;
1031 $fields[] = $field;
1032 continue;
1033 }
1034 if ($zo) {
1035 continue;
1036 }
1037 $fields[] = $field;
1038 }
1039 $form["fields"] = $fields;
1040 }
1041
1042 return $form;
1043 }
1044 }
1045 // Register the phone field with the field framework.
1046 GF_Fields::register(new Superaddons_GFRepeater_Field());
1047