PluginProbe
Repeater Fields for Gravity Forms / 3.2.3
Repeater Fields for Gravity Forms v3.2.3
3.2.3 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.3, 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