Changeset 3297921
- Timestamp:
- 05/21/2025 09:38:19 AM (9 months ago)
- Location:
- flex-fields
- Files:
-
- 14 edited
- 1 copied
-
tags/2.2.0 (copied) (copied from flex-fields/trunk)
-
tags/2.2.0/FlexFields/FlexFields_Base.php (modified) (7 diffs)
-
tags/2.2.0/FlexFields/FlexFields_Main.php (modified) (5 diffs)
-
tags/2.2.0/assets/js/flex_fields_backend.js (modified) (3 diffs)
-
tags/2.2.0/assets/js/flex_fields_frontend.js (modified) (2 diffs)
-
tags/2.2.0/flex-fields.php (modified) (1 diff)
-
tags/2.2.0/includes/flex_field.php (modified) (9 diffs)
-
tags/2.2.0/readme.txt (modified) (1 diff)
-
trunk/FlexFields/FlexFields_Base.php (modified) (7 diffs)
-
trunk/FlexFields/FlexFields_Main.php (modified) (5 diffs)
-
trunk/assets/js/flex_fields_backend.js (modified) (3 diffs)
-
trunk/assets/js/flex_fields_frontend.js (modified) (2 diffs)
-
trunk/flex-fields.php (modified) (1 diff)
-
trunk/includes/flex_field.php (modified) (9 diffs)
-
trunk/readme.txt (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
flex-fields/tags/2.2.0/FlexFields/FlexFields_Base.php
r3238435 r3297921 20 20 'checkbox' => 'flex_fields_option', 21 21 'radio' => 'flex_fields_option', 22 'taxonomy' => 'flex_fields_taxonomy', 22 23 ]; 23 24 … … 52 53 } 53 54 foreach ($tab->fields as $field) { 54 $all_fields[] = 'flex_field_' . $ff_id . '_' . $field->name; 55 $meta_key = 'flex_field_' . $ff_id . '_' . $field->name; 56 57 // If it's a repeater, we only track the outer field's name (JSON will include nested values) 58 if ($field->type === 'repeater') { 59 $all_fields[] = $meta_key; 60 } else { 61 $all_fields[] = $meta_key; 62 } 63 55 64 $value = $this->flex_fields_get_field_value($field, $type, $ff_id); 56 65 $functionName = $this->ff_get_function_name_by_type($field->type); … … 76 85 77 86 if ($type == 'post' && isset($field->name)) { 78 $ value = get_post_meta(get_the_ID(), 'flex_field_' . $ff_id . '_' . $field->name);79 $value = empty($value) ? null : $value[0];87 $meta_key = 'flex_field_' . $ff_id . '_' . $field->name; 88 $value = get_post_meta(get_the_ID(), $meta_key, true); 80 89 } elseif ($type == 'taxonomy') { 81 90 $ff_tag_id = filter_input(INPUT_GET, 'tag_ID', FILTER_SANITIZE_STRING); … … 190 199 } 191 200 201 /** 202 * Render a “Taxonomy” Flex-Fields control. 203 * 204 * @param int $id Field-group (post) ID. 205 * @param object $field Field definition. 206 * @param string|array $value Saved JSON or array. 207 * @param string|null $name Parent-repeater name (if inside a repeater). 208 * 209 * @return string HTML for meta-box output. 210 */ 211 public function flex_fields_taxonomy($id, $field, $value, $name = null): string 212 { 213 /* 1. meta-key that will be saved ------------------------------------ */ 214 $storage_name = $this->ff_generate_name($id, $field, $name); // e.g. flex_field_123_product_variations 215 216 /* 2. taxonomies for current post-type ------------------------------- */ 217 $post_type = get_post_type(); 218 $tax_objects = get_object_taxonomies($post_type, 'objects'); // category, post_tag, … 219 $tax_objects = array_filter($tax_objects, fn($t) => $t->name !== 'nav_menu'); 220 221 if ( is_string( $value ) ) { 222 // convert " and friends back to real quotes before json_decode() 223 $value = html_entity_decode( $value, ENT_QUOTES, 'UTF-8' ); 224 } 225 226 /* 3. previously saved JSON → array --------------------------------- */ 227 $saved = is_array($value) ? $value : json_decode($value, true); 228 $saved = is_array($saved) ? $saved : []; 229 230 /* 4. build HTML ----------------------------------------------------- */ 231 ob_start(); ?> 232 <div class="ff-taxonomy-selector" data-storage-name="<?php echo esc_attr($storage_name); ?>"> 233 234 <?php foreach ($tax_objects as $tax): ?> 235 <?php 236 $tax_key = $tax->name; // category, post_tag … 237 $active = isset($saved[$tax_key]); 238 $saved_terms = $active ? ($saved[$tax_key]['terms'] ?? []) : []; 239 ?> 240 <div class="ff-taxonomy-block" data-taxonomy="<?php echo esc_attr($tax_key); ?>"> 241 <label> 242 <input type="checkbox" 243 class="ff-taxonomy-toggle" 244 <?php checked($active); ?> > 245 <strong><?php echo esc_html($tax->label); ?></strong> 246 </label> 247 248 <?php 249 $terms = get_terms(['taxonomy' => $tax_key, 'hide_empty' => false]); 250 if (! empty($terms) && ! is_wp_error($terms)): ?> 251 <div class="ff-taxonomy-terms" 252 style="padding-left:20px; <?php echo $active ? '' : 'display:none'; ?>"> 253 <?php foreach ($terms as $term): 254 $checked = in_array($term->slug, $saved_terms, true); ?> 255 <label style="margin-right:10px;"> 256 <input type="checkbox" 257 class="ff-term-toggle" 258 data-term="<?php echo esc_attr($term->slug); ?>" 259 <?php checked($checked); ?> > 260 <?php echo esc_html($term->name); ?> 261 </label> 262 <?php endforeach; ?> 263 </div> 264 <?php endif; ?> 265 </div><br> 266 <?php endforeach; ?> 267 268 <!-- hidden JSON actually stored in post_meta --> 269 <input type="hidden" 270 class="ff-taxonomy-storage" 271 name="<?php echo esc_attr($storage_name); ?>" 272 value="<?php echo esc_attr(json_encode($saved)); ?>"> 273 </div> 274 <?php 275 return $this->ff_item_template($field, ob_get_clean()); 276 } 277 278 192 279 public function flex_fields_repeater($id, $field, $value, $name = null): string 193 280 { 194 281 $data = json_decode($value) ?? []; 282 error_log('Decoded repeater value: ' . print_r($data, true)); 195 283 $repeaters = $field->repeater ?? []; 196 284 $count = count($repeaters) > 4 ? 4 : count($repeaters); … … 221 309 foreach ($repeaters as $repeater) { 222 310 $function = $this->ff_get_function_name_by_type($repeater->type); 223 $html .= $this->$function($id, $repeater, $item->{$repeater->name}, $field->name); 311 $value = $item->{$repeater->name} ?? ''; 312 if (is_array($value) || is_object($value)) { 313 $value = wp_json_encode($value); 314 } 315 $html .= $this->$function($id, $repeater, $value, $field->name); 224 316 } 225 317 $html .= $actions; … … 253 345 public function ff_generate_name($id, $field, $repeater = null): string 254 346 { 255 return $repeater ? $field->name : 'flex_field_' . $id . '_' . $field->name; 347 if ($repeater) { 348 return $field->name; // return raw, don't build flex_field_x_y 349 } 350 return 'flex_field_' . $id . '_' . $field->name; 256 351 } 257 352 … … 290 385 { 291 386 return [ 387 388 /* ---------- INPUT ---------- */ 292 389 'input' => [ 293 'id' => true, 390 'id' => true, 391 'class' => true, 392 'type' => true, 393 'name' => true, 394 'value' => true, 395 'placeholder' => true, 396 'checked' => true, 397 'data-term' => true, // term slug on a taxonomy checkbox 398 // keep these two if you ever add them to <input> tags: 399 'data-taxonomy' => true, 400 'data-storage-name' => true, 401 ], 402 403 /* ---------- TEXTAREA ---------- */ 404 'textarea' => [ 405 'id' => true, 294 406 'class' => true, 295 ' type'=> true,296 ' name'=> true,297 ' value'=> true,298 'placeholder' => true,299 'checked' => true, 300 ],301 ' textarea' => [302 'id' => true,407 'name' => true, 408 'rows' => true, 409 'cols' => true, 410 ], 411 412 /* ---------- SELECT / OPTION ---------- */ 413 'select' => [ 414 'id' => true, 303 415 'class' => true, 304 'name' => true, 305 'rows' => true, 306 'cols' => true, 307 ], 308 'select' => [ 309 'id' => true, 416 'name' => true, 417 ], 418 'option' => [ 419 'value' => true, 420 'selected' => true, 421 ], 422 423 /* ---------- LABEL (needed because inputs sit inside <label>) ---- */ 424 'label' => [ 425 'id' => true, 310 426 'class' => true, 311 'name' => true, 312 ], 313 'option' => [ 314 'value' => true, 315 'selected' => true, 316 ], 317 'label' => [ 318 'id' => true, 319 'class' => true, 320 'for' => true, 321 ], 427 'for' => true, 428 ], 429 430 /* ---------- DIV (taxonomy wrappers, media wrappers, etc.) ------- */ 322 431 'div' => [ 323 'id' => true, 324 'class' => true, 325 'data-type' => true, 326 'data-id' => true, 327 'style' => true, 328 ], 329 'span' => [ 330 'id' => true, 331 'class' => true, 332 ], 333 'h2' => [ 334 'id' => true, 335 'class' => true, 336 ], 337 'h4' => [ 338 'id' => true, 339 'class' => true, 340 ], 341 'button' => [ 342 'id' => true, 343 'class' => true, 344 'type' => true, 345 ], 346 'img' => [ 347 'id' => true, 348 'class' => true, 349 'src' => true, 350 'alt' => true, 351 ], 352 'a' => [ 353 'id' => true, 354 'class' => true, 355 'href' => true, 356 'target' => true, 357 ], 358 'ul' => [ 359 'id' => true, 360 'class' => true, 361 ], 362 'li' => [ 363 'id' => true, 364 'class' => true, 365 'data-tab' => true, 366 ], 367 'code' => [ 368 'id' => true, 369 'class' => true, 370 ], 371 'br' => [], 432 'id' => true, 433 'class' => true, 434 'data-type' => true, 435 'data-id' => true, 436 'style' => true, 437 'data-taxonomy' => true, // each taxonomy block 438 'data-storage-name' => true, // outer selector 439 ], 440 441 /* ---------- EVERYTHING ELSE YOU ALREADY HAD -------------------- */ 442 'span' => [ 'id' => true, 'class' => true ], 443 'h2' => [ 'id' => true, 'class' => true ], 444 'h4' => [ 'id' => true, 'class' => true ], 445 'button'=> [ 'id' => true, 'class' => true, 'type' => true ], 446 'img' => [ 'id' => true, 'class' => true, 'src' => true, 'alt' => true ], 447 'a' => [ 'id' => true, 'class' => true, 'href' => true, 'target' => true ], 448 'ul' => [ 'id' => true, 'class' => true ], 449 'li' => [ 'id' => true, 'class' => true, 'data-tab' => true ], 450 'code' => [ 'id' => true, 'class' => true ], 451 'br' => [], 372 452 ]; 373 453 } -
flex-fields/tags/2.2.0/FlexFields/FlexFields_Main.php
r3240121 r3297921 133 133 <input type="hidden" class="ff_post_excerpt" name="post_excerpt" value='<?php echo esc_attr($ff_excerpt) ?>'> 134 134 <input type="hidden" class="ff_post_content" name="post_content" 135 value='<?php echo esc_attr($post->post_content) ?>'>135 value='<?php echo esc_attr($post->post_content) ?>'> 136 136 <div class="ff-cloning-form hidden"><?php include FLEX_FIELDS_PARTIALS_DIR . 'flex_field.php' ?></div> 137 137 <div id="ff-group-rules-block"> … … 182 182 <ul class="ff-be-tabs"> 183 183 <?php 184 foreach ($tabs as $t => $tab) { 185 if($tab->id === 0) continue; 186 $active_tab = $t === 0 ? 'active' : ''; 187 echo '<li class="ff-be-tab ' . esc_attr($active_tab) . '" data-id="' . esc_attr($tab->id) . '">'; 188 echo '<span class="ff-be-tab-name">' . esc_attr($tab->name) . '</span>'; 189 echo '<span class="ff-be-tab-delete">✕</span>'; 190 echo '</li>'; 184 if (!empty($tabs)) { 185 foreach ($tabs as $t => $tab) { 186 if($tab->id === 0) continue; 187 $active_tab = $t === 0 ? 'active' : ''; 188 echo '<li class="ff-be-tab ' . esc_attr($active_tab) . '" data-id="' . esc_attr($tab->id) . '">'; 189 echo '<span class="ff-be-tab-name">' . esc_attr($tab->name) . '</span>'; 190 echo '<span class="ff-be-tab-delete">✕</span>'; 191 echo '</li>'; 192 } 191 193 } 192 194 ?> … … 197 199 <?php 198 200 if (!$create_page) { 199 foreach ($tabs as $t => $tab) { 200 $active_tab = $t === 0 ? 'active' : ''; 201 echo '<div class="ff-form-tab ' . esc_attr($active_tab) . '" data-id="' . esc_attr($tab->id) . '">'; 202 foreach ($tab->fields as $field_key => $field) { 203 include FLEX_FIELDS_PARTIALS_DIR . 'flex_field.php'; 201 if (!empty($tabs)) { 202 foreach ($tabs as $t => $tab) { 203 $active_tab = $t === 0 ? 'active' : ''; 204 echo '<div class="ff-form-tab ' . esc_attr($active_tab) . '" data-id="' . esc_attr($tab->id) . '">'; 205 foreach ($tab->fields as $field_key => $field) { 206 include FLEX_FIELDS_PARTIALS_DIR . 'flex_field.php'; 207 } 208 echo '</div>'; 204 209 } 205 echo '</div>';206 210 } 207 211 } else { … … 274 278 return $this->flex_fields_get_posts('taxonomy=' . $taxonomy); 275 279 } elseif ($current_post_type == 'page') { 276 $ff_page_id = filter_input(INPUT_GET, 'post', FILTER_SANITIZE_STRING);280 $ff_page_id = isset($_GET['post']) ? strip_tags($_GET['post']) : null; 277 281 $page_template = get_post_meta($ff_page_id, '_wp_page_template', true); 278 282 $flex_fields_query = $page_template ? 'page_template=' . stripslashes($page_template) : 'post_type=page'; 279 283 return $this->flex_fields_get_posts($flex_fields_query); 280 284 } elseif ($current_post_type == 'post') { 281 $ff_post_id = filter_input(INPUT_GET, 'post', FILTER_SANITIZE_STRING);285 $ff_post_id = isset($_GET['post']) ? strip_tags($_GET['post']) : null; 282 286 $terms = get_the_terms($ff_post_id, 'category'); 283 287 if ($terms) { … … 308 312 foreach ($flex_all_inputs as $input) { 309 313 if (isset($_POST[$input])) { 310 $flex_input = wp_kses(wp_unslash($_POST[$input]), 'post'); 314 $raw_input = wp_unslash($_POST[$input]); 315 $flex_input = is_array($raw_input) ? wp_json_encode($raw_input) : sanitize_text_field($raw_input); 316 311 317 update_post_meta($post_id, $input, $flex_input); 312 318 } -
flex-fields/tags/2.2.0/assets/js/flex_fields_backend.js
r3240121 r3297921 54 54 }; 55 55 56 ff_visibility('.ff-default', !['gallery', 'image', 'file', 'repeater' ].includes(ff_type));56 ff_visibility('.ff-default', !['gallery', 'image', 'file', 'repeater','taxonomy'].includes(ff_type)); 57 57 ff_visibility('.ff-return-types', ['gallery', 'image', 'file'].includes(ff_type)); 58 58 ff_visibility('.ff-choices', ['select', 'radio', 'checkbox'].includes(ff_type)); … … 124 124 $('.ff_post_content').val(JSON.stringify(ff_tabs)); 125 125 } 126 127 // Taxonomy toggle: show/hide term checkboxes 128 $(document).on('change', '.ff-taxonomy-toggle', function () { 129 const $toggle = $(this); 130 const $wrapper = $toggle.closest('.ff-taxonomy-block'); 131 const $termsBox = $wrapper.find('.ff-taxonomy-terms'); 132 133 if ($toggle.is(':checked')) { 134 $termsBox.slideDown(); 135 } else { 136 $termsBox.slideUp(); 137 $termsBox.find('input[type="checkbox"]').prop('checked', false); 138 } 139 }); 140 141 // Initial trigger for loaded data 142 $('.ff-taxonomy-toggle').each(function () { 143 const $toggle = $(this); 144 const $termsBox = $toggle.closest('.ff-taxonomy-block').find('.ff-taxonomy-terms'); 145 if ($toggle.is(':checked')) { 146 $termsBox.show(); 147 } else { 148 $termsBox.hide(); 149 } 150 }); 151 152 // Show/hide term lists and push selections into the hidden JSON input 153 // -- CLICK / CHANGE inside any taxonomy or term checkbox -------------- 154 jQuery(document).on('change', 155 '.ff-taxonomy-selector input[type="checkbox"]', 156 function () { 157 158 const $selector = jQuery(this).closest('.ff-taxonomy-selector'); 159 const $storage = $selector.find('.ff-taxonomy-storage'); 160 let data = {}; 161 162 $selector.find('.ff-taxonomy-block').each(function () { 163 164 const $block = jQuery(this); 165 const taxonomy = $block.attr('data-taxonomy'); // <─ attr() 166 const active = $block.find('.ff-taxonomy-toggle').is(':checked'); 167 168 if (active) { 169 data[taxonomy] = {active: true, terms: []}; 170 171 $block.find('.ff-term-toggle:checked').each(function () { 172 data[taxonomy].terms.push(jQuery(this).attr('data-term')); // <─ attr() 173 }); 174 } 175 176 // UI show / hide 177 $block.find('.ff-taxonomy-terms')[active ? 'slideDown' : 'slideUp'](); 178 if (!active) { 179 $block.find('.ff-term-toggle').prop('checked', false); 180 } 181 }); 182 183 $storage.val(JSON.stringify(data)); 184 if (typeof ff_generate_input === 'function') ff_generate_input(); 185 }); 186 187 188 // Initialise visibility on page load 189 jQuery(function ($) { 190 $('.ff-taxonomy-toggle').each(function () { 191 const $block = $(this).closest('.ff-taxonomy-block'); 192 $block.find('.ff-taxonomy-terms')[$(this).is(':checked') ? 'show' : 'hide'](); 193 }); 194 }); 195 126 196 127 197 function ff_get_tab_fields(selector){ … … 209 279 } 210 280 const ff_new_tab = $('<li class="ff-be-tab active" data-id="' + ff_tab_id + '">' + 211 '<span class="ff-be-tab-name"></span>' +212 '<span class="ff-be-tab-delete">✕</span>' +213 '</li>');281 '<span class="ff-be-tab-name"></span>' + 282 '<span class="ff-be-tab-delete">✕</span>' + 283 '</li>'); 214 284 $('.ff-be-tabs').append(ff_new_tab); 215 285 -
flex-fields/tags/2.2.0/assets/js/flex_fields_frontend.js
r3219135 r3297921 138 138 139 139 const ff_stringify = (obj) => { 140 const encodeHTML = (str) => { 141 return String(str).replace(/&/g, "&") 142 .replace(/</g, "<") 143 .replace(/>/g, ">") 144 .replace(/"/g, """) 145 .replace(/'/g, "'") 146 .replace(/\n/g, ' ') // convert newlines to placeholder 147 .replace(/"/g, '%quote%') // optional: escape quotes 148 .replace(/\\/g, '\\\\'); // escape backslashes 149 }; 150 140 151 return '[' + obj.map(item => { 141 152 const entries = Object.entries(item).map(([key, value]) => { 142 const val = value.replaceAll('"', '%quote%');153 const val = encodeHTML(value); 143 154 return `"${key}":"${val}"`; 144 155 }); … … 146 157 }).join(',') + ']'; 147 158 }; 148 149 159 $(this).find('.ff-repeater-data').val(ff_stringify(ff_repeater_items)); 150 160 }) -
flex-fields/tags/2.2.0/flex-fields.php
r3243990 r3297921 4 4 Plugin URI: https://flex-fields.com/ 5 5 Description: Customize your WordPress experience effortlessly with Flex Fields – a powerful, professional, and intuitive plugin. Tailor your content with ease, giving your website the flexibility it deserves. 6 Version: 2. 1.196 Version: 2.2.0 7 7 Requires at least: 6.6 8 8 Requires PHP: 7.0 -
flex-fields/tags/2.2.0/includes/flex_field.php
r3238435 r3297921 18 18 'gallery' => __('Gallery', 'flex-fields'), 19 19 'repeater' => __('Repeater', 'flex-fields'), 20 'taxonomy' => __('Taxonomy', 'flex-fields'), 20 21 ]; 21 22 ?> … … 45 46 <div class="ff-label"> 46 47 <input type="text" value="<?php echo esc_attr($field->label ?? '') ?>" 47 class="ff-label"48 name="label"49 placeholder="Field Label">48 class="ff-label" 49 name="label" 50 placeholder="Field Label"> 50 51 </div> 51 52 </div> … … 54 55 <div class="ff-label"> 55 56 <input type="text" value="<?php echo esc_attr($field->name ?? '') ?>" class="ff-item-name" 56 name="name" placeholder="Field Name">57 name="name" placeholder="Field Name"> 57 58 </div> 58 59 </div> … … 61 62 <div class="ff-label"> 62 63 <input type="text" value="<?php echo esc_attr($field->default ?? '') ?>" 63 name="<?php echo esc_html($ff_default) ?>" placeholder="Default Value">64 name="<?php echo esc_html($ff_default) ?>" placeholder="Default Value"> 64 65 </div> 65 66 </div> … … 67 68 <div class="ff-content"> 68 69 <div class="ff-choices" 69 style="display: <?php echo isset($field) && in_array($field->type, ['select', 'radio', 'checkbox']) ? 'block' : 'none' ?>">70 style="display: <?php echo isset($field) && in_array($field->type, ['select', 'radio', 'checkbox']) ? 'block' : 'none' ?>"> 70 71 <h4>Choices</h4> 71 72 <div> … … 79 80 ?> 80 81 <input type="hidden" class="ff-hidden-choices" name="options" 81 value='<?php echo esc_attr($ff_options) ?>'>82 value='<?php echo esc_attr($ff_options) ?>'> 82 83 <?php foreach ($ff_group_options as $option) { ?> 83 84 <div class="ff-choices-block"> … … 94 95 </div> 95 96 <div class="ff-multiselect-block" 96 style="display: <?php echo isset($field) && $field->type == 'select' ? 'block' : 'none' ?>">97 style="display: <?php echo isset($field) && $field->type == 'select' ? 'block' : 'none' ?>"> 97 98 <label for="scales"> 98 99 <input class="ff_multiselect" type="checkbox" 99 name="multiselect" <?php echo isset($field) && $field->multiselect == 'true' ? 'checked' : '' ?> />100 name="multiselect" <?php echo isset($field) && $field->multiselect == 'true' ? 'checked' : '' ?> /> 100 101 <strong>Select Multiple</strong> 101 102 </label> … … 109 110 <label> 110 111 <input type="radio" 111 value="url" <?php echo (isset($field) && $field->return_type == 'url') || !isset($field) ? 'checked' : '' ?>>112 value="url" <?php echo (isset($field) && $field->return_type == 'url') || !isset($field) ? 'checked' : '' ?>> 112 113 URL 113 114 </label> 114 115 <label> 115 116 <input type="radio" 116 value="array" <?php echo isset($field) && $field->return_type == 'array' ? 'checked' : '' ?>>117 value="array" <?php echo isset($field) && $field->return_type == 'array' ? 'checked' : '' ?>> 117 118 Array 118 119 </label> … … 125 126 <div class="ff_repeater_content"> 126 127 <div class="ff-repeaters" 127 style="display: <?php echo isset($field) && $field->type == 'repeater' ? 'block' : 'none' ?>">128 style="display: <?php echo isset($field) && $field->type == 'repeater' ? 'block' : 'none' ?>"> 128 129 <div class="ff-repeater-content-items"> 129 130 <?php -
flex-fields/tags/2.2.0/readme.txt
r3243990 r3297921 4 4 Tags: field, meta, post, template, post types 5 5 Requires at least: 5.0 6 Tested up to: 6. 76 Tested up to: 6.8 7 7 Requires PHP: 7.0 8 Stable tag: 2. 1.198 Stable tag: 2.2.0 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html -
flex-fields/trunk/FlexFields/FlexFields_Base.php
r3238435 r3297921 20 20 'checkbox' => 'flex_fields_option', 21 21 'radio' => 'flex_fields_option', 22 'taxonomy' => 'flex_fields_taxonomy', 22 23 ]; 23 24 … … 52 53 } 53 54 foreach ($tab->fields as $field) { 54 $all_fields[] = 'flex_field_' . $ff_id . '_' . $field->name; 55 $meta_key = 'flex_field_' . $ff_id . '_' . $field->name; 56 57 // If it's a repeater, we only track the outer field's name (JSON will include nested values) 58 if ($field->type === 'repeater') { 59 $all_fields[] = $meta_key; 60 } else { 61 $all_fields[] = $meta_key; 62 } 63 55 64 $value = $this->flex_fields_get_field_value($field, $type, $ff_id); 56 65 $functionName = $this->ff_get_function_name_by_type($field->type); … … 76 85 77 86 if ($type == 'post' && isset($field->name)) { 78 $ value = get_post_meta(get_the_ID(), 'flex_field_' . $ff_id . '_' . $field->name);79 $value = empty($value) ? null : $value[0];87 $meta_key = 'flex_field_' . $ff_id . '_' . $field->name; 88 $value = get_post_meta(get_the_ID(), $meta_key, true); 80 89 } elseif ($type == 'taxonomy') { 81 90 $ff_tag_id = filter_input(INPUT_GET, 'tag_ID', FILTER_SANITIZE_STRING); … … 190 199 } 191 200 201 /** 202 * Render a “Taxonomy” Flex-Fields control. 203 * 204 * @param int $id Field-group (post) ID. 205 * @param object $field Field definition. 206 * @param string|array $value Saved JSON or array. 207 * @param string|null $name Parent-repeater name (if inside a repeater). 208 * 209 * @return string HTML for meta-box output. 210 */ 211 public function flex_fields_taxonomy($id, $field, $value, $name = null): string 212 { 213 /* 1. meta-key that will be saved ------------------------------------ */ 214 $storage_name = $this->ff_generate_name($id, $field, $name); // e.g. flex_field_123_product_variations 215 216 /* 2. taxonomies for current post-type ------------------------------- */ 217 $post_type = get_post_type(); 218 $tax_objects = get_object_taxonomies($post_type, 'objects'); // category, post_tag, … 219 $tax_objects = array_filter($tax_objects, fn($t) => $t->name !== 'nav_menu'); 220 221 if ( is_string( $value ) ) { 222 // convert " and friends back to real quotes before json_decode() 223 $value = html_entity_decode( $value, ENT_QUOTES, 'UTF-8' ); 224 } 225 226 /* 3. previously saved JSON → array --------------------------------- */ 227 $saved = is_array($value) ? $value : json_decode($value, true); 228 $saved = is_array($saved) ? $saved : []; 229 230 /* 4. build HTML ----------------------------------------------------- */ 231 ob_start(); ?> 232 <div class="ff-taxonomy-selector" data-storage-name="<?php echo esc_attr($storage_name); ?>"> 233 234 <?php foreach ($tax_objects as $tax): ?> 235 <?php 236 $tax_key = $tax->name; // category, post_tag … 237 $active = isset($saved[$tax_key]); 238 $saved_terms = $active ? ($saved[$tax_key]['terms'] ?? []) : []; 239 ?> 240 <div class="ff-taxonomy-block" data-taxonomy="<?php echo esc_attr($tax_key); ?>"> 241 <label> 242 <input type="checkbox" 243 class="ff-taxonomy-toggle" 244 <?php checked($active); ?> > 245 <strong><?php echo esc_html($tax->label); ?></strong> 246 </label> 247 248 <?php 249 $terms = get_terms(['taxonomy' => $tax_key, 'hide_empty' => false]); 250 if (! empty($terms) && ! is_wp_error($terms)): ?> 251 <div class="ff-taxonomy-terms" 252 style="padding-left:20px; <?php echo $active ? '' : 'display:none'; ?>"> 253 <?php foreach ($terms as $term): 254 $checked = in_array($term->slug, $saved_terms, true); ?> 255 <label style="margin-right:10px;"> 256 <input type="checkbox" 257 class="ff-term-toggle" 258 data-term="<?php echo esc_attr($term->slug); ?>" 259 <?php checked($checked); ?> > 260 <?php echo esc_html($term->name); ?> 261 </label> 262 <?php endforeach; ?> 263 </div> 264 <?php endif; ?> 265 </div><br> 266 <?php endforeach; ?> 267 268 <!-- hidden JSON actually stored in post_meta --> 269 <input type="hidden" 270 class="ff-taxonomy-storage" 271 name="<?php echo esc_attr($storage_name); ?>" 272 value="<?php echo esc_attr(json_encode($saved)); ?>"> 273 </div> 274 <?php 275 return $this->ff_item_template($field, ob_get_clean()); 276 } 277 278 192 279 public function flex_fields_repeater($id, $field, $value, $name = null): string 193 280 { 194 281 $data = json_decode($value) ?? []; 282 error_log('Decoded repeater value: ' . print_r($data, true)); 195 283 $repeaters = $field->repeater ?? []; 196 284 $count = count($repeaters) > 4 ? 4 : count($repeaters); … … 221 309 foreach ($repeaters as $repeater) { 222 310 $function = $this->ff_get_function_name_by_type($repeater->type); 223 $html .= $this->$function($id, $repeater, $item->{$repeater->name}, $field->name); 311 $value = $item->{$repeater->name} ?? ''; 312 if (is_array($value) || is_object($value)) { 313 $value = wp_json_encode($value); 314 } 315 $html .= $this->$function($id, $repeater, $value, $field->name); 224 316 } 225 317 $html .= $actions; … … 253 345 public function ff_generate_name($id, $field, $repeater = null): string 254 346 { 255 return $repeater ? $field->name : 'flex_field_' . $id . '_' . $field->name; 347 if ($repeater) { 348 return $field->name; // return raw, don't build flex_field_x_y 349 } 350 return 'flex_field_' . $id . '_' . $field->name; 256 351 } 257 352 … … 290 385 { 291 386 return [ 387 388 /* ---------- INPUT ---------- */ 292 389 'input' => [ 293 'id' => true, 390 'id' => true, 391 'class' => true, 392 'type' => true, 393 'name' => true, 394 'value' => true, 395 'placeholder' => true, 396 'checked' => true, 397 'data-term' => true, // term slug on a taxonomy checkbox 398 // keep these two if you ever add them to <input> tags: 399 'data-taxonomy' => true, 400 'data-storage-name' => true, 401 ], 402 403 /* ---------- TEXTAREA ---------- */ 404 'textarea' => [ 405 'id' => true, 294 406 'class' => true, 295 ' type'=> true,296 ' name'=> true,297 ' value'=> true,298 'placeholder' => true,299 'checked' => true, 300 ],301 ' textarea' => [302 'id' => true,407 'name' => true, 408 'rows' => true, 409 'cols' => true, 410 ], 411 412 /* ---------- SELECT / OPTION ---------- */ 413 'select' => [ 414 'id' => true, 303 415 'class' => true, 304 'name' => true, 305 'rows' => true, 306 'cols' => true, 307 ], 308 'select' => [ 309 'id' => true, 416 'name' => true, 417 ], 418 'option' => [ 419 'value' => true, 420 'selected' => true, 421 ], 422 423 /* ---------- LABEL (needed because inputs sit inside <label>) ---- */ 424 'label' => [ 425 'id' => true, 310 426 'class' => true, 311 'name' => true, 312 ], 313 'option' => [ 314 'value' => true, 315 'selected' => true, 316 ], 317 'label' => [ 318 'id' => true, 319 'class' => true, 320 'for' => true, 321 ], 427 'for' => true, 428 ], 429 430 /* ---------- DIV (taxonomy wrappers, media wrappers, etc.) ------- */ 322 431 'div' => [ 323 'id' => true, 324 'class' => true, 325 'data-type' => true, 326 'data-id' => true, 327 'style' => true, 328 ], 329 'span' => [ 330 'id' => true, 331 'class' => true, 332 ], 333 'h2' => [ 334 'id' => true, 335 'class' => true, 336 ], 337 'h4' => [ 338 'id' => true, 339 'class' => true, 340 ], 341 'button' => [ 342 'id' => true, 343 'class' => true, 344 'type' => true, 345 ], 346 'img' => [ 347 'id' => true, 348 'class' => true, 349 'src' => true, 350 'alt' => true, 351 ], 352 'a' => [ 353 'id' => true, 354 'class' => true, 355 'href' => true, 356 'target' => true, 357 ], 358 'ul' => [ 359 'id' => true, 360 'class' => true, 361 ], 362 'li' => [ 363 'id' => true, 364 'class' => true, 365 'data-tab' => true, 366 ], 367 'code' => [ 368 'id' => true, 369 'class' => true, 370 ], 371 'br' => [], 432 'id' => true, 433 'class' => true, 434 'data-type' => true, 435 'data-id' => true, 436 'style' => true, 437 'data-taxonomy' => true, // each taxonomy block 438 'data-storage-name' => true, // outer selector 439 ], 440 441 /* ---------- EVERYTHING ELSE YOU ALREADY HAD -------------------- */ 442 'span' => [ 'id' => true, 'class' => true ], 443 'h2' => [ 'id' => true, 'class' => true ], 444 'h4' => [ 'id' => true, 'class' => true ], 445 'button'=> [ 'id' => true, 'class' => true, 'type' => true ], 446 'img' => [ 'id' => true, 'class' => true, 'src' => true, 'alt' => true ], 447 'a' => [ 'id' => true, 'class' => true, 'href' => true, 'target' => true ], 448 'ul' => [ 'id' => true, 'class' => true ], 449 'li' => [ 'id' => true, 'class' => true, 'data-tab' => true ], 450 'code' => [ 'id' => true, 'class' => true ], 451 'br' => [], 372 452 ]; 373 453 } -
flex-fields/trunk/FlexFields/FlexFields_Main.php
r3240121 r3297921 133 133 <input type="hidden" class="ff_post_excerpt" name="post_excerpt" value='<?php echo esc_attr($ff_excerpt) ?>'> 134 134 <input type="hidden" class="ff_post_content" name="post_content" 135 value='<?php echo esc_attr($post->post_content) ?>'>135 value='<?php echo esc_attr($post->post_content) ?>'> 136 136 <div class="ff-cloning-form hidden"><?php include FLEX_FIELDS_PARTIALS_DIR . 'flex_field.php' ?></div> 137 137 <div id="ff-group-rules-block"> … … 182 182 <ul class="ff-be-tabs"> 183 183 <?php 184 foreach ($tabs as $t => $tab) { 185 if($tab->id === 0) continue; 186 $active_tab = $t === 0 ? 'active' : ''; 187 echo '<li class="ff-be-tab ' . esc_attr($active_tab) . '" data-id="' . esc_attr($tab->id) . '">'; 188 echo '<span class="ff-be-tab-name">' . esc_attr($tab->name) . '</span>'; 189 echo '<span class="ff-be-tab-delete">✕</span>'; 190 echo '</li>'; 184 if (!empty($tabs)) { 185 foreach ($tabs as $t => $tab) { 186 if($tab->id === 0) continue; 187 $active_tab = $t === 0 ? 'active' : ''; 188 echo '<li class="ff-be-tab ' . esc_attr($active_tab) . '" data-id="' . esc_attr($tab->id) . '">'; 189 echo '<span class="ff-be-tab-name">' . esc_attr($tab->name) . '</span>'; 190 echo '<span class="ff-be-tab-delete">✕</span>'; 191 echo '</li>'; 192 } 191 193 } 192 194 ?> … … 197 199 <?php 198 200 if (!$create_page) { 199 foreach ($tabs as $t => $tab) { 200 $active_tab = $t === 0 ? 'active' : ''; 201 echo '<div class="ff-form-tab ' . esc_attr($active_tab) . '" data-id="' . esc_attr($tab->id) . '">'; 202 foreach ($tab->fields as $field_key => $field) { 203 include FLEX_FIELDS_PARTIALS_DIR . 'flex_field.php'; 201 if (!empty($tabs)) { 202 foreach ($tabs as $t => $tab) { 203 $active_tab = $t === 0 ? 'active' : ''; 204 echo '<div class="ff-form-tab ' . esc_attr($active_tab) . '" data-id="' . esc_attr($tab->id) . '">'; 205 foreach ($tab->fields as $field_key => $field) { 206 include FLEX_FIELDS_PARTIALS_DIR . 'flex_field.php'; 207 } 208 echo '</div>'; 204 209 } 205 echo '</div>';206 210 } 207 211 } else { … … 274 278 return $this->flex_fields_get_posts('taxonomy=' . $taxonomy); 275 279 } elseif ($current_post_type == 'page') { 276 $ff_page_id = filter_input(INPUT_GET, 'post', FILTER_SANITIZE_STRING);280 $ff_page_id = isset($_GET['post']) ? strip_tags($_GET['post']) : null; 277 281 $page_template = get_post_meta($ff_page_id, '_wp_page_template', true); 278 282 $flex_fields_query = $page_template ? 'page_template=' . stripslashes($page_template) : 'post_type=page'; 279 283 return $this->flex_fields_get_posts($flex_fields_query); 280 284 } elseif ($current_post_type == 'post') { 281 $ff_post_id = filter_input(INPUT_GET, 'post', FILTER_SANITIZE_STRING);285 $ff_post_id = isset($_GET['post']) ? strip_tags($_GET['post']) : null; 282 286 $terms = get_the_terms($ff_post_id, 'category'); 283 287 if ($terms) { … … 308 312 foreach ($flex_all_inputs as $input) { 309 313 if (isset($_POST[$input])) { 310 $flex_input = wp_kses(wp_unslash($_POST[$input]), 'post'); 314 $raw_input = wp_unslash($_POST[$input]); 315 $flex_input = is_array($raw_input) ? wp_json_encode($raw_input) : sanitize_text_field($raw_input); 316 311 317 update_post_meta($post_id, $input, $flex_input); 312 318 } -
flex-fields/trunk/assets/js/flex_fields_backend.js
r3240121 r3297921 54 54 }; 55 55 56 ff_visibility('.ff-default', !['gallery', 'image', 'file', 'repeater' ].includes(ff_type));56 ff_visibility('.ff-default', !['gallery', 'image', 'file', 'repeater','taxonomy'].includes(ff_type)); 57 57 ff_visibility('.ff-return-types', ['gallery', 'image', 'file'].includes(ff_type)); 58 58 ff_visibility('.ff-choices', ['select', 'radio', 'checkbox'].includes(ff_type)); … … 124 124 $('.ff_post_content').val(JSON.stringify(ff_tabs)); 125 125 } 126 127 // Taxonomy toggle: show/hide term checkboxes 128 $(document).on('change', '.ff-taxonomy-toggle', function () { 129 const $toggle = $(this); 130 const $wrapper = $toggle.closest('.ff-taxonomy-block'); 131 const $termsBox = $wrapper.find('.ff-taxonomy-terms'); 132 133 if ($toggle.is(':checked')) { 134 $termsBox.slideDown(); 135 } else { 136 $termsBox.slideUp(); 137 $termsBox.find('input[type="checkbox"]').prop('checked', false); 138 } 139 }); 140 141 // Initial trigger for loaded data 142 $('.ff-taxonomy-toggle').each(function () { 143 const $toggle = $(this); 144 const $termsBox = $toggle.closest('.ff-taxonomy-block').find('.ff-taxonomy-terms'); 145 if ($toggle.is(':checked')) { 146 $termsBox.show(); 147 } else { 148 $termsBox.hide(); 149 } 150 }); 151 152 // Show/hide term lists and push selections into the hidden JSON input 153 // -- CLICK / CHANGE inside any taxonomy or term checkbox -------------- 154 jQuery(document).on('change', 155 '.ff-taxonomy-selector input[type="checkbox"]', 156 function () { 157 158 const $selector = jQuery(this).closest('.ff-taxonomy-selector'); 159 const $storage = $selector.find('.ff-taxonomy-storage'); 160 let data = {}; 161 162 $selector.find('.ff-taxonomy-block').each(function () { 163 164 const $block = jQuery(this); 165 const taxonomy = $block.attr('data-taxonomy'); // <─ attr() 166 const active = $block.find('.ff-taxonomy-toggle').is(':checked'); 167 168 if (active) { 169 data[taxonomy] = {active: true, terms: []}; 170 171 $block.find('.ff-term-toggle:checked').each(function () { 172 data[taxonomy].terms.push(jQuery(this).attr('data-term')); // <─ attr() 173 }); 174 } 175 176 // UI show / hide 177 $block.find('.ff-taxonomy-terms')[active ? 'slideDown' : 'slideUp'](); 178 if (!active) { 179 $block.find('.ff-term-toggle').prop('checked', false); 180 } 181 }); 182 183 $storage.val(JSON.stringify(data)); 184 if (typeof ff_generate_input === 'function') ff_generate_input(); 185 }); 186 187 188 // Initialise visibility on page load 189 jQuery(function ($) { 190 $('.ff-taxonomy-toggle').each(function () { 191 const $block = $(this).closest('.ff-taxonomy-block'); 192 $block.find('.ff-taxonomy-terms')[$(this).is(':checked') ? 'show' : 'hide'](); 193 }); 194 }); 195 126 196 127 197 function ff_get_tab_fields(selector){ … … 209 279 } 210 280 const ff_new_tab = $('<li class="ff-be-tab active" data-id="' + ff_tab_id + '">' + 211 '<span class="ff-be-tab-name"></span>' +212 '<span class="ff-be-tab-delete">✕</span>' +213 '</li>');281 '<span class="ff-be-tab-name"></span>' + 282 '<span class="ff-be-tab-delete">✕</span>' + 283 '</li>'); 214 284 $('.ff-be-tabs').append(ff_new_tab); 215 285 -
flex-fields/trunk/assets/js/flex_fields_frontend.js
r3219135 r3297921 138 138 139 139 const ff_stringify = (obj) => { 140 const encodeHTML = (str) => { 141 return String(str).replace(/&/g, "&") 142 .replace(/</g, "<") 143 .replace(/>/g, ">") 144 .replace(/"/g, """) 145 .replace(/'/g, "'") 146 .replace(/\n/g, ' ') // convert newlines to placeholder 147 .replace(/"/g, '%quote%') // optional: escape quotes 148 .replace(/\\/g, '\\\\'); // escape backslashes 149 }; 150 140 151 return '[' + obj.map(item => { 141 152 const entries = Object.entries(item).map(([key, value]) => { 142 const val = value.replaceAll('"', '%quote%');153 const val = encodeHTML(value); 143 154 return `"${key}":"${val}"`; 144 155 }); … … 146 157 }).join(',') + ']'; 147 158 }; 148 149 159 $(this).find('.ff-repeater-data').val(ff_stringify(ff_repeater_items)); 150 160 }) -
flex-fields/trunk/flex-fields.php
r3243990 r3297921 4 4 Plugin URI: https://flex-fields.com/ 5 5 Description: Customize your WordPress experience effortlessly with Flex Fields – a powerful, professional, and intuitive plugin. Tailor your content with ease, giving your website the flexibility it deserves. 6 Version: 2. 1.196 Version: 2.2.0 7 7 Requires at least: 6.6 8 8 Requires PHP: 7.0 -
flex-fields/trunk/includes/flex_field.php
r3238435 r3297921 18 18 'gallery' => __('Gallery', 'flex-fields'), 19 19 'repeater' => __('Repeater', 'flex-fields'), 20 'taxonomy' => __('Taxonomy', 'flex-fields'), 20 21 ]; 21 22 ?> … … 45 46 <div class="ff-label"> 46 47 <input type="text" value="<?php echo esc_attr($field->label ?? '') ?>" 47 class="ff-label"48 name="label"49 placeholder="Field Label">48 class="ff-label" 49 name="label" 50 placeholder="Field Label"> 50 51 </div> 51 52 </div> … … 54 55 <div class="ff-label"> 55 56 <input type="text" value="<?php echo esc_attr($field->name ?? '') ?>" class="ff-item-name" 56 name="name" placeholder="Field Name">57 name="name" placeholder="Field Name"> 57 58 </div> 58 59 </div> … … 61 62 <div class="ff-label"> 62 63 <input type="text" value="<?php echo esc_attr($field->default ?? '') ?>" 63 name="<?php echo esc_html($ff_default) ?>" placeholder="Default Value">64 name="<?php echo esc_html($ff_default) ?>" placeholder="Default Value"> 64 65 </div> 65 66 </div> … … 67 68 <div class="ff-content"> 68 69 <div class="ff-choices" 69 style="display: <?php echo isset($field) && in_array($field->type, ['select', 'radio', 'checkbox']) ? 'block' : 'none' ?>">70 style="display: <?php echo isset($field) && in_array($field->type, ['select', 'radio', 'checkbox']) ? 'block' : 'none' ?>"> 70 71 <h4>Choices</h4> 71 72 <div> … … 79 80 ?> 80 81 <input type="hidden" class="ff-hidden-choices" name="options" 81 value='<?php echo esc_attr($ff_options) ?>'>82 value='<?php echo esc_attr($ff_options) ?>'> 82 83 <?php foreach ($ff_group_options as $option) { ?> 83 84 <div class="ff-choices-block"> … … 94 95 </div> 95 96 <div class="ff-multiselect-block" 96 style="display: <?php echo isset($field) && $field->type == 'select' ? 'block' : 'none' ?>">97 style="display: <?php echo isset($field) && $field->type == 'select' ? 'block' : 'none' ?>"> 97 98 <label for="scales"> 98 99 <input class="ff_multiselect" type="checkbox" 99 name="multiselect" <?php echo isset($field) && $field->multiselect == 'true' ? 'checked' : '' ?> />100 name="multiselect" <?php echo isset($field) && $field->multiselect == 'true' ? 'checked' : '' ?> /> 100 101 <strong>Select Multiple</strong> 101 102 </label> … … 109 110 <label> 110 111 <input type="radio" 111 value="url" <?php echo (isset($field) && $field->return_type == 'url') || !isset($field) ? 'checked' : '' ?>>112 value="url" <?php echo (isset($field) && $field->return_type == 'url') || !isset($field) ? 'checked' : '' ?>> 112 113 URL 113 114 </label> 114 115 <label> 115 116 <input type="radio" 116 value="array" <?php echo isset($field) && $field->return_type == 'array' ? 'checked' : '' ?>>117 value="array" <?php echo isset($field) && $field->return_type == 'array' ? 'checked' : '' ?>> 117 118 Array 118 119 </label> … … 125 126 <div class="ff_repeater_content"> 126 127 <div class="ff-repeaters" 127 style="display: <?php echo isset($field) && $field->type == 'repeater' ? 'block' : 'none' ?>">128 style="display: <?php echo isset($field) && $field->type == 'repeater' ? 'block' : 'none' ?>"> 128 129 <div class="ff-repeater-content-items"> 129 130 <?php -
flex-fields/trunk/readme.txt
r3243990 r3297921 4 4 Tags: field, meta, post, template, post types 5 5 Requires at least: 5.0 6 Tested up to: 6. 76 Tested up to: 6.8 7 7 Requires PHP: 7.0 8 Stable tag: 2. 1.198 Stable tag: 2.2.0 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html
Note: See TracChangeset
for help on using the changeset viewer.