PluginProbe
Repeater Fields for Gravity Forms / 3.2.0
Repeater Fields for Gravity Forms v3.2.0
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
← All changes | fields/repeater_field.php +948 -0 2.3.23.2.0 View file →
@@ -1,0 +1,948 @@
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 + $is_entry_detail = $this->is_entry_detail();
119 + $is_form_editor = $this->is_form_editor();
120 + $form_id = $form['id'];
121 + $id = intval($this->id);
122 + $field_id = $is_entry_detail || $is_form_editor || $form_id == 0 ? "input_$id" : 'input_' . $form_id . "_$id";
123 + $size = $this->size;
124 + $disabled_text = $is_form_editor ? "disabled='disabled'" : '';
125 + $class_suffix = $is_entry_detail ? '_admin' : '';
126 + $class = $size . $class_suffix . " gf-field-repeater-data hidden";
127 + $class = esc_attr($class);
128 + $instruction_div = '';
129 + $html_input_type = 'hidden';
130 + $placeholder_attribute = $this->get_field_placeholder_attribute();
131 + $required_attribute = $this->isRequired ? 'aria-required="true"' : '';
132 + $invalid_attribute = $this->failed_validation ? 'aria-invalid="true"' : 'aria-invalid="false"';
133 + $aria_describedby = $this->get_aria_describedby();
134 + $tabindex = $this->get_tabindex();
135 + $repeater_add_button = $this->field_repeater_end_text;
136 + if ($repeater_add_button == "") {
137 + $repeater_add_button = esc_attr__("Add more", "gravityforms-repeater");
138 + }
139 + $initial_rows = $this->repeater_initial_rows;
140 + if ($initial_rows == "") {
141 + $initial_rows = 1;
142 + }
143 + $limit = $this->repeater_max;
144 + if ($limit == "" or $limit < 1) {
145 + $limit = 9999;
146 + }
147 + $limit = apply_filters("yeeaddons_gf_repeater_limit", 5, $limit);
148 + $initial_rows = apply_filters("yeeaddons_gf_repeater_initial_rows", 1, $initial_rows);
149 + $input = "<input data-initial_rows_map_check='" . $this->repeater_initial_rows_map . "' data-initial_rows_map='input_{$form_id}_" . $this->repeater_initial_rows_map . "' data-map_id='field_" . $form_id . "_" . $id . "' name='input_{$id}' id='{$field_id}' type='{$html_input_type}' value='{$value}' class='{$class}' {$tabindex} {$placeholder_attribute} {$required_attribute} {$invalid_attribute} {$disabled_text}/>";
150 + $html = '<div data-initial_rows_map_check="' . $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 . '_' . $this->repeater_initial_rows_map . '" data-map_id="field_' . $form_id . '_' . $id . '">
151 + <div class="repeater-field-warp-item">
152 + </div>
153 + <div class="repeater-field-footer"><a href="#"" class="gf-repeater-field-button-add" >' . $repeater_add_button . '</a></div>
154 + ' . $input . '
155 + <input type="hidden" class="gf-field-repeater-data-html hidden" />
156 + </div>';
157 + if (is_admin()) {
158 + $html = '<hr>End Repeater<hr>';
159 + return sprintf("<div class='ginput_container1'>%s</div>", $html);
160 + } else {
161 + return sprintf("<div class='ginput_container'>%s</div>", $html);
162 + }
163 + }
164 + public static function remove_validation($form)
165 + {
166 + $zo = false;
167 + $zo_datas = array();
168 + foreach ($form["fields"] as $field) {
169 + $type = $field->type;
170 + if ($type == "repeater_start") {
171 + $zo = true;
172 + continue;
173 + }
174 + if ($type == "repeater_end") {
175 + $zo = false;
176 + continue;
177 + }
178 + if ($zo) {
179 + if (!isset($field->repeater_validate)) {
180 + $field->repeater_validate = array("isRequired" => $field->isRequired);
181 + }
182 + $field->isRequired = false;
183 + $field->validateState = false;
184 + }
185 + }
186 + return $form;
187 + }
188 + function validate($value, $form)
189 + {
190 + $datas = json_decode($value, true);
191 + $list_fields_validate = array();
192 + $failedValidation = false;
193 + foreach ($form["fields"] as $field) {
194 + if (is_array($field->repeater_validate)) {
195 + foreach ($field->repeater_validate as $k => $v) {
196 + if ($k == "isRequired" && $v == true) {
197 + $list_fields_validate["input_" . $field->id] = $field->type;
198 + }
199 + }
200 + }
201 + }
202 + if (isset($datas["id"]) && is_array($datas["id"])) {
203 + // Check if file upload is attempted without the Pro version activated
204 + if (!class_exists('Yeeaddons_Upgrade_GF_Repeater_Plugin')) {
205 + $is_fileupload_attempt = false;
206 + foreach ($datas["id"] as $id) {
207 + foreach ($datas["fields"] as $field) {
208 + if ($field == "") {
209 + continue;
210 + }
211 + $field = str_replace('[]', '', $field);
212 + $input_name = $field . "__" . $id;
213 + // Standard file upload
214 + if (isset($_FILES[$input_name]) && !empty($_FILES[$input_name]['name']) && (is_array($_FILES[$input_name]['name']) ? !empty(array_filter($_FILES[$input_name]['name'])) : true)) {
215 + $is_fileupload_attempt = true;
216 + break 2;
217 + }
218 + // Multi-file upload (Ajax/plupload)
219 + if (strpos($field, 'gform_multifile_upload_') === 0 && !empty($_POST[$input_name])) {
220 + $is_fileupload_attempt = true;
221 + break 2;
222 + }
223 + }
224 + }
225 + if ($is_fileupload_attempt) {
226 + $this->failed_validation = true;
227 + $this->validation_message = esc_html__('File upload in repeater is only supported in the Pro version.', 'repeater-for-gravity-forms');
228 + return;
229 + }
230 + }
231 +
232 + foreach ($datas["id"] as $id) {
233 + foreach ($datas["fields"] as $field) {
234 + if ($field == "") {
235 + continue;
236 + }
237 + $field = str_replace('[]', '', $field);
238 + if (isset($list_fields_validate[$field])) {
239 + $getInputData = rgpost($field . "__" . $id);
240 + switch ($list_fields_validate[$field]) {
241 + case "name":
242 + $first_name = rgpost($field . "_3__" . $id);
243 + $last_name = rgpost($field . "_6__" . $id);
244 + if (empty($first_name) || empty($last_name)) {
245 + $failedValidation = true;
246 + }
247 + break;
248 + case 'address':
249 + $address = rgpost($field . "_1__" . $id);
250 + if (empty($address)) {
251 + $failedValidation = true;
252 + }
253 + break;
254 + case 'fileupload':
255 + if (isset($_FILES[$field . "__" . $id]) && $_FILES[$field . "__" . $id]['name'][0]) {
256 + //file uploaded
257 + } else {
258 + $failedValidation = true;
259 + }
260 + break;
261 + case 'radio':
262 + if (rgblank($getInputData)) {
263 + $failedValidation = true;
264 + } elseif ($getInputData === 'gf_other_choice') {
265 + $other_val = rgpost($field . "_other__" . $id);
266 + if (rgblank($other_val)) {
267 + $failedValidation = true;
268 + }
269 + }
270 + break;
271 + default:
272 + if (is_array($getInputData)) {
273 + foreach ($getInputData as $vl) {
274 + if (rgblank($vl)) {
275 + $failedValidation = true;
276 + }
277 + }
278 + } else {
279 + if (rgblank($getInputData)) {
280 + $failedValidation = true;
281 + }
282 + }
283 + break;
284 + }
285 + }
286 + }
287 + }
288 + if ($failedValidation) {
289 + $this->failed_validation = true;
290 + if ($this->errorMessage) {
291 + $this->validation_message = $this->errorMessage;
292 + } else {
293 + $this->validation_message = esc_html__('This field is required.', 'gravityforms');
294 + }
295 + return;
296 + } else {
297 + $this->failed_validation = false;
298 + }
299 + }
300 + }
301 + function custom_validation($form)
302 + {
303 + $dataRepeater = array();
304 + $zo = false;
305 + $zo_datas = array();
306 + foreach ($form["fields"] as $field) {
307 + $type = $field->type;
308 + if ($type == "repeater_start") {
309 + $zo = true;
310 + continue;
311 + }
312 + if ($zo) {
313 + $zo_datas[$field->id] = array("isRequired" => $field->isRequired);
314 + $field->isRequired = false;
315 + }
316 + if ($type == "repeater_end") {
317 + $zo = false;
318 + $zo_datas = array();
319 + continue;
320 + }
321 + }
322 + return $form;
323 + }
324 + /**
325 + * Gets the value of the submitted field.
326 + *
327 + * @since Unknown
328 + * @access public
329 + *
330 + * @used-by GFFormsModel::get_field_value()
331 + * @uses GF_Field::get_value_submission()
332 + * @uses GF_Field_Phone::sanitize_entry_value()
333 + *
334 + * @param array $field_values The dynamic population parameter names with their corresponding values to be populated.
335 + * @param bool $get_from_post_global_var Whether to get the value from the $_POST array as opposed to $field_values. Defaults to true.
336 + *
337 + * @return array|string
338 + */
339 + public function get_value_submission($field_values, $get_from_post_global_var = true)
340 + {
341 + $value = parent::get_value_submission($field_values, $get_from_post_global_var);
342 + $value = $this->sanitize_entry_value($value, $this->formId);
343 + return $value;
344 + }
345 + /**
346 + * Sanitizes the entry value.
347 + *
348 + * @since Unknown
349 + * @access public
350 + *
351 + * @used-by GF_Field_Phone::get_value_save_entry()
352 + * @used-by GF_Field_Phone::get_value_submission()
353 + *
354 + * @param string $value The value to be sanitized.
355 + * @param int $form_id The form ID of the submitted item.
356 + *
357 + * @return string The sanitized value.
358 + */
359 + public function sanitize_entry_value($value, $form_id)
360 + {
361 + $value = is_array($value) ? array_map('sanitize_text_field', $value) : sanitize_text_field($value);
362 + return $value;
363 + }
364 + /**
365 + * Gets the field value when an entry is being saved.
366 + *
367 + * @since Unknown
368 + * @access public
369 + *
370 + * @used-by GFFormsModel::prepare_value()
371 + * @uses GF_Field_Phone::sanitize_entry_value()
372 + * @uses GF_Field_Phone::$phoneFormat
373 + *
374 + * @param string $value The input value.
375 + * @param array $form The Form Object.
376 + * @param string $input_name The input name.
377 + * @param int $lead_id The Entry ID.
378 + * @param array $lead The Entry Object.
379 + *
380 + * @return string The field value.
381 + */
382 + public function get_value_save_entry($value, $form, $input_name, $lead_id, $lead)
383 + {
384 + $this->lead_id = $lead_id;
385 + $datas = json_decode($value, true);
386 + if (!is_array($datas) || !isset($datas["id"]) || !is_array($datas["id"])) {
387 + return maybe_serialize(array());
388 + }
389 +
390 + $values = array();
391 + $form_id = $this->formId;
392 + $get_form = GFFormsModel::get_form_meta_by_id($form_id);
393 + $form = $get_form[0];
394 + $fields = array();
395 + if (isset($datas["fields"]) && is_array($datas["fields"])) {
396 + foreach ($datas["fields"] as $f) {
397 + if (!is_string($f) || empty($f)) {
398 + continue;
399 + }
400 + $datas_f = explode(".", $f);
401 + $fields[] = $datas_f[0];
402 + }
403 + }
404 + $fields = array_unique($fields);
405 + foreach ($datas["id"] as $id_rand) {
406 + $datas_step = array();
407 + foreach ($fields as $field) {
408 + $field = str_replace("[]", "", $field);
409 + $inputs = $this->get_inputs($form, $field);
410 + if (is_array($inputs)) {
411 + $getInputData = array();
412 + foreach ($inputs as $child_input) {
413 + $getInputName = "input_" . $child_input["id"] . "__" . $id_rand;
414 + $vl = rgpost(str_replace('.', '_', strval($getInputName)));
415 + if ($vl !== "" && $vl !== false && $vl !== null) {
416 + $child_field_type = $this->get_type($form, "input_" . $child_input["id"], $form_id);
417 + if (is_string($vl)) {
418 + if ($child_field_type === 'textarea') {
419 + $vl = sanitize_textarea_field($vl);
420 + } else {
421 + $vl = sanitize_text_field($vl);
422 + }
423 + }
424 + $getInputData[$child_input["id"]] = $vl;
425 + }
426 + }
427 + $datas_step[$getInputName] = $getInputData;
428 + } else {
429 + $getInputName = $field . "__" . $id_rand;
430 + $saved_url = '';
431 +
432 + // 1. Check transient saved across multi-step pages by gform_unique_id or form_id
433 + $unique_id = rgpost('gform_unique_id');
434 + $transient_keys = array();
435 + if (!empty($unique_id)) {
436 + $transient_keys[] = 'gf_repeater_files_' . $unique_id;
437 + }
438 + $transient_keys[] = 'gf_repeater_files_' . $form_id;
439 + $transient_keys[] = 'gf_repeater_files_1';
440 +
441 + foreach ($transient_keys as $t_key) {
442 + $transient_files = get_transient($t_key);
443 + if (is_array($transient_files)) {
444 + if (!empty($transient_files[$getInputName])) {
445 + $saved_url = $transient_files[$getInputName];
446 + break;
447 + }
448 + // Also check alternative key name format
449 + foreach ($transient_files as $tk => $tv) {
450 + if (strpos($tk, '__' . $id_rand) !== false && !empty($tv)) {
451 + $clean_f = str_replace('gform_multifile_upload_' . $form_id . '_', '', $field);
452 + $clean_f = str_replace('input_', '', $clean_f);
453 + if (strpos($tk, '_' . $clean_f . '__') !== false || strpos($tk, 'input_' . $clean_f . '__') !== false) {
454 + $saved_url = $tv;
455 + break 2;
456 + }
457 + }
458 + }
459 + }
460 + }
461 +
462 +
463 + // 2. Check if $_POST contains a direct URL
464 + if (empty($saved_url)) {
465 + $post_val = rgpost(str_replace('.', '_', strval($getInputName)));
466 + if (!empty($post_val) && is_string($post_val) && strpos($post_val, 'http') === 0) {
467 + $saved_url = $post_val;
468 + }
469 + }
470 +
471 + // 3. Check gform_uploaded_files (GF native temp file store)
472 + if (empty($saved_url)) {
473 + $uploaded_files_json = rgpost('gform_uploaded_files');
474 + if (!empty($uploaded_files_json)) {
475 + $uploaded_files = json_decode(stripslashes($uploaded_files_json), true);
476 + if (is_array($uploaded_files) && isset($uploaded_files[$getInputName])) {
477 + $file_info = $uploaded_files[$getInputName];
478 + if (is_string($file_info) && strpos($file_info, 'http') === 0) {
479 + $saved_url = $file_info;
480 + }
481 + }
482 + }
483 + }
484 +
485 + // 4. Check $_FILES if uploaded on the current submit step
486 + 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))) {
487 + $res = apply_filters("yeeaddons_gf_repeater_file", "", $form_id, $_FILES[$getInputName]);
488 + if ($res && $res !== "Upgrade to pro version") {
489 + $saved_url = $res;
490 + }
491 + }
492 +
493 + // 5. Fallback to $_POST value if available
494 + if (empty($saved_url)) {
495 + $saved_url = rgpost(str_replace('.', '_', strval($getInputName)));
496 + }
497 +
498 + // Sanitize theo kiểu trường trước khi lưu vào DB
499 + $field_type = $this->get_type($form, $field, $form_id);
500 + if (is_string($saved_url)) {
501 + if ($field_type === 'textarea') {
502 + $saved_url = sanitize_textarea_field($saved_url);
503 + } elseif ($field_type === 'fileupload' || filter_var($saved_url, FILTER_VALIDATE_URL) !== false) {
504 + $saved_url = esc_url_raw($saved_url);
505 + } else {
506 + $saved_url = sanitize_text_field($saved_url);
507 + }
508 + }
509 +
510 + $datas_step[$getInputName] = $saved_url;
511 + }
512 + }
513 + $values[$id_rand] = $datas_step;
514 + }
515 + return maybe_serialize($values);
516 + }
517 + public function upload_file($form_id, $file)
518 + {
519 + $target = GFFormsModel::get_file_upload_path($form_id, $file['name']);
520 + if (!$target) {
521 + return 'FAILED (Upload folder could not be created.)';
522 + }
523 + if (move_uploaded_file($file['tmp_name'], $target['path'])) {
524 + $this->set_permissions($target['path']);
525 + return $target['url'];
526 + } else {
527 + return 'FAILED (Temporary file could not be copied.)';
528 + }
529 + }
530 + function set_permissions($path)
531 + {
532 + GFFormsModel::set_permissions($path);
533 + }
534 + public function get_value_entry_list($value, $entry, $field_id, $columns, $form)
535 + {
536 + if (empty($value)) {
537 + return '';
538 + } else {
539 + $dataArray = GFFormsModel::unserialize($value);
540 + $arrayCount = count($dataArray);
541 + if ($arrayCount > 1) {
542 + $returnText = $arrayCount . ' entries';
543 + } else {
544 + $returnText = $arrayCount . ' entry';
545 + }
546 + return $returnText;
547 + }
548 + }
549 + function get_datas_field($format = "html", $value = '', $form_id = "")
550 + {
551 + global $wpdb;
552 + $dataArray = GFFormsModel::unserialize($value);
553 + $get_form = GFFormsModel::get_form_meta_by_id($form_id);
554 + $form = $get_form[0];
555 + if (isset($_GET['lid'])) {
556 + $result = GFAPI::get_entry(absint($_GET['lid']));
557 + } else {
558 + $table = $wpdb->prefix . "gf_entry";
559 + $mylink = $wpdb->get_row("SELECT id FROM $table ORDER BY id DESC");
560 + $result = ($mylink && isset($mylink->id)) ? GFAPI::get_entry($mylink->id) : array();
561 + }
562 + if (!is_array($result) || is_wp_error($result)) {
563 + $result = array();
564 + }
565 + if (!is_array($dataArray) || empty($dataArray)) {
566 + return '';
567 + }
568 + if ($format === 'html') {
569 + $html = '<ol>';
570 + foreach ($dataArray as $step_datas) {
571 + $html .= '<li><ul>';
572 + foreach ($step_datas as $name => $vl) {
573 + $type = $this->get_type($form, $name, $form_id);
574 + $lb = $this->get_custom_field_label($form, $name, $type, false, $form_id);
575 + if (is_array($vl)) {
576 + switch ($type) {
577 + case "address":
578 + $vl_data = "";
579 + foreach ($vl as $k => $v) {
580 + $child_lb = $this->get_custom_field_label($form, "input_" . $k, $type, true);
581 + $vl_data .= esc_html($child_lb) . ": " . esc_html($v) . "<br>";
582 + }
583 + $html .= '<li>' . esc_html($lb) . ": <br>" . $vl_data . "</li>";
584 + break;
585 + default:
586 + $vl_data = implode(", ", array_map('esc_html', $vl));
587 + $html .= '<li>' . esc_html($lb) . ": " . $vl_data . "</li>";
588 + break;
589 + }
590 + } else {
591 + $vl_data = $vl;
592 +
593 + // Helper functions kiểm tra và render file/hình ảnh
594 + $encode_url = function ($url) {
595 + return preg_replace_callback('/[^\x20-\x7E]/u', function ($match) {
596 + return rawurlencode($match[0]);
597 + }, trim($url));
598 + };
599 +
600 + $is_valid_url = function ($url) use ($encode_url) {
601 + if (empty($url))
602 + return false;
603 + $encoded = $encode_url($url);
604 + return filter_var($encoded, FILTER_VALIDATE_URL) !== false;
605 + };
606 +
607 + $is_image_url = function ($url) {
608 + if (!is_string($url) || empty($url))
609 + return false;
610 + $clean_path = parse_url($url, PHP_URL_PATH);
611 + if (!$clean_path) {
612 + $clean_path = $url;
613 + }
614 + $ext = strtolower(pathinfo($clean_path, PATHINFO_EXTENSION));
615 + return in_array($ext, array('jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico', 'avif', 'heic'), true);
616 + };
617 +
618 + $render_file_or_image = function ($file_url, $display_name = '') use ($is_image_url) {
619 + $file_url = trim($file_url);
620 + if (empty($file_url))
621 + return '';
622 + $filename = !empty($display_name) ? $display_name : basename($file_url);
623 +
624 + if ($is_image_url($file_url)) {
625 + return '<a href="' . esc_url($file_url) . '" target="_blank" class="repeater-image-preview" style="display:inline-block; margin: 4px 6px 4px 0;">'
626 + . '<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;" />'
627 + . '</a>';
628 + } else {
629 + return '<a href="' . esc_url($file_url) . '" download>' . esc_html($filename) . '</a>';
630 + }
631 + };
632 +
633 + switch ($type) {
634 + case "fileupload":
635 + if ($vl_data === "Upgrade to pro version") {
636 + $html .= '<li>' . esc_html($lb) . ": " . esc_html($vl_data) . "</li>";
637 + break;
638 + }
639 +
640 + // Tách chuỗi theo dấu phẩy phòng trường hợp có nhiều file
641 + $parts = array_filter(array_map('trim', explode(",", $vl_data)));
642 + $content = array();
643 +
644 + // TRƯỜNG HỢP 1: Tất cả các phần tử đều là URL hợp lệ
645 + $all_are_urls = !empty($parts);
646 + foreach ($parts as $part) {
647 + if (!$is_valid_url($part)) {
648 + $all_are_urls = false;
649 + break;
650 + }
651 + }
652 +
653 + if ($all_are_urls) {
654 + foreach ($parts as $part) {
655 + $filename = basename($part);
656 + $content[] = $render_file_or_image($part, $filename);
657 + }
658 + $html .= '<li>' . esc_html($lb) . ': ' . implode(" ", $content) . "</li>";
659 + break;
660 + }
661 +
662 + // TRƯỜNG HỢP 2: Chuỗi chứa tên file hoặc URL không hợp lệ -> Tìm trong $result
663 + $main_name = explode("__", $name);
664 + $main_name = explode("_", $main_name[0]);
665 + $main_name_id = isset($main_name[4]) ? $main_name[4] : (isset($main_name[1]) ? $main_name[1] : null);
666 +
667 + if ($main_name_id && isset($result[$main_name_id])) {
668 + $raw_uploads = $result[$main_name_id];
669 + $data_uploads = is_array($raw_uploads) ? $raw_uploads : json_decode($raw_uploads, true);
670 +
671 + if (!is_array($data_uploads)) {
672 + $data_uploads = array_filter(array_map('trim', explode(",", (string) $raw_uploads)));
673 + }
674 +
675 + foreach ($parts as $n) {
676 + $clean_n = sanitize_file_name($n);
677 + $name_s = preg_quote(pathinfo($clean_n, PATHINFO_FILENAME), '/');
678 +
679 + foreach ($data_uploads as $upload_file) {
680 + // Regex khớp chính xác tên file gốc hoặc tên file có đánh số thứ tự
681 + if (preg_match('/' . $name_s . '(\d+)?\./i', $upload_file)) {
682 + $content[] = $render_file_or_image($upload_file, $clean_n);
683 + break;
684 + }
685 + }
686 + }
687 + }
688 +
689 + $html .= '<li>' . esc_html($lb) . ': ' . (!empty($content) ? implode(" ", $content) : esc_html($vl_data)) . "</li>";
690 + break;
691 +
692 + default:
693 + if (is_string($vl_data) && $is_valid_url($vl_data) && $is_image_url($vl_data)) {
694 + $html .= '<li>' . esc_html($lb) . ': ' . $render_file_or_image($vl_data) . "</li>";
695 + } else {
696 + $html .= '<li>' . esc_html($lb) . ": " . esc_html($vl_data) . "</li>";
697 + }
698 + break;
699 + }
700 + }
701 + }
702 + $html .= '</ul></li>';
703 + }
704 + $html .= '</ol>';
705 + $html = apply_filters("yeeadons_gravity_forms_repeater_html", $html, $dataArray, $form);
706 + return $html;
707 + } else {
708 + $text = '';
709 + foreach ($dataArray as $step_datas) {
710 + if (!is_array($step_datas)) {
711 + continue;
712 + }
713 + foreach ($step_datas as $name => $vl) {
714 + $type = $this->get_type($form, $name, $form_id);
715 + $lb = $this->get_custom_field_label($form, $name, $type, false, $form_id);
716 + $lb = esc_html($lb);
717 +
718 + if (is_array($vl)) {
719 + switch ($type) {
720 + case "address":
721 + $vl_data = "";
722 + foreach ($vl as $k => $v) {
723 + $child_lb = $this->get_custom_field_label($form, "input_" . $k, $type, true);
724 + $vl_data .= esc_html($child_lb) . ": " . esc_html($v) . "\n";
725 + }
726 + $text .= $lb . " : \n" . $vl_data;
727 + break;
728 + default:
729 + $vl_escaped = array_map('esc_html', $vl);
730 + $text .= $lb . ": " . implode(", ", $vl_escaped) . "\n";
731 + break;
732 + }
733 + } else {
734 + $vl_data = $vl;
735 + switch ($type) {
736 + case "fileupload":
737 + if ($vl_data === "Upgrade to pro version") {
738 + $text .= $lb . ": " . esc_html($vl_data) . "\n";
739 + break;
740 + }
741 + $parts = array_filter(array_map('trim', explode(",", $vl_data)));
742 + $content = array();
743 +
744 + $all_are_urls = !empty($parts);
745 + foreach ($parts as $part) {
746 + if (filter_var($part, FILTER_VALIDATE_URL) === false) {
747 + $all_are_urls = false;
748 + break;
749 + }
750 + }
751 +
752 + if ($all_are_urls) {
753 + foreach ($parts as $part) {
754 + $filename = basename($part);
755 + $content[] = esc_html($filename) . ' (' . esc_url($part) . ')';
756 + }
757 + $text .= $lb . ': ' . implode(", ", $content) . "\n";
758 + } else {
759 + $main_name = explode("__", $name);
760 + $main_name = explode("_", $main_name[0]);
761 + $main_name_id = isset($main_name[4]) ? $main_name[4] : (isset($main_name[1]) ? $main_name[1] : null);
762 +
763 + if ($main_name_id && isset($result[$main_name_id])) {
764 + $raw_uploads = $result[$main_name_id];
765 + $data_uploads = is_array($raw_uploads) ? $raw_uploads : json_decode($raw_uploads, true);
766 + if (!is_array($data_uploads)) {
767 + $data_uploads = array_filter(array_map('trim', explode(",", (string)$raw_uploads)));
768 + }
769 +
770 + foreach ($parts as $n) {
771 + $clean_n = sanitize_file_name($n);
772 + $name_s = preg_quote(pathinfo($clean_n, PATHINFO_FILENAME), '/');
773 + foreach ($data_uploads as $upload_file) {
774 + if (preg_match('/' . $name_s . '(\d+)?\./i', $upload_file)) {
775 + $content[] = esc_html($clean_n) . ' (' . esc_url($upload_file) . ')';
776 + break;
777 + }
778 + }
779 + }
780 + }
781 + $text .= $lb . ': ' . (!empty($content) ? implode(", ", $content) : esc_html($vl_data)) . "\n";
782 + }
783 + break;
784 +
785 + default:
786 + $text .= $lb . ": " . esc_html($vl_data) . "\n";
787 + break;
788 + }
789 + }
790 + }
791 + $text .= "\n";
792 + }
793 + $text = apply_filters("yeeadons_gravity_forms_repeater_text", $text, $dataArray, $form);
794 + return $text;
795 + }
796 + }
797 + public function get_value_export($entry, $input_id = '', $use_text = false, $is_csv = false)
798 + {
799 + //Export doesn’t require encoding, but field data may require some manipulation or formatting before it is exported
800 + $form_id = $entry["form_id"];
801 + $return = $this->get_datas_field("text", rgar($entry, $input_id), $form_id);
802 + return $return;
803 + }
804 + public function get_value_merge_tag($value, $input_id, $entry, $form, $modifier, $raw_value, $url_encode, $esc_html, $format, $nl2br)
805 + {
806 + if (empty($value)) {
807 + return '';
808 + }
809 + $form_id = isset($form['id']) ? absint($form['id']) : null;
810 + $return = $this->get_datas_field($format, $value, $form_id);
811 + return $return;
812 + }
813 + public function get_value_entry_detail($value, $currency = '', $use_text = false, $format = 'html', $media = 'screen')
814 + {
815 + global $wpdb;
816 + if (empty($value)) {
817 + return '';
818 + } else {
819 + $form_id = $this->formId;
820 + $return = $this->get_datas_field($format, $value, $form_id);
821 + return $return;
822 + }
823 + }
824 + function get_custom_field_label($form, $name = '', $type = '', $child = false, $form_id = '')
825 + {
826 + if (is_array($form)) {
827 + if (!array_key_exists('fields', $form)) {
828 + return false;
829 + }
830 + } else {
831 + return false;
832 + }
833 + $name = str_replace("gform_multifile_upload_" . $form_id, "input", $name);
834 + $names = explode("__", $name);
835 + $names = explode("_", $names[0]);
836 + $name = isset($names[1]) ? $names[1] : $names[0];
837 + foreach ($form['fields'] as $field_key => $field_value) {
838 + if (is_array($field_value->inputs)) {
839 + foreach ($field_value->inputs as $children_id) {
840 + if ($children_id["id"] == $name) {
841 + if ($child) {
842 + return $children_id["label"];
843 + } else {
844 + return $field_value->label;
845 + }
846 + }
847 + }
848 + } else {
849 + if ($field_value->id == $name) {
850 + return $field_value->label;
851 + }
852 + }
853 + }
854 + return "";
855 + }
856 + function get_inputs($form, $name = '')
857 + {
858 + if (is_array($form)) {
859 + if (!array_key_exists('fields', $form)) {
860 + return false;
861 + }
862 + } else {
863 + return false;
864 + }
865 + $names = explode("__", $name);
866 + $names = explode("_", $names[0]);
867 + $name = isset($names[1]) ? $names[1] : $names[0];
868 + foreach ($form['fields'] as $field_key => $field_value) {
869 + if ($field_value->id == $name) {
870 + if ($field_value->type == "date") {
871 + return $name;
872 + }
873 + if (is_array($field_value->inputs)) {
874 + return $field_value->inputs;
875 + }
876 + }
877 + }
878 + return false;
879 + }
880 + function get_type($form, $name = '', $form_id = "")
881 + {
882 + if (is_array($form)) {
883 + if (!array_key_exists('fields', $form)) {
884 + return false;
885 + }
886 + } else {
887 + return false;
888 + }
889 + $name = str_replace("gform_multifile_upload_" . $form_id, "input", $name);
890 + $names = explode("__", $name);
891 + $names = explode("_", $names[0]);
892 + $name = isset($names[1]) ? $names[1] : $names[0];
893 + foreach ($form['fields'] as $field_key => $field_value) {
894 + if (is_array($field_value->inputs)) {
895 + foreach ($field_value->inputs as $children_id) {
896 + if ($children_id["id"] == $name) {
897 + return $field_value->type;
898 + }
899 + }
900 + } else {
901 + if ($field_value->id == $name) {
902 + return $field_value->type;
903 + }
904 + }
905 + }
906 + return "";
907 + }
908 +
909 + public static function remove_child_fields($form)
910 + {
911 + if (!is_admin()) {
912 + return $form;
913 + }
914 +
915 + $page = isset($_GET['page']) ? $_GET['page'] : '';
916 + $gf_page = isset($_GET['gf_page']) ? $_GET['gf_page'] : '';
917 +
918 + $target_pages = array('gf_entries', 'gf_export');
919 + $target_gf_pages = array('select_columns', 'print-entry');
920 +
921 + if (in_array($page, $target_pages) || in_array($gf_page, $target_gf_pages)) {
922 + $fields = array();
923 + $zo = false;
924 + foreach ($form["fields"] as $field) {
925 + $type = $field->type;
926 + if ($type == "repeater_start") {
927 + $zo = true;
928 + $fields[] = $field;
929 + continue;
930 + }
931 + if ($type == "repeater_end") {
932 + $zo = false;
933 + $fields[] = $field;
934 + continue;
935 + }
936 + if ($zo) {
937 + continue;
938 + }
939 + $fields[] = $field;
940 + }
941 + $form["fields"] = $fields;
942 + }
943 +
944 + return $form;
945 + }
946 +}
947 +// Register the phone field with the field framework.
948 +GF_Fields::register(new Superaddons_GFRepeater_Field());