├── .github └── FUNDING.yml ├── class-gf-field-repeater2-end.php ├── class-gf-field-repeater2.php ├── css └── gf-repeater2.css ├── js ├── gf-repeater2-admin.js ├── gf-repeater2-admin.min.js ├── gf-repeater2.js ├── gf-repeater2.min.js └── jquery.postcapture.min.js ├── phpcs.xml ├── readme.md ├── readme.txt └── repeater2-for-gravity-forms.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | patreon: getbutterfly 4 | custom: ['https://www.buymeacoffee.com/wolffe'] 5 | -------------------------------------------------------------------------------- /class-gf-field-repeater2-end.php: -------------------------------------------------------------------------------- 1 | 31 | 36 | 37 | '; 38 | 39 | echo '
  • 40 | 45 | 46 |
  • '; 47 | } 48 | } 49 | 50 | public static function gform_appearance_settings( $position, $form_id ) { 51 | if ( (int) $position === 400 ) { 52 | echo '
  • 53 | 54 | 59 |
  • '; 60 | } 61 | } 62 | 63 | public static function gform_editor() { 64 | echo ""; 72 | } 73 | 74 | public static function gform_tooltips( $tooltips ) { 75 | $tooltips['form_field_repeater2_end_add'] = "The HTML to replace the default add button. This HTML will be placed inside of a span tag with the class 'gf_repeater2_add'."; 76 | $tooltips['form_field_repeater2_end_remove'] = "The HTML to replace the default remove button. This HTML will be placed inside of a span tag with the class 'gf_repeater2_remove'."; 77 | $tooltips['form_field_repeater2_end_hideButtons'] = "If enabled, no add and remove buttons will be displayed. This is useful if you plan on using custom javascript to trigger the repeater2."; 78 | 79 | return $tooltips; 80 | } 81 | 82 | public function get_field_content( $value, $force_frontend_label, $form ) { 83 | if (is_admin()) { 84 | $admin_buttons = $this->get_admin_buttons(); 85 | 86 | $field_content = "{$admin_buttons} 87 |
    88 |
    End Repeater
    89 |
    REPEATER
    90 |
    End of Repeater
    91 |
    "; 92 | } else { 93 | $add_html = $this->add; 94 | $remove_html = $this->remove; 95 | $hideButtons = $this->hideButtons; 96 | $tabindex = GFCommon::get_tabindex(); 97 | 98 | if (empty($add_html)) { $add_html = "\"+\""; } 99 | if (empty($remove_html)) { $remove_html = "\"-\""; } 100 | 101 | $field_content = "
    \n"; 102 | 103 | if (!$hideButtons) { 104 | $field_content .= "{$add_html}"; 105 | $field_content .= "{$remove_html}"; 106 | } 107 | 108 | $field_content .= "
    "; 109 | } 110 | return $field_content; 111 | } 112 | } 113 | GF_Fields::register(new GF_Field_Repeater2_End()); 114 | -------------------------------------------------------------------------------- /class-gf-field-repeater2.php: -------------------------------------------------------------------------------- 1 | 67 | 72 | 73 | "; 74 | 75 | echo "
  • 76 | 81 | 82 |
  • "; 83 | 84 | echo "
  • 85 | 90 | 91 |
  • "; 92 | } 93 | } 94 | 95 | public static function gform_appearance_settings($position, $form_id) { 96 | if ($position == 400) { 97 | echo "
  • 98 | 99 | 104 |
  • "; 105 | } 106 | } 107 | 108 | public static function gform_editor() { 109 | echo ""; 118 | } 119 | 120 | public static function gform_tooltips($tooltips) { 121 | $tooltips['form_field_repeater2_start'] = "The number of times the repeater2 will be repeated when the form is rendered. Leaving this field blank or setting it to a number higher than the maximum number is the same as setting it to 1."; 122 | $tooltips['form_field_repeater2_min'] = "The minimum number of times the repeater2 is allowed to be repeated. Leaving this field blank or setting it to a number higher than the maximum field is the same as setting it to 1."; 123 | $tooltips['form_field_repeater2_max'] = "The maximum number of times the repeater2 is allowed to be repeated. Leaving this field blank or setting it to a number lower than the minimum field is the same as setting it to unlimited."; 124 | $tooltips['form_field_repeater2_hideLabel'] = "If this is checked, the repeater2 label and description will not be shown to users on the form."; 125 | return $tooltips; 126 | } 127 | 128 | function validate($value, $form) { 129 | $repeater2_required = $this->repeater2RequiredChildren; 130 | 131 | if (!empty($repeater2_required)) { 132 | $dataArray = json_decode($value, true); 133 | 134 | foreach ($form['fields'] as $key=>$value) { 135 | $fieldKeys[$value['id']] = $key; 136 | 137 | if (is_array($value['inputs'])) { 138 | foreach ($value['inputs'] as $inputKey=>$inputValue) { 139 | $inputKeys[$value['id']][$inputValue['id']] = $inputKey; 140 | } 141 | } 142 | } 143 | 144 | if ($dataArray['repeatCount'] < $this->min) { 145 | $this->failed_validation = true; 146 | $this->validation_message = "A minimum number of ".$this->min." is required."; 147 | return; 148 | } 149 | 150 | if ($this->max && $dataArray['repeatCount'] > $this->max) { 151 | $this->failed_validation = true; 152 | $this->validation_message = "A maximum number of ".$this->max." is allowed."; 153 | return; 154 | } 155 | 156 | for ($i = 1; $i < $dataArray['repeatCount'] + 1; $i++) { 157 | foreach ($dataArray['children'] as $field_id=>$field) { 158 | $inputNames = $field['inputs']; 159 | $repeatSkips = rgars($field, 'conditionalLogic/skip'); 160 | 161 | 162 | if (!is_array($inputNames)) { continue; } 163 | 164 | if (is_array($repeatSkips)) { 165 | if (in_array($i, $repeatSkips) || in_array('all', $repeatSkips)) { continue; } 166 | } 167 | 168 | foreach ($inputNames as $inputName) { 169 | if (is_array($inputName)) { $inputName = reset($inputName); } 170 | 171 | if (substr($inputName, -2) == '[]') { 172 | $getInputName = substr($inputName, 0, strlen($inputName) - 2).'-'.$dataArray['repeater2Id'].'-'.$i; 173 | } else { 174 | $getInputName = $inputName.'-'.$dataArray['repeater2Id'].'-'.$i; 175 | } 176 | 177 | $getInputName = str_replace('.', '_', strval($getInputName)); 178 | $getInputData = rgpost($getInputName); 179 | $getInputIdNum = preg_split("/(_|-)/", $getInputName); 180 | 181 | if (in_array($getInputIdNum[1], $repeater2_required)) { 182 | $fieldKey = $fieldKeys[$getInputIdNum[1]]; 183 | $fieldType = $form['fields'][$fieldKey]['type']; 184 | $failedValidation = false; 185 | 186 | switch($fieldType) { 187 | case 'name': 188 | $requiredIDs = array(3, 6); 189 | if (in_array($getInputIdNum[2], $requiredIDs) && empty($getInputData)) { $failedValidation = true; } 190 | break; 191 | case 'address': 192 | $skipIDs = array(2); 193 | if (!in_array($getInputIdNum[2], $skipIDs) && empty($getInputData)) { $failedValidation = true; } 194 | break; 195 | default: 196 | if (empty($getInputData)) { $failedValidation = true; } 197 | } 198 | 199 | if ($failedValidation) { 200 | $this->failed_validation = true; 201 | if ($this->errorMessage) { $this->validation_message = $this->errorMessage; } else { $this->validation_message = "A required field was left blank."; } 202 | return; 203 | } 204 | } 205 | } 206 | } 207 | } 208 | } 209 | } 210 | 211 | public function get_field_content( $value, $force_frontend_label, $form ) { 212 | if ( is_admin() ) { 213 | $admin_buttons = $this->get_admin_buttons(); 214 | $field_content = "{$admin_buttons} 215 |
    216 |
    Begin Repeater
    217 |
    REPEATER
    218 |
    Top of Repeater
    219 |
    "; 220 | } else { 221 | $field_label = $this->get_field_label($force_frontend_label, $value); 222 | $description = $this->get_description($this->description, 'gsection_description gf_repeater2_description'); 223 | $hide_label = $this->hideLabel; 224 | $validation_message = ( $this->failed_validation && ! empty( $this->validation_message ) ) ? sprintf( "
    %s
    ", $this->validation_message ) : ''; 225 | if (!empty($field_label)) { $field_label = "

    {$field_label}

    "; } else { $field_label = ''; } 226 | if ($hide_label) { $field_label = ''; $description = ''; } 227 | $field_content = "
    {$field_label}{FIELD}
    {$description}{$validation_message}"; 228 | } 229 | return $field_content; 230 | } 231 | 232 | public function get_field_input($form, $value = '', $entry = null) { 233 | if (is_admin()) { 234 | return ''; 235 | } else { 236 | $form_id = $form['id']; 237 | $is_entry_detail = $this->is_entry_detail(); 238 | $is_form_editor = $this->is_form_editor(); 239 | $id = (int) $this->id; 240 | $field_id = $is_entry_detail || $is_form_editor || $form_id == 0 ? "input_$id" : 'input_' . $form_id . "_$id"; 241 | $tabindex = $this->get_tabindex(); 242 | $repeater2_parem = $this->inputName; 243 | $repeater2_start = $this->start; 244 | $repeater2_min = $this->min; 245 | $repeater2_max = $this->max; 246 | $repeater2_required = $this->repeater2RequiredChildren; 247 | $repeater2_children = $this->repeater2Children; 248 | 249 | if (!empty($repeater2_parem)) { 250 | $repeater2_parem_value = GFFormsModel::get_parameter_value($repeater2_parem, $value, $this); 251 | if (!empty($repeater2_parem_value)) { $repeater2_start = $repeater2_parem_value; } 252 | } 253 | 254 | if (!empty($repeater2_children)) { 255 | $repeater2_children_info = array(); 256 | $repeater2_parems = GF_Field_Repeater2::get_children_parem_values($form, $repeater2_children); 257 | 258 | foreach($repeater2_children as $repeater2_child) { 259 | $repeater2_children_info[$repeater2_child] = array(); 260 | $repeater2_child_field_index = GF_Field_Repeater2::get_field_index($form, 'id', $repeater2_child); 261 | 262 | if (!empty($repeater2_required)) { 263 | if (in_array($repeater2_child, $repeater2_required)) { 264 | $repeater2_children_info[$repeater2_child]['required'] = true; 265 | } 266 | } 267 | 268 | if (!empty($repeater2_parems)) { 269 | if (array_key_exists($repeater2_child, $repeater2_parems)) { 270 | $repeater2_children_info[$repeater2_child]['prePopulate'] = $repeater2_parems[$repeater2_child]; 271 | } 272 | } 273 | 274 | if ($repeater2_child_field_index !== false) { 275 | if ($form['fields'][$repeater2_child_field_index]['inputMask']) { 276 | $repeater2_children_info[$repeater2_child]['inputMask'] = $form['fields'][$repeater2_child_field_index]['inputMaskValue']; 277 | } elseif ($form['fields'][$repeater2_child_field_index]['type'] == 'phone' && $form['fields'][$repeater2_child_field_index]['phoneFormat'] = 'standard') { 278 | $repeater2_children_info[$repeater2_child]['inputMask'] = "(999) 999-9999"; 279 | } 280 | 281 | if ($form['fields'][$repeater2_child_field_index]['conditionalLogic']) { 282 | $repeater2_children_info[$repeater2_child]['conditionalLogic'] = $form['fields'][$repeater2_child_field_index]['conditionalLogic']; 283 | } 284 | } 285 | } 286 | 287 | $repeater2_children = $repeater2_children_info; 288 | } 289 | 290 | if (empty($value)) { 291 | $value = array(); 292 | $value['formId'] = $form_id; 293 | if (!empty($repeater2_start)) { $value['start'] = $repeater2_start; } 294 | if (!empty($repeater2_min)) { $value['min'] = $repeater2_min; } 295 | if (!empty($repeater2_max)) { $value['max'] = $repeater2_max; } 296 | if (!empty($repeater2_children)) { $value['children'] = $repeater2_children; } 297 | 298 | $value = json_encode($value); 299 | } 300 | 301 | return sprintf("", $id, $field_id, $value, $tabindex); 302 | } 303 | } 304 | 305 | public function get_value_save_entry($value, $form, $input_name, $lead_id, $lead) { 306 | $dataArray = json_decode($value, true); 307 | $value = Array(); 308 | 309 | for ($i = 1; $i < $dataArray['repeatCount'] + 1; $i++) { 310 | foreach ($dataArray['children'] as $field_id=>$field) { 311 | $inputData = Array(); 312 | 313 | if (array_key_exists('inputs', $field)) { 314 | $inputNames = $field['inputs']; 315 | $repeatSkips = rgars($field, 'conditionalLogic/skip'); 316 | 317 | 318 | if (is_array($repeatSkips)) { 319 | if (in_array($i, $repeatSkips) || in_array('all', $repeatSkips)) { continue; } 320 | } 321 | 322 | if (is_array($inputNames)) { 323 | foreach ($inputNames as $inputName) { 324 | if (substr($inputName, -2) == '[]') { 325 | $getInputName = substr($inputName, 0, strlen($inputName) - 2).'-'.$dataArray['repeater2Id'].'-'.$i; 326 | } else { 327 | $getInputName = $inputName.'-'.$dataArray['repeater2Id'].'-'.$i; 328 | } 329 | 330 | $getInputData = rgpost(str_replace('.', '_', strval($getInputName))); 331 | 332 | if (!empty($getInputData)) { 333 | if (is_array($getInputData)) { 334 | foreach ($getInputData as $theInputData) { 335 | $inputData[] = $theInputData; 336 | } 337 | } else { 338 | $inputData[] = $getInputData; 339 | } 340 | } 341 | } 342 | } 343 | } else { 344 | if (GF_Field_Repeater2::get_field_type($form, $field_id) == 'section') { $inputData = '[gfRepeater-section]'; } 345 | } 346 | 347 | $childValue[$field_id] = $inputData; 348 | } 349 | $value[$i] = $childValue; 350 | } 351 | return maybe_serialize($value); 352 | } 353 | 354 | public function get_value_entry_list($value, $entry, $field_id, $columns, $form) { 355 | if (empty($value)) { 356 | return ''; 357 | } else { 358 | $dataArray = GFFormsModel::unserialize($value); 359 | $arrayCount = count($dataArray); 360 | if ($arrayCount > 1) { $returnText = $arrayCount.' entries'; } else { $returnText = $arrayCount.' entry'; } 361 | return $returnText; 362 | } 363 | } 364 | 365 | public function get_value_entry_detail($value, $currency = '', $use_text = false, $format = 'html', $media = 'screen') { 366 | if (empty($value)) { 367 | return ''; 368 | } else { 369 | $dataArray = GFFormsModel::unserialize($value); 370 | $arrayCount = count($dataArray); 371 | $output = "\n"; 372 | $count = 0; 373 | $repeatCount = 0; 374 | $display_empty_fields = rgget('gf_display_empty_fields', $_COOKIE); 375 | $form_id = $this->formId; 376 | $get_form = GFFormsModel::get_form_meta_by_id($form_id); 377 | $form = $get_form[0]; 378 | 379 | foreach ($dataArray as $key=>$value) { 380 | $repeatCount++; 381 | $tableContents = ''; 382 | 383 | if (!empty($value) && !is_array($value)) { 384 | $save_value = $value; 385 | unset($value); 386 | $value[0] = $save_value; 387 | } 388 | 389 | foreach ($value as $childKey => $childValue) { 390 | $count++; 391 | $childValueOutput = ''; 392 | 393 | if (empty($display_empty_fields) && count((array) $childValue) == 0) { 394 | continue; 395 | } 396 | 397 | if (is_numeric($childKey)) { 398 | $field_index = GF_Field_Repeater2::get_field_index($form, 'id', $childKey); 399 | if ($field_index === false) { continue; } 400 | $entry_title = $form['fields'][$field_index]['label']; 401 | } else { 402 | $entry_title = $childKey; 403 | } 404 | 405 | $entry_title = str_replace('[gfRepeater-count]', $repeatCount, $entry_title); 406 | 407 | if ($format == 'html') { 408 | if ($childValue == '[gfRepeater-section]') { 409 | if ($media == 'email') { 410 | $tableStyling = ' style="font-size:14px;font-weight:bold;background-color:#eee;border-bottom:1px solid #dfdfdf;padding:7px 7px"'; 411 | } else { 412 | $tableStyling = ' class="entry-view-section-break"'; 413 | } 414 | } else { 415 | if ($media == 'email') { 416 | $tableStyling = ' style="background-color:#EAF2FA;font-family:sans-serif;font-size:12px;font-weight:bold"'; 417 | } else { 418 | $tableStyling = ' class="entry-view-field-name"'; 419 | } 420 | } 421 | 422 | $tableContents .= "\n".$entry_title."\n\n"; 423 | } else { 424 | $tableContents .= $entry_title.": "; 425 | } 426 | 427 | if (is_array($childValue)) { 428 | if (count($childValue) == 1) { 429 | $childValueOutput = $childValue[0]; 430 | } elseif (count($childValue) > 1) { 431 | if ($format == 'html') { 432 | if ($media == 'email') { 433 | $childValueOutput = "