向定制器中的菜单项添加设置

时间:2020-08-06 作者:helgatheviking

WordPress 5.4添加了wp_nav_menu_item_custom_fields_customize_template 钩子可将自定义字段添加到自定义程序中的导航菜单项设置中。我已经找到了如何显示我想添加的其他字段(作为导航菜单角色插件的一部分),代码片段如下:

/**
* Display the fields in the Customizer.
*/
function kia_customizer_custom_fields() { 

    global $wp_roles;

    /**
    * Pass the menu item to the filter function.
    * This change is suggested as it allows the use of information from the menu item (and
    * by extension the target object) to further customize what filters appear during menu
    * construction.
    */
    $display_roles = apply_filters( \'nav_menu_roles\', $wp_roles->role_names );

    if( ! $display_roles ) return;

    ?>
    <p class="field-nav_menu_logged_in_out nav_menu_logged_in_out nav_menu_logged_in_out-thin">
        <fieldset>
            <legend><?php _e( \'Display Mode\', \'nav-menu-roles\' ); ?></legend>

            <label for="edit-menu-item-role_logged_in-{{ data.menu_item_id }}">
                <input type="radio" id="edit-menu-item-role_logged_in-{{ data.menu_item_id }}" class="edit-menu-item-logged_in_out" value="in" name="menu-item-role_logged_in" />
                    <?php _e( \'Logged In Users\', \'nav-menu-roles\' ); ?><br/>
            </label>
            <label for="edit-menu-item-role_logged_out-{{ data.menu_item_id }}">
                <input type="radio" id="edit-menu-item-role_logged_out-{{ data.menu_item_id }}" class="edit-menu-item-logged_in_out" value="out" name="menu-item-role_logged_out" />
                    <?php _e( \'Logged Out Users\', \'nav-menu-roles\' ); ?><br/>
            </label>
            <label for="edit-menu-item-role_everyone-{{ data.menu_item_id }}">
                <input type="radio" id="edit-menu-item-role_everyone-{{ data.menu_item_id }}" class="edit-menu-item-logged_in_out" value="" name="menu-item-role_everyone" />
                    <?php _e( \'Everyone\', \'nav-menu-roles\' ); ?><br/>
            </label>

        </fieldset>
    </p>
    <p class="field-nav_menu_roles nav_menu_roles nav_menu_roles-thin">
        <fieldset class="roles">
            <legend><?php _e( \'Restrict menu item to a minimum role\', \'nav-menu-roles\' ); ?></legend>

            <?php

            /* Loop through each of the available roles. */
            foreach ( $display_roles as $role => $name ) : ?>
               
               <label for="edit-menu-item-role_<?php echo $role; ?>-{{ data.menu_item_id }}">
                    <input type="checkbox" id="edit-menu-item-role_<?php echo esc_attr( $role ); ?>-{{ data.menu_item_id }}" class="edit-menu-item-role" value="<?php echo esc_attr( $role ); ?>" name="menu-item-role_<?php echo esc_attr( $role ); ?>" />
                        <?php echo esc_html( $name ); ?><br/>
                </label>

            <?php endforeach; ?>

        </fieldset>
    </p>
    <?php
}
add_action( \'wp_nav_menu_item_custom_fields_customize_template\', \'kia_customizer_custom_fields\' );
但我不知道如何添加任何数据,因为默认字段都是从一些Javascript模板获取数据。

例如,描述的值为{{ data.description }}.

看起来数据正在添加到WP_Customize_Manager class 数据是从每个设置的json() 方法,该方法没有用于修改结果的筛选器。

如果我能正确地追溯它json() 方法尝试从WP_Customize_Nav_Menu_Item_Setting class 它也没有用于添加自定义数据的筛选器。

所以,我想找出如何1。将一些其他属性传递给data 和2。在自定义程序中加载时,设置收音机和复选框值。

Update #1

我已经在过滤了wp_setup_nav_menu_item 像这样:

/**
* Adds value of new field to nav menu $item object
*/
function kia_setup_nav_item( $menu_item ) {

    if( is_object( $menu_item ) && isset( $menu_item->ID ) ) {
        $roles = get_post_meta( $menu_item->ID, \'_nav_menu_role\', true );
    }
    return $menu_item;
}
add_filter( \'wp_setup_nav_menu_item\', \'kia_setup_nav_item\' );
但是根据@Nikola Ivanov Nikolov的评论,我意识到这些数据在customizer脚本对象中可用。。。好像在里面wp.customize.settings.settings

console log screenshot showing the wp.customize.settings.settings object with roles key on each nav_menu_item array

我已经找到了wp.customize.Menus.MenuItemControl 因此,现在似乎是在菜单项控件ready/expanded.

WordPress Customizer screenshot for a menu item named "My Shop" and showing radio buttons for "Display mode" and checkboxes for "restrict menu item to minimum role"

Update #2

我有一些Javascript,至少可以检查当前值!

animated gif, where clicking on a menu item reveals additional fields such as display mode and role restrictions. When clicking on "logged in" display mode, checkboxes of roles appear.

/**
 * Customizer Communicator
 */
jQuery( document ).ready(function($) {
    "use strict";

    $( \'.customize-control-nav_menu_item\' ).on( \'expanded\', function() {

        var $control     = $(this);
        var menu_item_id = $(this).find( \'.nav_menu_logged_in_out\' ).data( \'menu_item_id\' );
        var control      = wp.customize.control( \'nav_menu_item[\' + menu_item_id + \']\' );
        var settings     = control.setting.get();

        if ( \'undefined\' === typeof( settings.roles ) || \'\' === settings.roles ) {
            $control.find( \'.edit-menu-item-logged_in_out[value=""]\' ).prop( \'checked\', true ).change();
        } else if ( \'out\' === settings.roles ) {
            $control.find( \'.edit-menu-item-logged_in_out[value="out"]\' ).prop( \'checked\', true ).change();
        } else if ( \'in\' === settings.roles ) {
            $control.find( \'.edit-menu-item-logged_in_out[value="in"]\' ).prop( \'checked\', true ).change();
        } else if ( $.isArray( settings.roles ) ) {
            $control.find( \'.edit-menu-item-logged_in_out[value="in"]\' ).prop( \'checked\', true ).change();
            $.each( settings.roles, function( index, role ) {
                $control.find( \'.edit-menu-item-role[value="\' + role + \'"]\' ).prop( \'checked\', true );
            } );
        }

        $( \'.edit-menu-item-logged_in_out\' ).on( \'change\', function() {
            var $roles = $control.find( \'.nav_menu_roles\' );
            if ( \'in\' === $(this).val() ) {
                $roles.show();
            } else {
                $roles.hide();
            }   
        });

    });

});
因此,这仍然留下了如何确保保存数据的问题。

1 个回复
最合适的回答,由SO网友:Weston Ruter 整理而成

您遇到了在Customizer中修改导航菜单的不完整实现。尤其是phpdoc内部WP_Customize_Nav_Menu_Item_Setting::preview() 您可以看到以下评论:

// @todo Add get_post_metadata filters for plugins to add their data.
尽管如此,尽管我们没有实现对导航菜单项Posteta的预览更改的核心支持,但仍然可以做到。但首先,让我们讨论一下JavaScript。

扩展Nav菜单项控件首先需要确定需要扩展的Nav菜单项控件,这是使用JS API实现此目的的方法:

wp.customize.control.bind( \'add\', ( control ) => {
    if ( control.extended( wp.customize.Menus.MenuItemControl ) ) {
        control.deferred.embedded.done( () => {
            extendControl( control );
        } );
    }
} );
这个extendControl() 函数需要使用您通过添加的新字段的行为来增强控件kia_customizer_custom_fields():

/**
 * Extend the control with roles information.
 *
 * @param {wp.customize.Menus.MenuItemControl} control
 */
function extendControl( control ) {
    control.authFieldset = control.container.find( \'.nav_menu_role_authentication\' );
    control.rolesFieldset = control.container.find( \'.nav_menu_roles\' );

    // Set the initial UI state.
    updateControlFields( control );

    // Update the UI state when the setting changes programmatically.
    control.setting.bind( () => {
        updateControlFields( control );
    } );

    // Update the setting when the inputs are modified.
    control.authFieldset.find( \'input\' ).on( \'click\', function () {
        setSettingRoles( control.setting, this.value );
    } );
    control.rolesFieldset.find( \'input\' ).on( \'click\', function () {
        const checkedRoles = [];
        control.rolesFieldset.find( \':checked\' ).each( function () {
            checkedRoles.push( this.value );
        } );
        setSettingRoles( control.setting, checkedRoles.length === 0 ? \'in\' : checkedRoles );
    } );
}
在这里,您可以看到它根据控件的设置值设置初始UI状态,然后在使用双向数据绑定进行更改时进行更新。

下面是一个函数,负责更改roles 在自定义程序中Setting 导航菜单项的对象:

/**
 * Extend the setting with roles information.
 *
 * @param {wp.customize.Setting} setting
 * @param {string|Array} roles
 */
function setSettingRoles( setting, roles ) {
    setting.set(
        Object.assign(
            {},
            _.clone( setting() ),
            { roles }
        )
    );
}
下面是如何将设置的值应用于控件的字段:

/**
 * Apply the control\'s setting value to the control\'s fields.
 *
 * @param {wp.customize.Menus.MenuItemControl} control
 */
function updateControlFields( control ) {
    const roles = control.setting().roles || \'\';

    const radioValue = _.isArray( roles ) ? \'in\' : roles;
    const checkedRoles = _.isArray( roles ) ? roles : [];

    control.rolesFieldset.toggle( \'in\' === radioValue );

    const authRadio = control.authFieldset.find( `input[type=radio][value="${ radioValue }"]` );
    authRadio.prop( \'checked\', true );

    control.rolesFieldset.find( \'input[type=checkbox]\' ).each( function () {
        this.checked = checkedRoles.includes( this.value );
    } );
}
这就是所有必需的JS代码,可以通过以下方式在PHP中排队:

add_action(
    \'customize_controls_enqueue_scripts\',
    static function () {
        wp_enqueue_script(
            \'customize-nav-menu-roles\',
            plugin_dir_url( __FILE__ ) . \'/customize-nav-menu-roles.js\',
            [ \'customize-nav-menus\' ],
            filemtime( __DIR__ . \'/customize-nav-menu-roles.js\' ),
            true
        );
    }
);
现在来看看所需的PHP代码。。。

WP_Customize_Nav_Menu_Item_Setting 类不知道自定义字段,它只会将它们去掉。所以我们需要实现我们自己的预览方法。下面是如何将其连接起来:

add_action(
    \'customize_register\',
    static function( WP_Customize_Manager $wp_customize ) {
        if ( $wp_customize->settings_previewed() ) {
            foreach ( $wp_customize->settings() as $setting ) {
                if ( $setting instanceof WP_Customize_Nav_Menu_Item_Setting ) {
                    preview_nav_menu_setting_postmeta( $setting );
                }
            }
        }
    },
    1000
);
这将在每个注册的nav菜单项设置上循环并调用preview_nav_menu_setting_postmeta():

/**
 * Preview changes to the nav menu item roles.
 *
 * Note the unimplemented to-do in the doc block for the setting\'s preview method.
 *
 * @see WP_Customize_Nav_Menu_Item_Setting::preview()
 *
 * @param WP_Customize_Nav_Menu_Item_Setting $setting Setting.
 */
function preview_nav_menu_setting_postmeta( WP_Customize_Nav_Menu_Item_Setting $setting ) {
    $roles = get_sanitized_roles_post_data( $setting );
    if ( null === $roles ) {
        return;
    }

    add_filter(
        \'get_post_metadata\',
        static function ( $value, $object_id, $meta_key ) use ( $setting, $roles ) {
            if ( $object_id === $setting->post_id && \'_nav_menu_role\' === $meta_key ) {
                return [ $roles ];
            }
            return $value;
        },
        10,
        3
    );
}
您可以在这里看到,它向底层添加了一个过滤器get_post_meta() 呼叫正在预览的角色值是通过以下方式获得的get_sanitized_roles_post_data():

/**
 * Save changes to the nav menu item roles.
 *
 * Note the unimplemented to-do in the doc block for the setting\'s preview method.
 *
 * @see WP_Customize_Nav_Menu_Item_Setting::update()
 *
 * @param WP_Customize_Nav_Menu_Item_Setting $setting Setting.
 */
function save_nav_menu_setting_postmeta( WP_Customize_Nav_Menu_Item_Setting $setting ) {
    $roles = get_sanitized_roles_post_data( $setting );
    if ( null !== $roles ) {
        update_post_meta( $setting->post_id, \'_nav_menu_role\', $roles );
    }
}
现在,为了节约,我们做了类似的事情。首先,我们在以下位置循环所有导航菜单项设置:customize_save_after:

add_action(
    \'customize_save_after\',
    function ( WP_Customize_Manager $wp_customize ) {
        foreach ( $wp_customize->settings() as $setting ) {
            if ( $setting instanceof WP_Customize_Nav_Menu_Item_Setting && $setting->check_capabilities() ) {
                save_nav_menu_setting_postmeta( $setting );
            }
        }
    }
);
在哪里save_nav_menu_setting_postmeta() 获取预览的设置值,然后将其保存到Posteta中:

/**
 * Save changes to the nav menu item roles.
 *
 * Note the unimplemented to-do in the doc block for the setting\'s preview method.
 *
 * @see WP_Customize_Nav_Menu_Item_Setting::update()
 *
 * @param WP_Customize_Nav_Menu_Item_Setting $setting Setting.
 */
function save_nav_menu_setting_postmeta( WP_Customize_Nav_Menu_Item_Setting $setting ) {
    $roles = get_sanitized_roles_post_data( $setting );
    if ( null !== $roles ) {
        update_post_meta( $setting->post_id, \'_nav_menu_role\', $roles );
    }
}
Theget_sanitized_roles_post_data() 函数如下所示:

/**
 * Sanitize roles value.
 *
 * @param string|array $value Roles.
 * @return array|string Sanitized roles.
 */
function sanitize_roles_value( $value ) {
    global $wp_roles;
    if ( is_array( $value ) ) {
        return array_intersect( $value, array_keys( $wp_roles->role_names ) );
    } elseif ( in_array( $value, [ \'\', \'in\', \'out\' ], true ) ) {
        return $value;
    }
    return \'\';
}
就是这样。

这里有一个完整的工作插件,一旦与导航菜单角色插件一起激活,就可以将所有这些部分放在一起:https://gist.github.com/westonruter/7f2b9c18113f0576a72e0aca3ce3dbcb

用于在定制器设置和控件字段之间进行数据绑定的JS逻辑可以变得更加优雅。例如,它可以使用React。或者它可以使用wp.customize.Element 其他控件使用的。但这可以通过良好的ol\'jQuery完成工作。

关于此实现的一个警告是:由于自定义程序如何预览尚未保存的导航菜单项,您将无法预览对此类导航菜单项的角色值的更改。(在引擎盖下,自定义程序创建一个否定的post ID,以表示尚未保存到DB的nav菜单项。)

相关推荐

Making sub-menus exclusive

我真的不知道该怎么解释我在这里找的东西,我在这里找得太露骨了。在我的网站上,我有一个附带菜单,其中包含一系列子类别,每个子类别中都有一些项目。我想知道当我打开另一个子类别时,是否有办法关闭所有其他打开的子类别,例如:1. Animals ----A. Cats ----B. Dogs 2. People ----A. Samantha ----B. Daniel 当我按下“动物”时,我希望“人”关闭,反之亦