Created
February 27, 2026 22:21
-
-
Save SamuelHadsall/e3af157996a307e73e3bdeae677777a7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Gravity Forms Multi-Select Dropdown field | |
| */ | |
| class GF_Field_MultiSelect_Dropdown extends GF_Field { | |
| public $type = 'ms_dropdown'; | |
| // fix comma-in-term problem by using JSON storage | |
| public $storageType = 'json'; | |
| public $allowsPrepopulate = true; | |
| // Tell GF this field has choices (so the Choices UI shows up) | |
| public function has_choices() { | |
| return true; | |
| } | |
| public function get_input_type() { | |
| return 'multiselect'; | |
| } | |
| /** | |
| * Returns the field's form editor icon. | |
| * | |
| * This could be an icon url or a gform-icon class. | |
| * | |
| * @since 2.5 | |
| * | |
| * @return string | |
| */ | |
| public function get_form_editor_field_icon() { | |
| return 'gform-icon--multi-select'; | |
| } | |
| // Submit as an array (you already had this, keep it) | |
| public function is_value_submission_array() { | |
| return true; | |
| } | |
| public function get_form_editor_field_title() { | |
| $decklopedia = new Decklopedia(); | |
| return esc_html__('Multi-Select Dropdown', $decklopedia->get_plugin_name()); | |
| } | |
| /** | |
| * Returns the class names of the settings which should be available on the field in the form editor. | |
| * | |
| * @since Unknown | |
| * @access public | |
| * | |
| * @return array Settings available within the field editor. | |
| */ | |
| function get_form_editor_field_settings() { | |
| return array( | |
| 'conditional_logic_field_setting', | |
| 'prepopulate_field_setting', | |
| 'error_message_setting', | |
| 'label_setting', | |
| 'label_placement_setting', | |
| 'admin_label_setting', | |
| 'size_setting', | |
| 'choices_setting', | |
| 'choice_values_setting', | |
| 'rules_setting', | |
| 'visibility_setting', | |
| 'description_setting', | |
| 'css_class_setting', | |
| 'sp_msd_enable_search', | |
| 'sp_msd_enable_select_all', | |
| 'sp_msd_max_selections', | |
| 'sp_msd_display_mode', | |
| ); | |
| } | |
| /** | |
| * Indicates this field type can be used when configuring conditional logic rules. | |
| * | |
| * @return bool | |
| */ | |
| public function is_conditional_logic_supported() { | |
| return true; | |
| } | |
| public function get_form_editor_button() { | |
| return [ | |
| 'group' => 'advanced_fields', | |
| 'text' => $this->get_form_editor_field_title(), | |
| 'icon' => 'gform-icon--select' // pick any | |
| ]; | |
| } | |
| /** | |
| * Returns the field inner markup. | |
| * | |
| * @since Unknown | |
| * @access public | |
| * | |
| * @uses GF_Field_MultiSelect::is_entry_detail() | |
| * @uses GF_Field_MultiSelect::is_form_editor() | |
| * @uses GF_Field_MultiSelect::get_conditional_logic_event() | |
| * @uses GF_Field_MultiSelect::get_tabindex() | |
| * | |
| * @param array $form The Form Object currently being processed. | |
| * @param string|array $value The field value. From default/dynamic population, $_POST, or a resumed incomplete submission. | |
| * @param null|array $entry Null or the Entry Object currently being edited. | |
| * | |
| * @return string The field input HTML markup. | |
| */ | |
| public function get_field_input($form, $value = '', $entry = null) { | |
| $form_id = absint($form['id']); | |
| $field_id = intval($this->id); | |
| error_log(sprintf( | |
| 'MSD INPUT #%d raw $value=%s defaultValue=%s', | |
| (int) $this->id, | |
| is_array($value) ? json_encode($value) : (string) $value, | |
| (string) ($this->defaultValue ?? '') | |
| )); | |
| $tabindex = $this->get_tabindex(); | |
| $required = $this->isRequired ? 'aria-required="true"' : ''; | |
| $placeholder = esc_attr($this->placeholder); | |
| $is_entry_detail = $this->is_entry_detail(); | |
| $is_form_editor = $this->is_form_editor(); | |
| $dom_id = ($is_entry_detail || $is_form_editor || $form_id == 0) | |
| ? "input_{$field_id}" | |
| : "input_{$form_id}_{$field_id}"; | |
| // IMPORTANT: GPPA sets default value often, not runtime $value | |
| if (method_exists($this, 'get_value_default_if_empty')) { | |
| $value = $this->get_value_default_if_empty($value); | |
| } else { | |
| if (($value === '' || $value === null || $value === []) && !empty($this->defaultValue)) { | |
| $value = $this->defaultValue; | |
| } | |
| } | |
| // Normalize ONCE | |
| $current = is_array($value) ? $value : ($value !== '' ? (array) $value : []); | |
| // Support comma-separated string | |
| if (count($current) === 1 && is_string($current[0]) && strpos($current[0], ',') !== false) { | |
| $current = array_map('trim', explode(',', $current[0])); | |
| } | |
| $choices = (array) $this->choices; | |
| $input_name = "input_{$field_id}[]"; // array submission | |
| $enable_search = filter_var($this->sp_msd_enable_search ?? false, FILTER_VALIDATE_BOOLEAN); | |
| $enable_select_all = filter_var($this->sp_msd_enable_select_all ?? false, FILTER_VALIDATE_BOOLEAN); | |
| $data = [ | |
| 'enableSearch' => $enable_search ? '1' : '0', | |
| 'enableSelectAll' => $enable_select_all ? '1' : '0', | |
| 'maxSelections' => (isset($this->sp_msd_max_selections) && $this->sp_msd_max_selections !== '') ? (int) $this->sp_msd_max_selections : '', | |
| 'displayMode' => $this->sp_msd_display_mode ?: 'chips', | |
| 'inputName' => $input_name, | |
| ]; | |
| $data_attrs = ''; | |
| foreach ($data as $k => $v) { | |
| $data_attrs .= ' data-' . esc_attr($k) . '="' . esc_attr($v) . '"'; | |
| } | |
| $screen = function_exists('get_current_screen') ? get_current_screen() : null; | |
| // Gravity Forms → Form Editor | |
| $is_gf_editor = | |
| (isset($_GET['page']) && $_GET['page'] === 'gf_edit_forms') // GF 2.7+ | |
| || ($screen && strpos($screen->id ?? '', 'gf_edit_forms') !== false); | |
| ob_start(); ?> | |
| <div class="sp-msd gform-theme__no-reset--children" id="sp-msd-<?php echo $form_id . '-' . $field_id; ?>" <?php echo $data_attrs; ?>> | |
| <?php | |
| // If GPPA is driving this field, it often stores the computed value in a gppa_value_* inputName. | |
| // Render that input so GPPA has somewhere to write. | |
| $gppa_input_name = ''; | |
| if (!empty($this->inputName) && strpos((string)$this->inputName, 'gppa_value_') === 0) { | |
| $gppa_input_name = (string) $this->inputName; | |
| } | |
| // Render the GPPA receiver (hidden input) if applicable. | |
| // NOTE: we don't rely on $value here because GPPA may populate after render. | |
| if ($gppa_input_name) { | |
| echo '<input type="hidden" class="sp-msd__gppa" name="' . esc_attr($gppa_input_name) . '" value="" />'; | |
| } | |
| echo "<select multiple='multiple' | |
| id='" . esc_attr($dom_id) . "' | |
| name='input_" . esc_attr($field_id) . "[]' | |
| class='sp-msd__native' | |
| style='display:none;'>"; | |
| foreach ($choices as $choice) { | |
| $val = (string) $choice['value']; | |
| $text = (string) $choice['text']; | |
| $sel = in_array($val, array_map('strval', $current), true) ? " selected='selected'" : ''; | |
| echo "<option value='" . esc_attr($val) . "'{$sel}>" . esc_html($text) . "</option>"; | |
| } | |
| echo "</select>"; | |
| ?> | |
| <button type="button" | |
| class="sp-msd__button" | |
| aria-haspopup="listbox" | |
| aria-expanded="false" | |
| <?php echo $tabindex . ' ' . $required; ?>> | |
| <span class="sp-msd__placeholder"><?php echo $placeholder ?: esc_html__('Select...', 'decklopedia'); ?></span> | |
| <span class="sp-msd__chips" aria-hidden="true"> | |
| <?php if ($is_gf_editor) { | |
| echo '<span class="sp-msd__chip skeleton" aria-hidden="true"></span>'; | |
| } | |
| ?> | |
| </span> | |
| <span class="sp-msd__count" aria-hidden="true"></span> | |
| <span class="sp-msd__chevron" aria-hidden="true">▾</span> | |
| </button> | |
| <div class="sp-msd__panel" role="group" aria-label="<?php echo esc_attr($this->label); ?>"> | |
| <?php | |
| $raw_choices = is_array($this->choices) ? $this->choices : []; | |
| $choices = []; | |
| foreach ($raw_choices as $c) { | |
| if (is_array($c)) { | |
| $text = isset($c['text']) ? (string) $c['text'] : (string) reset($c); | |
| $val = isset($c['value']) && $c['value'] !== '' ? (string) $c['value'] : $text; | |
| } else { | |
| $text = (string) $c; | |
| $val = $text; | |
| } | |
| $choices[] = ['text' => $text, 'value' => $val]; | |
| } | |
| // Optional: if no choices yet (freshly added field), render an empty list gracefully | |
| if (empty($choices)) { | |
| $choices = []; // or seed with some defaults if you prefer | |
| } | |
| if ($enable_search) : ?> | |
| <div class="sp-msd__search"> | |
| <input type="text" class="sp-msd__search-input" placeholder="<?php esc_attr_e('Search...', 'decklopedia'); ?>"> | |
| </div> | |
| <?php endif; ?> | |
| <?php | |
| if ($enable_select_all) : ?> | |
| <div class="sp-msd__selectall"> | |
| <label><input type="checkbox" class="sp-msd__toggle-all"> <?php esc_html_e('Select all', 'decklopedia'); ?></label> | |
| </div> | |
| <?php endif; ?> | |
| <ul class="sp-msd__list" role="listbox" aria-multiselectable="true"> | |
| <?php foreach ($choices as $i => $choice): | |
| $val = $choice['value']; | |
| $text = $choice['text']; | |
| $checked = in_array((string) $val, array_map('strval', $current), true); | |
| $item_id = "spmsd-{$form_id}-{$field_id}-{$i}"; | |
| ?> | |
| <li class="sp-msd__item" role="option" aria-selected="<?php echo $checked ? 'true' : 'false'; ?>"> | |
| <label for="<?php echo esc_attr($item_id); ?>"> | |
| <input id="<?php echo esc_attr($item_id); ?>" | |
| type="checkbox" | |
| class="sp-msd__cb" | |
| value="<?php echo esc_attr($val); ?>" | |
| <?php checked($checked); ?>> | |
| <span class="sp-msd__label"><?php echo esc_html($text); ?></span> | |
| </label> | |
| </li> | |
| <?php endforeach; ?> | |
| </ul> | |
| <?php if ($data['maxSelections'] !== '') : ?> | |
| <div class="sp-msd__note" aria-live="polite"></div> | |
| <?php endif; ?> | |
| </div> | |
| </div> | |
| <?php | |
| return ob_get_clean(); | |
| } | |
| /** | |
| * Converts an array to a string. | |
| * | |
| * @since 2.2.3.7 Changed access to public. | |
| * @since 2.2 | |
| * @access public | |
| * | |
| * @uses \GF_Field_MultiSelect::$storageType | |
| * | |
| * @param array $value The array to convert to a string. | |
| * | |
| * @return string The converted string. | |
| */ | |
| public function to_string($value) { | |
| if ($this->storageType === 'json') { | |
| return json_encode($value); | |
| } else { | |
| return is_array($value) ? implode(',', $value) : $value; | |
| } | |
| } | |
| /** | |
| * Converts a string to an array. | |
| * | |
| * @since 2.2.3.7 Changed access to public. | |
| * @since 2.2 | |
| * @access public | |
| * | |
| * @uses \GF_Field_MultiSelect::$storageType | |
| * | |
| * @param string $value A comma-separated or JSON string to convert. | |
| * | |
| * @return array The converted array. | |
| */ | |
| public function to_array($value) { | |
| if (empty($value)) { | |
| return array(); | |
| } elseif (is_array($value)) { | |
| return $value; | |
| } elseif ($this->storageType !== 'json' || $value[0] !== '[') { | |
| return array_map('trim', explode(',', $value)); | |
| } else { | |
| $json = json_decode($value, true); | |
| return $json == null ? array() : $json; | |
| } | |
| } | |
| /** Sanitize on submission */ | |
| public function sanitize_entry_value($value, $form_id) { | |
| if (is_array($value)) { | |
| $value = array_values(array_filter(array_map('sanitize_text_field', $value), 'strlen')); | |
| } else { | |
| $value = sanitize_text_field($value); | |
| } | |
| return $value; | |
| } | |
| /** | |
| * Format the entry value for display on the entries list page. | |
| * | |
| * @since Unknown | |
| * @access public | |
| * | |
| * @param string|array $value The field value. | |
| * @param array $entry The Entry Object currently being processed. | |
| * @param string $field_id The field or input ID currently being processed. | |
| * @param array $columns The properties for the columns being displayed on the entry list page. | |
| * @param array $form The Form Object currently being processed. | |
| * | |
| * @return string $value The value of the field. Escaped. | |
| */ | |
| public function get_value_entry_list($value, $entry, $field_id, $columns, $form) { | |
| // Add space after comma-delimited values. | |
| $value = implode(', ', $this->to_array($value)); | |
| return esc_html($value); | |
| } | |
| // This method exists because get_value_save_entry wants a string for a $value - but these custom inputs have an array... so json encode them - that will be stored as the string for GF | |
| public function get_value_save_entry($value, $form, $input_name, $lead_id, $lead) { | |
| if (rgblank($value)) { | |
| return ''; | |
| } elseif (is_array($value)) { | |
| foreach ($value as &$v) { | |
| if (is_array($v)) { | |
| $v = ''; | |
| } | |
| $v = $this->sanitize_entry_value($v, $form['id']); | |
| } | |
| // this works better than imploding with a comma! | |
| return json_encode($value); | |
| } else { | |
| return $this->sanitize_entry_value($value, $form['id']); | |
| } | |
| } | |
| } | |
| GF_Fields::register(new GF_Field_MultiSelect_Dropdown()); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| class GF_Multiselect_Service { | |
| /** Register the field type with Gravity Forms */ | |
| public function register_field() { | |
| // Only proceed when GF is loaded | |
| if (! class_exists('\GFForms') || ! class_exists('\GF_Fields')) return; | |
| // Lazy-require the field class here to avoid load-order issues | |
| require_once plugin_dir_path(__FILE__) . '/GF/class-gf-field-multiselect-dropdown.php'; | |
| } | |
| public function gppa_is_supported_field($is_supported, $field) { | |
| if (is_object($field) && isset($field->type) && $field->type === 'ms_dropdown') { | |
| return true; | |
| } | |
| return $is_supported; | |
| } | |
| public function gppa_input_choices($choices, $field) { | |
| if (!is_object($field) || $field->type !== 'ms_dropdown') { | |
| return $choices; | |
| } | |
| // Normalize | |
| $out = []; | |
| foreach ((array) $choices as $c) { | |
| if (is_array($c)) { | |
| $text = isset($c['text']) ? (string) $c['text'] : (string) reset($c); | |
| $val = array_key_exists('value', $c) && $c['value'] !== '' ? (string) $c['value'] : $text; | |
| } else { | |
| $text = (string) $c; | |
| $val = $text; | |
| } | |
| $out[] = ['text' => $text, 'value' => $val]; | |
| } | |
| return $out; | |
| } | |
| /** Front-end assets only when a form contains our field */ | |
| public function enqueue_assets($form, $is_ajax) { | |
| $has = false; | |
| foreach ((array) rgar($form, 'fields') as $field) { | |
| if (is_object($field) && $field->type === 'ms_dropdown') { | |
| $has = true; | |
| break; | |
| } | |
| if (is_array($field) && rgar($field, 'type') === 'ms_dropdown') { | |
| $has = true; | |
| break; | |
| } | |
| } | |
| if (!$has) return; | |
| $ver = '1.0.0'; | |
| wp_enqueue_style( | |
| 'deck-gf-msd', | |
| DECKLOPEDIA_PLUGIN_URL . 'public/css/gf-multisel.css', | |
| [], | |
| $ver | |
| ); | |
| wp_enqueue_script( | |
| 'deck-gf-msd', | |
| DECKLOPEDIA_PLUGIN_URL . 'public/js/gf-multisel.js', | |
| ['jquery'], | |
| $ver, | |
| true | |
| ); | |
| wp_add_inline_script( | |
| 'deck-gf-msd', | |
| <<<JS | |
| try { | |
| gform.addFilter('gppa_is_supported_field', function(isSupported, field){ | |
| return field && field.type === 'ms_dropdown' ? true : isSupported; | |
| }); | |
| } catch(e) { console.warn('[ms_dropdown] GPPA frontend not present:', e); } | |
| jQuery(document).on('gppa_updated_batch_fields', function(){ | |
| // If your init needs re-binding, call it here (otherwise safe to no-op) | |
| // e.g., jQuery('.sp-msd').each(function(){ /* re-init if needed */ }); | |
| }); | |
| console.log('[ms_dropdown] GPPA frontend shim loaded'); | |
| JS, | |
| 'after' | |
| ); // ensure it runs after the main handle | |
| } | |
| public function enqueue_gf_editor_assets($hook) { | |
| // Only on GF form editor page | |
| if (empty($_GET['page']) || $_GET['page'] !== 'gf_edit_forms') return; | |
| $ver = '1.0.0'; | |
| wp_enqueue_style( | |
| 'deck-gf-msd', | |
| DECKLOPEDIA_PLUGIN_URL . 'public/css/gf-multisel.css', | |
| [], | |
| $ver | |
| ); | |
| wp_enqueue_script( | |
| 'deck-gf-msd', | |
| DECKLOPEDIA_PLUGIN_URL . 'public/js/gf-multisel.js', | |
| ['jquery'], | |
| $ver, | |
| true | |
| ); | |
| } | |
| public function enqueue_gf_editor_script($hook) { | |
| if (empty($_GET['page']) || $_GET['page'] !== 'gf_edit_forms') return; | |
| // Load after GPPA’s script. Using an empty handle with inline JS ensures a real <script>. | |
| wp_register_script('deck-gf-editor', '', ['jquery'], null, true); | |
| wp_enqueue_script('deck-gf-editor'); | |
| wp_add_inline_script( | |
| 'deck-gf-editor', | |
| <<<JS | |
| (function($){ | |
| // Make GPPA treat our custom field as supported in the editor | |
| try { | |
| gform.addFilter('gppa_is_supported_field', function(isSupported, field){ | |
| return field && field.type === 'ms_dropdown' ? true : isSupported; | |
| }); | |
| } catch(e) { | |
| console.warn('[ms_dropdown] GPPA editor not present:', e); | |
| } | |
| $(document).on('gform_load_field_settings', function (event, field, form) { | |
| // Display mode | |
| $('#sp_msd_display_mode').val(field.sp_msd_display_mode || 'chips'); | |
| // Checkboxes | |
| $('#sp_msd_enable_search').prop('checked', String(field.sp_msd_enable_search) === '1' || field.sp_msd_enable_search === true); | |
| $('#sp_msd_enable_select_all').prop('checked', String(field.sp_msd_enable_select_all) === '1' || field.sp_msd_enable_select_all === true); | |
| // Max selections | |
| $('#sp_msd_max_selections').val(field.sp_msd_max_selections || ''); | |
| }); | |
| // Runs right before GF saves the form object to the DB | |
| if (window.gform && gform.addFilter) { | |
| gform.addFilter('gform_form_editor_form_object_pre_save', function(form){ | |
| try { | |
| (form.fields || []).forEach(function(f){ | |
| if (f && f.type === 'ms_dropdown') { | |
| // Never persist inputType (this is what causes "reverting") | |
| if (typeof f.inputType !== 'undefined') delete f.inputType; | |
| } | |
| }); | |
| } catch(e) {} | |
| return form; | |
| }); | |
| } | |
| // Tell GF which settings to unhide for ms_dropdown | |
| window.fieldSettings = window.fieldSettings || {}; | |
| fieldSettings.ms_dropdown = (fieldSettings.ms_dropdown || '') | |
| + ', choices_setting, choice_values_setting' | |
| + ', sp_msd_enable_search, sp_msd_enable_select_all, sp_msd_max_selections, sp_msd_display_mode'; | |
| // Optional console pings | |
| console.log('[ms_dropdown] GF editor shim loaded (admin)'); | |
| })(jQuery); | |
| JS | |
| ); | |
| } | |
| private function is_gf_form_editor(): bool { | |
| if (! is_admin()) return false; | |
| $screen = function_exists('get_current_screen') ? get_current_screen() : null; | |
| return (isset($_GET['page']) && $_GET['page'] === 'gf_edit_forms') | |
| || ($screen && strpos((string)($screen->id ?? ''), 'gf_edit_forms') !== false); | |
| } | |
| public function dl_msd_fix_gppa_props($form, $form_id = null) { | |
| if (empty($form['fields'])) return $form; | |
| $deck_id = $this->dl_get_deck_id_from_request(); | |
| error_log('MSD DEBUG deck_id RESOLVED=' . $deck_id); | |
| foreach ($form['fields'] as &$field) { | |
| if (!is_object($field)) continue; | |
| // ONLY the real Deck ID field | |
| if ((int) $field->id === 79) { | |
| $field->allowsPrepopulate = true; | |
| $field->inputName = 'deck_id'; | |
| // Do NOT wipe it out on AJAX requests | |
| if ($deck_id > 0) { | |
| $field->defaultValue = (string) $deck_id; | |
| $field->value = (string) $deck_id; | |
| } | |
| error_log(sprintf( | |
| 'MSD DEBUG field#%d defaultValue=%s value=%s inputName=%s', | |
| (int) $field->id, | |
| (string) ($field->defaultValue ?? ''), | |
| (string) ($field->value ?? ''), | |
| (string) ($field->inputName ?? '') | |
| )); | |
| } | |
| if ($field->type !== 'ms_dropdown') continue; | |
| $field->allowsPrepopulate = true; | |
| if (!isset($field->choices) || !is_array($field->choices)) { | |
| $field->choices = []; | |
| } | |
| // Only needed for GPPA + dynamic population (front end) | |
| if (! $this->is_gf_form_editor()) { | |
| $field->inputType = 'multiselect'; | |
| } else { | |
| if (isset($field->inputType)) unset($field->inputType); | |
| } | |
| if (!empty($field->{'gppa-choices-enabled'}) && empty($field->inputName)) { | |
| $field->inputName = sprintf('input_%d', (int)$field->id); | |
| } | |
| } | |
| unset($field); | |
| return $form; | |
| } | |
| public function dl_msd_force_meta_identity($form, $form_id) { | |
| if (empty($form['fields']) || !is_array($form['fields'])) { | |
| return $form; | |
| } | |
| foreach ($form['fields'] as &$f) { | |
| if (!is_array($f)) { | |
| continue; | |
| } | |
| $looks_like_msd = | |
| ($f['type'] ?? '') === 'ms_dropdown' | |
| || ($f['inputType'] ?? '') === 'ms_dropdown' | |
| || isset($f['sp_msd_display_mode']) | |
| || isset($f['sp_msd_enable_search']) | |
| || isset($f['sp_msd_enable_select_all']) | |
| || isset($f['sp_msd_max_selections']); | |
| if (!$looks_like_msd) { | |
| continue; | |
| } | |
| // Force the field to instantiate as *your* class | |
| $f['type'] = 'ms_dropdown'; | |
| // Critical: prevent GF from instantiating/selecting a core field handler | |
| unset($f['inputType']); | |
| // Optional cleanup: if anything ever stored the wrong "inputs" schema | |
| // leave it alone unless you know it's breaking. | |
| } | |
| unset($f); | |
| return $form; | |
| } | |
| public function dl_msd_debug_meta($form) { | |
| if (empty($form['fields'])) return $form; | |
| foreach ($form['fields'] as $f) { | |
| if (!is_array($f)) continue; | |
| $id = $f['id'] ?? '?'; | |
| $type = $f['type'] ?? ''; | |
| $inputType = $f['inputType'] ?? ''; | |
| $hasProps = (isset($f['sp_msd_enable_search']) || isset($f['sp_msd_display_mode'])); | |
| if ($type === 'ms_dropdown' || $inputType === 'ms_dropdown' || $hasProps) { | |
| error_log("MSD META field #{$id} type={$type} inputType={$inputType} hasProps=" . (int)$hasProps); | |
| } | |
| } | |
| return $form; | |
| } | |
| public function dl_msd_debug_objects($form) { | |
| if (empty($form['fields'])) return $form; | |
| foreach ($form['fields'] as $f) { | |
| if (!is_object($f)) continue; | |
| $id = $f->id ?? '?'; | |
| $type = $f->type ?? ''; | |
| $inputType = $f->inputType ?? ''; | |
| if ($type === 'ms_dropdown' || $inputType === 'ms_dropdown' || property_exists($f, 'sp_msd_display_mode')) { | |
| error_log("MSD OBJ field #{$id} class=" . get_class($f) . " type={$type} inputType={$inputType}"); | |
| } | |
| } | |
| return $form; | |
| } | |
| public function dl_msd_strip_inputType_on_save($meta, $form_id, $meta_name) { | |
| if ($meta_name !== 'form_meta') { | |
| return $meta; | |
| } | |
| if (empty($meta['fields']) || !is_array($meta['fields'])) { | |
| return $meta; | |
| } | |
| foreach ($meta['fields'] as &$f) { | |
| if (!is_array($f)) continue; | |
| $is_msd = | |
| (($f['type'] ?? '') === 'ms_dropdown') | |
| || isset($f['sp_msd_display_mode']) | |
| || isset($f['sp_msd_enable_search']) | |
| || isset($f['sp_msd_enable_select_all']); | |
| if (!$is_msd) continue; | |
| // Force identity | |
| $f['type'] = 'ms_dropdown'; | |
| // The poison pill: never store this | |
| unset($f['inputType']); | |
| } | |
| unset($f); | |
| return $meta; | |
| } | |
| /** Admin field editor: extra settings blocks (optional) */ | |
| public function editor_settings_markup($position, $form_id) { | |
| if ($position != 50) return; | |
| ?> | |
| <li class="sp_msd_enable_search field_setting" style="margin-top:8px;"> | |
| <input type="checkbox" | |
| id="sp_msd_enable_search" | |
| onclick="SetFieldProperty('sp_msd_enable_search', this.checked ? 1 : 0)"> | |
| <label for="sp_msd_enable_search" class="inline"><?php esc_html_e('Enable search', 'decklopedia'); ?></label> | |
| </li> | |
| <li class="sp_msd_enable_select_all field_setting"> | |
| <input type="checkbox" | |
| id="sp_msd_enable_select_all" | |
| onclick="SetFieldProperty('sp_msd_enable_select_all', this.checked ? 1 : 0)"> | |
| <label for="sp_msd_enable_select_all" class="inline"><?php esc_html_e('Enable “Select all”', 'decklopedia'); ?></label> | |
| </li> | |
| <li class="sp_msd_max_selections field_setting"> | |
| <label for="sp_msd_max_selections"><?php esc_html_e('Max selections (optional)', 'decklopedia'); ?></label> | |
| <input type="number" id="sp_msd_max_selections" min="0" oninput="SetFieldProperty('sp_msd_max_selections', this.value)"> | |
| </li> | |
| <li class="sp_msd_display_mode field_setting"> | |
| <label for="sp_msd_display_mode"><?php esc_html_e('Display mode', 'decklopedia'); ?></label> | |
| <select id="sp_msd_display_mode" onchange="SetFieldProperty('sp_msd_display_mode', this.value)"> | |
| <option value="chips"><?php esc_html_e('Chips', 'decklopedia'); ?></option> | |
| <option value="count"><?php esc_html_e('Count only', 'decklopedia'); ?></option> | |
| </select> | |
| </li> | |
| <?php | |
| } | |
| public function editor_defaults() { ?> | |
| case 'ms_dropdown': | |
| field.placeholder = 'Select...'; | |
| field.sp_msd_display_mode = 'chips'; | |
| field.sp_msd_enable_search = false; | |
| field.sp_msd_enable_select_all = false; | |
| field.sp_msd_max_selections = ''; | |
| break; | |
| <?php } | |
| private function dl_get_deck_id_from_request(): int { | |
| // 1) direct deck_id anywhere (GET/POST) | |
| if (!empty($_REQUEST['deck_id'])) { | |
| return absint($_REQUEST['deck_id']); | |
| } | |
| // 2) parse field-values style payloads (AJAX) | |
| foreach (['gform_field_values', 'field_values', 'gppa_field_values'] as $key) { | |
| if (!empty($_REQUEST[$key])) { | |
| $vals = []; | |
| parse_str((string) $_REQUEST[$key], $vals); | |
| if (!empty($vals['deck_id'])) { | |
| return absint($vals['deck_id']); | |
| } | |
| } | |
| } | |
| // 3) hidden input fallback (GF input name) | |
| if (!empty($_REQUEST['input_79'])) { | |
| return absint($_REQUEST['input_79']); | |
| } | |
| return 0; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| private function define_services() { | |
| // GF Multiselect Dropdown | |
| $gfms = new GF_Multiselect_Service(); | |
| $this->loader->add_action('gform_loaded', $gfms, 'register_field', 5); | |
| $this->loader->add_filter('gppa_is_supported_field', $gfms, 'gppa_is_supported_field', 10, 2); | |
| $this->loader->add_filter('gppa_input_choices', $gfms, 'gppa_input_choices', 10, 2); | |
| $this->loader->add_action('gform_enqueue_scripts', $gfms, 'enqueue_assets', 10, 2); | |
| $this->loader->add_action('admin_enqueue_scripts', $gfms, 'enqueue_gf_editor_assets', 98); | |
| $this->loader->add_action('admin_enqueue_scripts', $gfms, 'enqueue_gf_editor_script', 99); | |
| $this->loader->add_filter('gform_form_post_get_meta', $gfms, 'dl_msd_force_meta_identity', 1, 2); | |
| $this->loader->add_filter('gform_pre_render', $gfms, 'dl_msd_fix_gppa_props', 1); | |
| $this->loader->add_filter('gform_pre_validation', $gfms, 'dl_msd_fix_gppa_props', 1); | |
| $this->loader->add_filter('gform_pre_submission_filter', $gfms, 'dl_msd_fix_gppa_props', 1); | |
| $this->loader->add_filter('gform_admin_pre_render', $gfms, 'dl_msd_fix_gppa_props', 1); | |
| $this->loader->add_filter('gform_form_post_get_meta', $gfms, 'dl_msd_fix_gppa_props', 1, 2); | |
| $this->loader->add_filter('gform_form_post_get_meta', $gfms, 'dl_msd_debug_meta', 999); | |
| $this->loader->add_filter('gform_pre_render', $gfms, 'dl_msd_debug_objects', 999); | |
| // Admin field editor settings (optional extras) | |
| $this->loader->add_action('gform_field_standard_settings', $gfms, 'editor_settings_markup', 10, 2); | |
| $this->loader->add_action('gform_editor_js_set_default_values', $gfms, 'editor_defaults'); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment