Plugin Directory

Changeset 3297921


Ignore:
Timestamp:
05/21/2025 09:38:19 AM (9 months ago)
Author:
flexfields
Message:

Tagging version 2.2.0

Location:
flex-fields
Files:
14 edited
1 copied

Legend:

Unmodified
Added
Removed
  • flex-fields/tags/2.2.0/FlexFields/FlexFields_Base.php

    r3238435 r3297921  
    2020            'checkbox' => 'flex_fields_option',
    2121            'radio' => 'flex_fields_option',
     22            'taxonomy' => 'flex_fields_taxonomy',
    2223        ];
    2324
     
    5253            }
    5354            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
    5564                $value = $this->flex_fields_get_field_value($field, $type, $ff_id);
    5665                $functionName = $this->ff_get_function_name_by_type($field->type);
     
    7685
    7786        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);
    8089        } elseif ($type == 'taxonomy') {
    8190            $ff_tag_id = filter_input(INPUT_GET, 'tag_ID', FILTER_SANITIZE_STRING);
     
    190199    }
    191200
     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
    192279    public function flex_fields_repeater($id, $field, $value, $name = null): string
    193280    {
    194281        $data = json_decode($value) ?? [];
     282        error_log('Decoded repeater value: ' . print_r($data, true));
    195283        $repeaters = $field->repeater ?? [];
    196284        $count = count($repeaters) > 4 ? 4 : count($repeaters);
     
    221309                foreach ($repeaters as $repeater) {
    222310                    $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);
    224316                }
    225317                $html .= $actions;
     
    253345    public function ff_generate_name($id, $field, $repeater = null): string
    254346    {
    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;
    256351    }
    257352
     
    290385    {
    291386        return [
     387
     388            /* ---------- INPUT ---------- */
    292389            '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,
    294406                '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,
    303415                '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,
    310426                '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.) ------- */
    322431            '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'    => [],
    372452        ];
    373453    }
  • flex-fields/tags/2.2.0/FlexFields/FlexFields_Main.php

    r3240121 r3297921  
    133133        <input type="hidden" class="ff_post_excerpt" name="post_excerpt" value='<?php echo esc_attr($ff_excerpt) ?>'>
    134134        <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) ?>'>
    136136        <div class="ff-cloning-form hidden"><?php include FLEX_FIELDS_PARTIALS_DIR . 'flex_field.php' ?></div>
    137137        <div id="ff-group-rules-block">
     
    182182                <ul class="ff-be-tabs">
    183183                    <?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                        }
    191193                    }
    192194                    ?>
     
    197199                <?php
    198200                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>';
    204209                        }
    205                         echo '</div>';
    206210                    }
    207211                } else {
     
    274278            return $this->flex_fields_get_posts('taxonomy=' . $taxonomy);
    275279        } 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;
    277281            $page_template = get_post_meta($ff_page_id, '_wp_page_template', true);
    278282            $flex_fields_query = $page_template ? 'page_template=' . stripslashes($page_template) : 'post_type=page';
    279283            return $this->flex_fields_get_posts($flex_fields_query);
    280284        } 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;
    282286            $terms = get_the_terms($ff_post_id, 'category');
    283287            if ($terms) {
     
    308312            foreach ($flex_all_inputs as $input) {
    309313                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
    311317                    update_post_meta($post_id, $input, $flex_input);
    312318                }
  • flex-fields/tags/2.2.0/assets/js/flex_fields_backend.js

    r3240121 r3297921  
    5454        };
    5555
    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));
    5757        ff_visibility('.ff-return-types', ['gallery', 'image', 'file'].includes(ff_type));
    5858        ff_visibility('.ff-choices', ['select', 'radio', 'checkbox'].includes(ff_type));
     
    124124        $('.ff_post_content').val(JSON.stringify(ff_tabs));
    125125    }
     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
    126196
    127197    function ff_get_tab_fields(selector){
     
    209279            }
    210280            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>');
    214284            $('.ff-be-tabs').append(ff_new_tab);
    215285
  • flex-fields/tags/2.2.0/assets/js/flex_fields_frontend.js

    r3219135 r3297921  
    138138
    139139            const ff_stringify = (obj) => {
     140                const encodeHTML = (str) => {
     141                    return String(str).replace(/&/g, "&amp;")
     142                      .replace(/</g, "&lt;")
     143                      .replace(/>/g, "&gt;")
     144                      .replace(/"/g, "&quot;")
     145                      .replace(/'/g, "&#039;")
     146                      .replace(/\n/g, ' ') // convert newlines to placeholder
     147                      .replace(/"/g, '%quote%')    // optional: escape quotes
     148                      .replace(/\\/g, '\\\\');     // escape backslashes
     149                };
     150
    140151                return '[' + obj.map(item => {
    141152                    const entries = Object.entries(item).map(([key, value]) => {
    142                         const val = value.replaceAll('"', '%quote%');
     153                        const val = encodeHTML(value);
    143154                        return `"${key}":"${val}"`;
    144155                    });
     
    146157                }).join(',') + ']';
    147158            };
    148 
    149159            $(this).find('.ff-repeater-data').val(ff_stringify(ff_repeater_items));
    150160        })
  • flex-fields/tags/2.2.0/flex-fields.php

    r3243990 r3297921  
    44Plugin URI: https://flex-fields.com/
    55Description: 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.19
     6Version: 2.2.0
    77Requires at least: 6.6
    88Requires PHP: 7.0
  • flex-fields/tags/2.2.0/includes/flex_field.php

    r3238435 r3297921  
    1818    'gallery' => __('Gallery', 'flex-fields'),
    1919    'repeater' => __('Repeater', 'flex-fields'),
     20    'taxonomy' => __('Taxonomy', 'flex-fields'),
    2021];
    2122?>
     
    4546                    <div class="ff-label">
    4647                        <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">
    5051                    </div>
    5152                </div>
     
    5455                    <div class="ff-label">
    5556                        <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">
    5758                    </div>
    5859                </div>
     
    6162                    <div class="ff-label">
    6263                        <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">
    6465                    </div>
    6566                </div>
     
    6768                <div class="ff-content">
    6869                    <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' ?>">
    7071                        <h4>Choices</h4>
    7172                        <div>
     
    7980                            ?>
    8081                            <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) ?>'>
    8283                            <?php foreach ($ff_group_options as $option) { ?>
    8384                                <div class="ff-choices-block">
     
    9495                        </div>
    9596                        <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' ?>">
    9798                            <label for="scales">
    9899                                <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' : '' ?> />
    100101                                <strong>Select Multiple</strong>
    101102                            </label>
     
    109110                            <label>
    110111                                <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' : '' ?>>
    112113                                URL
    113114                            </label>
    114115                            <label>
    115116                                <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' : '' ?>>
    117118                                Array
    118119                            </label>
     
    125126            <div class="ff_repeater_content">
    126127                <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' ?>">
    128129                    <div class="ff-repeater-content-items">
    129130                        <?php
  • flex-fields/tags/2.2.0/readme.txt

    r3243990 r3297921  
    44Tags: field, meta, post, template, post types
    55Requires at least: 5.0
    6 Tested up to: 6.7
     6Tested up to: 6.8
    77Requires PHP: 7.0
    8 Stable tag: 2.1.19
     8Stable tag: 2.2.0
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
  • flex-fields/trunk/FlexFields/FlexFields_Base.php

    r3238435 r3297921  
    2020            'checkbox' => 'flex_fields_option',
    2121            'radio' => 'flex_fields_option',
     22            'taxonomy' => 'flex_fields_taxonomy',
    2223        ];
    2324
     
    5253            }
    5354            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
    5564                $value = $this->flex_fields_get_field_value($field, $type, $ff_id);
    5665                $functionName = $this->ff_get_function_name_by_type($field->type);
     
    7685
    7786        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);
    8089        } elseif ($type == 'taxonomy') {
    8190            $ff_tag_id = filter_input(INPUT_GET, 'tag_ID', FILTER_SANITIZE_STRING);
     
    190199    }
    191200
     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 &quot; 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
    192279    public function flex_fields_repeater($id, $field, $value, $name = null): string
    193280    {
    194281        $data = json_decode($value) ?? [];
     282        error_log('Decoded repeater value: ' . print_r($data, true));
    195283        $repeaters = $field->repeater ?? [];
    196284        $count = count($repeaters) > 4 ? 4 : count($repeaters);
     
    221309                foreach ($repeaters as $repeater) {
    222310                    $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);
    224316                }
    225317                $html .= $actions;
     
    253345    public function ff_generate_name($id, $field, $repeater = null): string
    254346    {
    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;
    256351    }
    257352
     
    290385    {
    291386        return [
     387
     388            /* ---------- INPUT ---------- */
    292389            '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,
    294406                '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,
    303415                '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,
    310426                '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.) ------- */
    322431            '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'    => [],
    372452        ];
    373453    }
  • flex-fields/trunk/FlexFields/FlexFields_Main.php

    r3240121 r3297921  
    133133        <input type="hidden" class="ff_post_excerpt" name="post_excerpt" value='<?php echo esc_attr($ff_excerpt) ?>'>
    134134        <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) ?>'>
    136136        <div class="ff-cloning-form hidden"><?php include FLEX_FIELDS_PARTIALS_DIR . 'flex_field.php' ?></div>
    137137        <div id="ff-group-rules-block">
     
    182182                <ul class="ff-be-tabs">
    183183                    <?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                        }
    191193                    }
    192194                    ?>
     
    197199                <?php
    198200                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>';
    204209                        }
    205                         echo '</div>';
    206210                    }
    207211                } else {
     
    274278            return $this->flex_fields_get_posts('taxonomy=' . $taxonomy);
    275279        } 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;
    277281            $page_template = get_post_meta($ff_page_id, '_wp_page_template', true);
    278282            $flex_fields_query = $page_template ? 'page_template=' . stripslashes($page_template) : 'post_type=page';
    279283            return $this->flex_fields_get_posts($flex_fields_query);
    280284        } 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;
    282286            $terms = get_the_terms($ff_post_id, 'category');
    283287            if ($terms) {
     
    308312            foreach ($flex_all_inputs as $input) {
    309313                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
    311317                    update_post_meta($post_id, $input, $flex_input);
    312318                }
  • flex-fields/trunk/assets/js/flex_fields_backend.js

    r3240121 r3297921  
    5454        };
    5555
    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));
    5757        ff_visibility('.ff-return-types', ['gallery', 'image', 'file'].includes(ff_type));
    5858        ff_visibility('.ff-choices', ['select', 'radio', 'checkbox'].includes(ff_type));
     
    124124        $('.ff_post_content').val(JSON.stringify(ff_tabs));
    125125    }
     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
    126196
    127197    function ff_get_tab_fields(selector){
     
    209279            }
    210280            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>');
    214284            $('.ff-be-tabs').append(ff_new_tab);
    215285
  • flex-fields/trunk/assets/js/flex_fields_frontend.js

    r3219135 r3297921  
    138138
    139139            const ff_stringify = (obj) => {
     140                const encodeHTML = (str) => {
     141                    return String(str).replace(/&/g, "&amp;")
     142                      .replace(/</g, "&lt;")
     143                      .replace(/>/g, "&gt;")
     144                      .replace(/"/g, "&quot;")
     145                      .replace(/'/g, "&#039;")
     146                      .replace(/\n/g, ' ') // convert newlines to placeholder
     147                      .replace(/"/g, '%quote%')    // optional: escape quotes
     148                      .replace(/\\/g, '\\\\');     // escape backslashes
     149                };
     150
    140151                return '[' + obj.map(item => {
    141152                    const entries = Object.entries(item).map(([key, value]) => {
    142                         const val = value.replaceAll('"', '%quote%');
     153                        const val = encodeHTML(value);
    143154                        return `"${key}":"${val}"`;
    144155                    });
     
    146157                }).join(',') + ']';
    147158            };
    148 
    149159            $(this).find('.ff-repeater-data').val(ff_stringify(ff_repeater_items));
    150160        })
  • flex-fields/trunk/flex-fields.php

    r3243990 r3297921  
    44Plugin URI: https://flex-fields.com/
    55Description: 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.19
     6Version: 2.2.0
    77Requires at least: 6.6
    88Requires PHP: 7.0
  • flex-fields/trunk/includes/flex_field.php

    r3238435 r3297921  
    1818    'gallery' => __('Gallery', 'flex-fields'),
    1919    'repeater' => __('Repeater', 'flex-fields'),
     20    'taxonomy' => __('Taxonomy', 'flex-fields'),
    2021];
    2122?>
     
    4546                    <div class="ff-label">
    4647                        <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">
    5051                    </div>
    5152                </div>
     
    5455                    <div class="ff-label">
    5556                        <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">
    5758                    </div>
    5859                </div>
     
    6162                    <div class="ff-label">
    6263                        <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">
    6465                    </div>
    6566                </div>
     
    6768                <div class="ff-content">
    6869                    <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' ?>">
    7071                        <h4>Choices</h4>
    7172                        <div>
     
    7980                            ?>
    8081                            <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) ?>'>
    8283                            <?php foreach ($ff_group_options as $option) { ?>
    8384                                <div class="ff-choices-block">
     
    9495                        </div>
    9596                        <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' ?>">
    9798                            <label for="scales">
    9899                                <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' : '' ?> />
    100101                                <strong>Select Multiple</strong>
    101102                            </label>
     
    109110                            <label>
    110111                                <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' : '' ?>>
    112113                                URL
    113114                            </label>
    114115                            <label>
    115116                                <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' : '' ?>>
    117118                                Array
    118119                            </label>
     
    125126            <div class="ff_repeater_content">
    126127                <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' ?>">
    128129                    <div class="ff-repeater-content-items">
    129130                        <?php
  • flex-fields/trunk/readme.txt

    r3243990 r3297921  
    44Tags: field, meta, post, template, post types
    55Requires at least: 5.0
    6 Tested up to: 6.7
     6Tested up to: 6.8
    77Requires PHP: 7.0
    8 Stable tag: 2.1.19
     8Stable tag: 2.2.0
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
Note: See TracChangeset for help on using the changeset viewer.