menu 동적으로 변경하기

Share on facebook
Facebook
Share on twitter
Twitter
Share on linkedin
LinkedIn
Share on print
Print

wordpress 사이트의 menu를 동적으로(?) 변경해야 할 필요가 있을 때가 있다. 동적이라는 표현이 모호한데, 예를 들어서 명확하게 정의해본다.

  • 특정 메뉴는 고객이 로그인 되었을 때만 나타나거나, 로그아웃 되었을 때만 나타난다.
  • My Account 메뉴는 고객이 로그인 되면 고객의 아이디를 사용하여, ‘Hi, OOO’와 같이 변경된다.

이러한 기능을 지원하는 플러그인에는 User Menus 플러그인 등이 있다고 한다. 1 이 포스팅에선 플러그인을 사용하지 않고, 직접 수정하는 방법을 정리한다.

참고로 플러그인이나 라이브러리를 사용하는 방법을 선호한다면 아래 문서들을 참고한다.

  • Font Awesome Library : 아래 Case4) 와 같은 방식으로 메뉴별로 css class를 준다. 차이점은 Case4)는 직접 css 파일에 수정이 필요한 반면, Font Awesome Library는 사전에 작성된 css 파일을 호출하는 방식으로 동작한다. 2

Filter Hook

wordpress의 menu를 변경할 수 있는 filter hook은 ‘wp_nav_menu_objects‘ 이다. 다음과 같이 4개의 메뉴 아이템을 가지고 있는 sample site에 적용해보자.

메뉴이름 Main_Menu, 메뉴 위치 Top

child theme의 functions.php 파일에 다음과 같은 형태로 add_hook 해서 메뉴를 변경할 수 있다. 3

add_filter( 'wp_nav_menu_objects', 'my_dynamic_menu_items', 10, 2);
function my_dynamic_menu_items( $sorted_menu_items, $args ) {
    // do something
    return $sorted_menu_items;
}

Case1) 특정 메뉴를 조건에 따라 Show/Hide

위 4가지 메뉴 아이템 중 ① 로그인 상태에선 로그인 메뉴를 숨기고, 로그아웃 메뉴를 보여주고, ② 로그아웃 상태에선 로그인 메뉴를 보여주고, 로그아웃 메뉴를 숨기도록 메뉴를 변경해보자.

add_filter( 'wp_nav_menu_objects', 'my_dynamic_menu_items', 10, 2);
function my_dynamic_menu_items( $sorted_menu_items, $args ) {
    
    // Case1) 특정 메뉴를 조건에 따라 Show/Hide
    $my_menu_items = array_filter($sorted_menu_items, function (&$menu) {
        $show = true;
        
        if (is_user_logged_in()) {
            if ( '로그인' == $menu->title) {
                $show = false;
            } 
        } else {
            if ('로그아웃' == $menu->title) {
                $show = false;
            }
        }
        return $show;
    });
    return $my_menu_items;
}

Case2) User Role에 따라 특정 메뉴를 Show/Hide

1) 메뉴에 특정 css class 부여

테스트 용도로 ‘어드민메뉴’를 만들고 css class를 ‘admin_only’라고 지정한다.

만약 css 클래스 입력화면이 보이지 않는다면, 우상단의 ‘화면 옵션’ 버튼을 눌러 아래 css 클래스 체크박스를 체크한다.

2) functions.php에 코드 추가하기

add_filter( 'wp_nav_menu_objects', 'my_dynamic_menu_items', 10, 2);
function my_dynamic_menu_items( $sorted_menu_items, $args ) {
    
    // Case2) User Role에 따라 특정 메뉴를 Show/Hide
    $my_menu_items = array_filter($sorted_menu_items, function (&$menu) {
        $show = true;
        if (is_user_logged_in()) {
            $user_id = get_current_user_id();
            $user_info = get_userdata($user_id);
            $user_role = implode(', ', $user_info->roles);
            $menu_class = implode(', ', $menu->classes);

            if (stripos($menu_class, 'admin_only') !== false) {
                if (stripos($user_role, "administrator") === false) {
                    $show = false;
                }
            }
        }
        return $show;
    });
    return $my_menu_items;
}

Case3) 로그인 아이디를 사용하여 메뉴명 변경

테스트 용도로 메뉴를 추가한다. 메뉴의 내비게이션 라벨은 #MY-ACCOUNT 라 해보자.

add_filter( 'wp_nav_menu_objects', 'my_dynamic_menu_items', 10, 2);
function my_dynamic_menu_items( $sorted_menu_items, $args ) {
    
    // Case3) 로그인 아이디를 사용하여 메뉴명 변경하기
    $my_menu_items = array();
    foreach ($sorted_menu_items as $menu_item) {
        if (! is_user_logged_in()) {
            if (strpos($menu_item->title, '#MY-ACCOUNT') !== false) {
                //로그아웃 상태에선 #MY-ACCOUNT 메뉴를 숨긴다.
                continue;
            } else {
                array_push($my_menu_items, $menu_item);
            }
        } else {
            if (strpos($menu_item->title, '#MY-ACCOUNT') !== false) {
                $menu_item->title = 'Hi, ' . wp_get_current_user()->user_email;
            }
            array_push($my_menu_items, $menu_item);
        }
    }
    return $my_menu_items;
}

결과는 다음과 같다. 4


Case4) 메뉴에 이미지 추가 (css class 사용)

간단하게 메뉴에 이미지 아이콘을 추가하는 방법은 메뉴 아이템의 css class를 이용하는 방법이다. 5

1) 메뉴에 css class 부여

위의 Case2)의 1) 내용을 그대로 활용해보자. 사용할 메뉴 아이템은 ‘어드민메뉴’이고, css class는 admin_only이다.

2) 이미지 아이콘 업로드하고 URL 확인하기

아이콘을 업로드하고 미디어 라이브러리에서 URL을 확인한다.
http://localhost/wordpress_dev1/wp-content/uploads/2019/08/boy.png


3) 테마의 css 파일에 스타일을 추가

child theme의 sytle.css 파일에 아래 스타일을 추가했다. .admin_only a 스타일은 아이콘 삽입 후 ‘어드민메뉴’ 텍스트가 가려져 padding을 추가한 부분이다. 6

.admin_only {
	background-image:
		url('http://localhost/wordpress_dev1/wp-content/uploads/2019/08/boy.png');
	background-repeat: no-repeat;
	background-position: left;
	background-size: contain;
	padding-left: 5px;
}

.admin_only a {
	padding-left: 50px;
}

4) 끝

아래와 같이 표현된다.


Case5) 메뉴에 이미지 추가 (description 속성, Walker 클래스)

추가로… CSS 에 이미지 경로를 고정하는 방법외에 다른방법 추가하면

  1. Case2) 에서와 같이 메뉴에 추가 화면 옵션으로 ‘설명(description)’을 활성화하고 이곳에 이미지 URL을 입력한다.
  2. Walker_Nav_Menu 클래스를 extends하는 클래스를 새로 만들어, 메뉴 아이템에 description 값이 존재하면 이 값을 메뉴 아이콘으로 처리하도록 코드를 수정해서 활용한다.

위 방법에 대한 설명들은 아래 링크를 참조한다. 난 Case6) 이 필요하기 때문에, Case5)는 별도로 해보지 않는다. 유사하기도 하고.

https://fluentthemes.com/adding-different-icons-different-items-wp-nav-menu/

https://stackoverflow.com/questions/18055526/php-wp-nav-menu-adding-icons?lq=1


Case6) 메뉴에 이미지 동적으로 추가

사용자가 로그인하면 사용자의 avatar 이미지를 #MY-ACCOUNT 메뉴에 추가해보자. Case4)의 css class를 사용하는 방법은 특정 이미지 파일을 하드코딩으로 메뉴의 css class와 연결하는 방법이라, 사용자가 로그인할 때마다 avatar 이미지를 가져와서 보이게 할 수 없다.

다시 처음에 wp_nav_menu_objects에 add_filter한 코드를 살펴보면 parameter를 2개 받고 있는데, 2번째 parameter인 $args는 wp_nav_menu()를 포함하는 stdClass형태의 object라고 설명되어 있다. 7

add_filter( 'wp_nav_menu_objects', 'my_dynamic_menu_items', 10, 2);
function my_dynamic_menu_items( $sorted_menu_items, $args ) {
    // do something
    return $sorted_menu_items;
}

before와 link_before가 비슷해보이는데, 차이점은 아래와 같다.

<li 메뉴아이템> {before} <a 메뉴링크> {link_before} “메뉴명” </a> </li>

link_before 값을 아래와 같이 주고, 시험해보자.

add_filter( 'wp_nav_menu_objects', 'my_dynamic_menu_items', 10, 2);
function my_dynamic_menu_items( $sorted_menu_items, $args ) {
    
    // Case5) 메뉴에 이미지 추가하기... 동적으로 !!
    if (is_user_logged_in()) {
        $args->link_before = get_avatar(get_current_user_id(), 30);        
    }
    return $my_menu_items;
}

로그아웃 상태
로그인 상태

로그인한 사용자의 avatar는 성공적으로 가져왔다. 하지만 위와 같이 접근해선 메뉴 아이템별로 다른 아이콘을 지정할 방법이 없다.

실패 ….


Case6다시) 메뉴에 이미지 동적으로 추가

/twentyseventeen/template-parts/navigation/navigation-top.php

theme의 header.php 파일에서 wp_nav_menu 함수를 호출하는 부분을 수정하면 된다. 동작 여부를 확인하기 위함이므로, my-child 테마의 부모 테마인 twentyseventeen 테마의 탑 메뉴 부분을 바로 수정해본다.

<?php
/**
 * Displays top navigation
 *
 * @package WordPress
 * @subpackage Twenty_Seventeen
 * @since 1.0
 * @version 1.2
 */

?>
<nav id="site-navigation" class="main-navigation" role="navigation" aria-label="<?php esc_attr_e( 'Top Menu', 'twentyseventeen' ); ?>">
	<button class="menu-toggle" aria-controls="top-menu" aria-expanded="false">
		<?php
		echo twentyseventeen_get_svg( array( 'icon' => 'bars' ) );
		echo twentyseventeen_get_svg( array( 'icon' => 'close' ) );
		_e( 'Menu', 'twentyseventeen' );
		?>
	</button>

	<?php
	wp_nav_menu(
		array(
			'theme_location' => 'top',
			'menu_id'        => 'top-menu',
		    'walker' => new Custom_Walker_Nav_Menu()
		)
	);
	?>

	<?php if ( ( twentyseventeen_is_frontpage() || ( is_home() && is_front_page() ) ) && has_custom_header() ) : ?>
		<a href="#content" class="menu-scroll-down"><?php echo twentyseventeen_get_svg( array( 'icon' => 'arrow-right' ) ); ?><span class="screen-reader-text"><?php _e( 'Scroll down to content', 'twentyseventeen' ); ?></span></a>
	<?php endif; ?>
</nav><!-- #site-navigation -->

/my-child/functions.php

이 방법은 wp_nav_menu_objects 훅을 사용하지 않는다. Waler_Nav_Menu 클래스를 extends하는 새로운 class를 가져오는 부분만 추가하면 된다.

require_once (get_stylesheet_directory() . '/includes/class-custom-walker-nav-menu.php');

my-child/includes/class-custom-walker-nav-menu.php

Waler_Nav_Menu 클래스를 extends하는 Custom 클래스를 만든다. 다른 부분은 모두 그대로 두고, 로그인한 상태에서 MY-ACCOUNT 메뉴 아이템을 만나는 경우 avatar를 삽입하는 코드만 추가했다.

<?php

class Custom_Walker_Nav_Menu extends Walker_Nav_Menu {
    
    /**
     * Starts the element output.
     *
     * @since 3.0.0
     * @since 4.4.0 The {@see 'nav_menu_item_args'} filter was added.
     *
     * @see Walker::start_el()
     *
     * @param string   $output Used to append additional content (passed by reference).
     * @param WP_Post  $item   Menu item data object.
     * @param int      $depth  Depth of menu item. Used for padding.
     * @param stdClass $args   An object of wp_nav_menu() arguments.
     * @param int      $id     Current item ID.
     */
    public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
            $t = '';
            $n = '';
        } else {
            $t = "\t";
            $n = "\n";
        }
        $indent = ( $depth ) ? str_repeat( $t, $depth ) : '';
        
        $classes   = empty( $item->classes ) ? array() : (array) $item->classes;
        $classes[] = 'menu-item-' . $item->ID;
        
        /**
         * Filters the arguments for a single nav menu item.
         *
         * @since 4.4.0
         *
         * @param stdClass $args  An object of wp_nav_menu() arguments.
         * @param WP_Post  $item  Menu item data object.
         * @param int      $depth Depth of menu item. Used for padding.
         */
        $args = apply_filters( 'nav_menu_item_args', $args, $item, $depth );
        
        /**
         * Filters the CSS classes applied to a menu item's list item element.
         *
         * @since 3.0.0
         * @since 4.1.0 The `$depth` parameter was added.
         *
         * @param string[] $classes Array of the CSS classes that are applied to the menu item's `<li>` element.
         * @param WP_Post  $item    The current menu item.
         * @param stdClass $args    An object of wp_nav_menu() arguments.
         * @param int      $depth   Depth of menu item. Used for padding.
         */
        $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );
        $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
        
        /**
         * Filters the ID applied to a menu item's list item element.
         *
         * @since 3.0.1
         * @since 4.1.0 The `$depth` parameter was added.
         *
         * @param string   $menu_id The ID that is applied to the menu item's `<li>` element.
         * @param WP_Post  $item    The current menu item.
         * @param stdClass $args    An object of wp_nav_menu() arguments.
         * @param int      $depth   Depth of menu item. Used for padding.
         */
        $id = apply_filters( 'nav_menu_item_id', 'menu-item-' . $item->ID, $item, $args, $depth );
        $id = $id ? ' id="' . esc_attr( $id ) . '"' : '';
        
        $output .= $indent . '<li' . $id . $class_names . '>';
        
        $atts           = array();
        $atts['title']  = ! empty( $item->attr_title ) ? $item->attr_title : '';
        $atts['target'] = ! empty( $item->target ) ? $item->target : '';
        if ( '_blank' === $item->target && empty( $item->xfn ) ) {
            $atts['rel'] = 'noopener noreferrer';
        } else {
            $atts['rel'] = $item->xfn;
        }
        $atts['href']         = ! empty( $item->url ) ? $item->url : '';
        $atts['aria-current'] = $item->current ? 'page' : '';
        
        /**
         * Filters the HTML attributes applied to a menu item's anchor element.
         *
         * @since 3.6.0
         * @since 4.1.0 The `$depth` parameter was added.
         *
         * @param array $atts {
         *     The HTML attributes applied to the menu item's `<a>` element, empty strings are ignored.
         *
         *     @type string $title        Title attribute.
         *     @type string $target       Target attribute.
         *     @type string $rel          The rel attribute.
         *     @type string $href         The href attribute.
         *     @type string $aria_current The aria-current attribute.
         * }
         * @param WP_Post  $item  The current menu item.
         * @param stdClass $args  An object of wp_nav_menu() arguments.
         * @param int      $depth Depth of menu item. Used for padding.
         */
        $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth );
        
        $attributes = '';
        foreach ( $atts as $attr => $value ) {
            if ( ! empty( $value ) ) {
                $value       = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
                $attributes .= ' ' . $attr . '="' . $value . '"';
            }
        }
        
        /** This filter is documented in wp-includes/post-template.php */
        $title = apply_filters( 'the_title', $item->title, $item->ID );
        
        /**
         * Filters a menu item's title.
         *
         * @since 4.4.0
         *
         * @param string   $title The menu item's title.
         * @param WP_Post  $item  The current menu item.
         * @param stdClass $args  An object of wp_nav_menu() arguments.
         * @param int      $depth Depth of menu item. Used for padding.
         */
        $title = apply_filters( 'nav_menu_item_title', $title, $item, $args, $depth );
        
        $item_output  = $args->before;
        $item_output .= '<a' . $attributes . '>';
        // 삽입한 코드는 다음과 같다.
        if (is_user_logged_in() && (strpos($item->title, '#MY-ACCOUNT') !== false)) {
            $item_output .= get_avatar(get_current_user_id(), 30);
        }        
        $item_output .= $args->link_before . $title . $args->link_after;

        
        
        $my_menu_items = array();
        
        $item_output .= '</a>';
        $item_output .= $args->after;
        
        /**
         * Filters a menu item's starting output.
         *
         * The menu item's starting output only includes `$args->before`, the opening `<a>`,
         * the menu item's title, the closing `</a>`, and `$args->after`. Currently, there is
         * no filter for modifying the opening and closing `<li>` for a menu item.
         *
         * @since 3.0.0
         *
         * @param string   $item_output The menu item's starting HTML output.
         * @param WP_Post  $item        Menu item data object.
         * @param int      $depth       Depth of menu item. Used for padding.
         * @param stdClass $args        An object of wp_nav_menu() arguments.
         */
        $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
    }
    
}

결과는

성공

Footnotes

  1. https://www.wpexplorer.com/custom-menus-wordpress-users/
  2. https://wphive.com/tutorials/how-to-add-icons-to-your-wordpress-menu/
    https://wordpress.stackexchange.com/questions/272797/unique-icons-next-to-each-wordpress-menu-item
  3. https://bekseju9n.pe.kr/wp-content/uploads/2019/08/eclipse-workspace-wordpress_dev1_wp-content_themes_my-child_functions.php-Eclipse-IDE-2019-08-14-오전-12_45_31.jpg
    https://bekseju9n.pe.kr/wp-content/uploads/2019/08/eclipse-workspace-wordpress_dev1_wp-content_themes_my-child_functions.php-Eclipse-IDE-2019-08-14-오전-12_45_37.jpg
  4. Case1, 2에서 테스트 용도로 추가한 메뉴는 functions.php 처리 코드를 제거하였기 때문에 그대로 보이고 있다. 무시하자.
  5. https://www.sktthemes.org/wordpress/add-icons-wordpress-custom-menus/
    https://www.wpbeginner.com/plugins/how-to-add-image-icons-with-navigation-menus-in-wordpress/
  6. 이미지 resize to fit : https://stackoverflow.com/questions/9262861/css-background-image-to-fit-width-height-should-auto-scale-in-proportion
  7. https://developer.wordpress.org/reference/hooks/wp_nav_menu_objects/
    https://developer.wordpress.org/reference/functions/wp_nav_menu/

답글 남기기

이메일 주소를 발행하지 않을 것입니다. 필수 항목은 *(으)로 표시합니다

  • 카테고리

  • Count per Day

    • 1026This post:
    • 129011Total reads:
    • 71730Total visitors:
    • 3Reads today:
    • 3Visitors today:
    • 2019년 3월 10일Counter starts on: