Add menu management to the Customizer.

This brings in the Menu Customizer plugin: https://wordpress.org/plugins/menu-customizer/.

props celloexpressions, westonruter, valendesigns, voldemortensen, ocean90, adamsilverstein, kucrut, jorbin, designsimply, afercia, davidakennedy, obenland.
see #32576.

git-svn-id: https://develop.svn.wordpress.org/trunk@32806 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Dominik Schilling (ocean90) 2015-06-16 22:07:08 +00:00
parent 0433b87fdf
commit c0a66eba79
14 changed files with 7864 additions and 57 deletions

View File

@ -141,7 +141,7 @@ module.exports = function(grunt) {
},
cssmin: {
options: {
'wp-admin': ['wp-admin', 'color-picker', 'customize-controls', 'customize-widgets', 'ie', 'install', 'login', 'press-this', 'deprecated-*']
'wp-admin': ['wp-admin', 'color-picker', 'customize-controls', 'customize-widgets', 'customize-nav-menus', 'ie', 'install', 'login', 'press-this', 'deprecated-*']
},
core: {
expand: true,

View File

@ -0,0 +1,964 @@
#accordion-section-menu_locations {
position: relative;
margin-bottom: 15px;
}
.menu-in-location,
.menu-in-locations {
display: block;
color: #999;
font-weight: 600;
font-size: 10px;
}
#customize-controls .control-section .accordion-section-title:focus .menu-in-location,
#customize-controls .control-section .accordion-section-title:hover .menu-in-location,
#customize-controls .control-section .accordion-section-title:focus .menu-in-locations,
#customize-controls .control-section .accordion-section-title:hover .menu-in-locations {
color: #fff;
}
.wp-customizer .menu-item-bar .menu-item-handle,
.wp-customizer .menu-item-settings,
.wp-customizer .menu-item-settings .description-thin {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.wp-customizer .menu-item-bar {
margin: 0;
}
.wp-customizer .menu-item-bar .menu-item-handle {
max-width: 100%;
background: #fff;
}
.wp-customizer .menu-item-handle .item-title {
margin-right: 0;
}
.wp-customizer .menu-item-handle .item-type {
padding: 1px 15px 0 5px;
float: right;
text-align: right;
}
.wp-customizer .menu-item-settings {
max-width: 100%;
overflow: hidden;
padding: 10px;
background: #eee;
border: 1px solid #999;
border-top: none;
}
.wp-customizer .menu-item-settings .description-thin {
width: 100%;
height: auto;
margin: 0 0 8px 0;
}
.wp-customizer .menu-item-settings input[type="text"] {
width: 100%;
}
.wp-customizer .menu-item-settings .submitbox {
margin: 0;
padding: 0;
}
.wp-customizer .menu-item-settings .link-to-original {
padding: 5px 0;
border: none;
font-style: normal;
margin: 0;
width: 100%;
}
.wp-customizer .menu-item .submitbox .submitdelete {
display: block;
float: left;
margin: 6px 0 0;
padding: 0;
cursor: pointer;
}
.wp-customizer .menu-item .submitbox .submitdelete:focus {
-webkit-box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
}
/* Menu-item reordering nav. */
#customize-theme-controls button.reorder-toggle {
padding: 5px 8px;
}
.menu-item-reorder-nav {
display: none;
background-color: #fff;
position: absolute;
top: 0;
right: 0;
}
#customize-theme-controls .reordering .add-new-menu-item {
opacity: 0.2;
pointer-events: none;
cursor: not-allowed;
}
.menu-item-reorder-nav button {
position: relative;
overflow: hidden;
float: left;
display: block;
width: 30px;
height: 40px;
color: #82878c;
text-indent: -9999px;
cursor: pointer;
background: transparent;
border: none;
-webkit-box-shadow: none;
box-shadow: none;
outline: none;
}
.menu-item-reorder-nav button:before {
display: inline-block;
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 100%;
font: normal 20px/40px dashicons;
text-align: center;
text-indent: 0;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.menu-item-reorder-nav button:hover,
.menu-item-reorder-nav button:focus {
color: #191e23;
background: #eee;
}
.menus-move-down:before {
content: '\f347';
}
.menus-move-up:before {
content: '\f343';
}
.menus-move-left:before {
content: '\f341';
}
.menus-move-right:before {
content: '\f345';
}
.move-up-disabled .menus-move-up,
.move-down-disabled .menus-move-down,
.move-right-disabled .menus-move-right,
.move-left-disabled .menus-move-left,
.menu-item-depth-0 .menus-move-left,
.menu-item-depth-10 .menus-move-right {
color: #d5d5d5 !important;
background-color: #fff !important;
cursor: default;
pointer-events: none;
}
.menu-item-reorder-nav:before {
content: "";
display: block;
position: absolute;
left: -10px;
width: 10px;
height: 40px;
background: -webkit-linear-gradient(left, rgba(250,250,250,0) 0%,rgba(250,250,250,1) 100%);
background: -webkit-gradient(linear, left top, right top, from(rgba(250,250,250,0)), to(rgba(250,250,250,1)));
background: -webkit-linear-gradient(left, rgba(250,250,250,0) 0%, rgba(250,250,250,1) 100%);
background: linear-gradient(to right, rgba(250,250,250,0) 0%,rgba(250,250,250,1) 100%);
}
.reordering .menu-item .item-controls,
.reordering .menu-item .item-type {
display: none;
}
.reordering .menu-item-reorder-nav {
display: block;
}
.customize-control input.menu-name-field {
width: 100%; /* Override the 98% default for customizer inputs, to align with the size of menu items. */
margin: 12px 0;
}
.wp-customizer .menu-item .item-edit {
position: absolute;
right: -19px;
top: 2px;
display: block;
width: 30px;
height: 38px;
margin-right: 0 !important;
text-indent: 100%;
outline: none;
overflow: hidden;
white-space: nowrap;
cursor: pointer;
}
.customize-control-nav_menu_item .item-edit:focus {
color: #0073aa;
-webkit-box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
}
/* Duplicates `.nav-menus-php .item-edit:before {}` in common.css:2220. */
.wp-customizer .menu-item .item-edit:before {
top: -1px;
right: 0;
content: '\f140';
border: none;
background: none;
font: normal 20px/1 dashicons;
speak: none;
display: block;
padding: 0;
text-indent: 0;
text-align: center;
position: relative;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-decoration: none !important;
}
.wp-customizer .menu-item.menu-item-edit-active .item-edit:before {
content: '\f142';
}
.wp-customizer .menu-item .item-edit:before {
line-height: 2;
}
/* Duplicates `.nav-menus-php .menu-item-edit-active .item-edit:before {}` in common.css:2271. */
.wp-customizer .menu-item .menu-item-edit-active .item-edit:before {
content: '\f142';
}
.wp-customizer .menu-item-settings p.description {
font-style: normal;
}
.wp-customizer .menu-settings dl {
margin: 12px 0 0 0;
padding: 0;
}
.wp-customizer .menu-settings .checkbox-input {
margin-top: 8px;
}
.wp-customizer .menu-settings .menu-theme-locations {
border-top: 1px solid #ccc;
}
.wp-customizer .menu-settings {
margin-top: 36px;
border-top: none;
}
.menu-settings .customize-control-checkbox label {
line-height: 1;
}
/* @todo update selector or potentially remove */
.menu-settings .customize-control.customize-control-checkbox {
margin-bottom: 8px; /* Override collapsing at smaller viewports. */
}
.customize-control-menu {
margin-top: 4px;
}
#customize-controls .customize-info.open.active-menu-screen-options .customize-help-toggle {
color: #555;
}
/* Screen Options */
.customize-screen-options-toggle {
background: none;
border: none;
color: #555;
cursor: pointer;
padding: 20px;
position: absolute;
right: 31px;
top: 4px;
}
#customize-controls .customize-info .customize-help-toggle {
padding: 20px;
}
#customize-controls .customize-info .customize-help-toggle:before {
padding: 5px;
}
.customize-screen-options-toggle:hover,
.customize-screen-options-toggle:active,
.customize-screen-options-toggle:focus,
.active-menu-screen-options .customize-screen-options-toggle,
#customize-controls .customize-info.open.active-menu-screen-options .customize-help-toggle:hover,
#customize-controls .customize-info.open.active-menu-screen-options .customize-help-toggle:active,
#customize-controls .customize-info.open.active-menu-screen-options .customize-help-toggle:focus {
color: #0073aa;
}
.customize-screen-options-toggle:focus,
#customize-controls .customize-info .customize-help-toggle:focus {
outline: none;
-webkit-box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
}
.customize-screen-options-toggle:before {
-moz-osx-font-smoothing: grayscale;
border: none;
content: "\f111";
display: block;
font: 20px/1 "dashicons";
padding: 5px;
text-align: center;
text-decoration: none !important;
text-indent: 0;
left: 5px;
position: absolute;
top: 5px;
}
.wp-customizer #screen-options-wrap {
display: none;
background: #fff;
border-top: 1px solid #ddd;
padding: 4px 15px 0;
}
.wp-customizer .metabox-prefs label {
display: block;
padding-right: 0;
line-height: 30px;
}
#accordion-panel-menus .field-link-target,
#accordion-panel-menus .field-attr-title,
#accordion-panel-menus .field-css-classes,
#accordion-panel-menus .field-xfn,
#accordion-panel-menus .field-description {
display: none;
}
#accordion-panel-menus.field-link-target-active .field-link-target,
#accordion-panel-menus.field-attr-title-active .field-attr-title,
#accordion-panel-menus.field-css-classes-active .field-css-classes,
#accordion-panel-menus.field-xfn-active .field-xfn,
#accordion-panel-menus.field-description-active .field-description {
display: block;
}
/* Not exactly sure what broke this, or why it works without these styles in core. */
.wp-customizer .wp-full-overlay a.collapse-sidebar {
position: fixed;
left: 0;
}
/* WARNING: The 20px factor is hard-coded in JS. */
.menu-item-depth-0 { margin-left: 0; }
.menu-item-depth-1 { margin-left: 20px; }
.menu-item-depth-2 { margin-left: 40px; }
.menu-item-depth-3 { margin-left: 60px; }
.menu-item-depth-4 { margin-left: 80px; }
.menu-item-depth-5 { margin-left: 100px; }
.menu-item-depth-6 { margin-left: 120px; }
.menu-item-depth-7 { margin-left: 140px; }
.menu-item-depth-8 { margin-left: 160px; } /* Not likely to be used or useful beyond this depth */
.menu-item-depth-9 { margin-left: 180px; }
.menu-item-depth-10 { margin-left: 200px; }
.menu-item-depth-11 { margin-left: 220px; }
/* @todo handle .menu-item-settings width */
.menu-item-depth-0 > .menu-item-bar { margin-right: 0; }
.menu-item-depth-1 > .menu-item-bar { margin-right: 20px; }
.menu-item-depth-2 > .menu-item-bar { margin-right: 40px; }
.menu-item-depth-3 > .menu-item-bar { margin-right: 60px; }
.menu-item-depth-4 > .menu-item-bar { margin-right: 80px; }
.menu-item-depth-5 > .menu-item-bar { margin-right: 100px; }
.menu-item-depth-6 > .menu-item-bar { margin-right: 120px; }
.menu-item-depth-7 > .menu-item-bar { margin-right: 140px; }
.menu-item-depth-8 > .menu-item-bar { margin-right: 160px; }
.menu-item-depth-9 > .menu-item-bar { margin-right: 180px; }
.menu-item-depth-10 > .menu-item-bar { margin-right: 200px; }
.menu-item-depth-11 > .menu-item-bar { margin-right: 220px; }
/* Submenu left margin. */
/* @todo menu-item-transport seems entirely unused. */
.menu-item-depth-0 .menu-item-transport { margin-left: 0; }
.menu-item-depth-1 .menu-item-transport { margin-left: -20px; }
.menu-item-depth-3 .menu-item-transport { margin-left: -60px; }
.menu-item-depth-4 .menu-item-transport { margin-left: -80px; }
.menu-item-depth-2 .menu-item-transport { margin-left: -40px; }
.menu-item-depth-5 .menu-item-transport { margin-left: -100px; }
.menu-item-depth-6 .menu-item-transport { margin-left: -120px; }
.menu-item-depth-7 .menu-item-transport { margin-left: -140px; }
.menu-item-depth-8 .menu-item-transport { margin-left: -160px; }
.menu-item-depth-9 .menu-item-transport { margin-left: -180px; }
.menu-item-depth-10 .menu-item-transport { margin-left: -200px; }
.menu-item-depth-11 .menu-item-transport { margin-left: -220px; }
/* WARNING: The 20px factor is hard-coded in JS. */
.reordering .menu-item-depth-0 { margin-left: 0; }
.reordering .menu-item-depth-1 { margin-left: 15px; }
.reordering .menu-item-depth-2 { margin-left: 30px; }
.reordering .menu-item-depth-3 { margin-left: 45px; }
.reordering .menu-item-depth-4 { margin-left: 60px; }
.reordering .menu-item-depth-5 { margin-left: 75px; }
.reordering .menu-item-depth-6 { margin-left: 90px; }
.reordering .menu-item-depth-7 { margin-left: 105px; }
.reordering .menu-item-depth-8 { margin-left: 120px; } /* Not likely to be used or useful beyond this depth */
.reordering .menu-item-depth-9 { margin-left: 135px; }
.reordering .menu-item-depth-10 { margin-left: 150px; }
.reordering .menu-item-depth-11 { margin-left: 165px; }
.reordering .menu-item-depth-0 > .menu-item-bar { margin-right: 0; }
.reordering .menu-item-depth-1 > .menu-item-bar { margin-right: 15px; }
.reordering .menu-item-depth-2 > .menu-item-bar { margin-right: 30px; }
.reordering .menu-item-depth-3 > .menu-item-bar { margin-right: 45px; }
.reordering .menu-item-depth-4 > .menu-item-bar { margin-right: 60px; }
.reordering .menu-item-depth-5 > .menu-item-bar { margin-right: 75px; }
.reordering .menu-item-depth-6 > .menu-item-bar { margin-right: 90px; }
.reordering .menu-item-depth-7 > .menu-item-bar { margin-right: 105px; }
.reordering .menu-item-depth-8 > .menu-item-bar { margin-right: 120px; }
.reordering .menu-item-depth-9 > .menu-item-bar { margin-right: 135px; }
.reordering .menu-item-depth-10 > .menu-item-bar { margin-right: 150px; }
.reordering .menu-item-depth-11 > .menu-item-bar { margin-right: 165px; }
.control-section-nav_menu .menu .menu-item-edit-active {
margin-left: 0;
}
.control-section-nav_menu .menu .menu-item-edit-active .menu-item-bar {
margin-right: 0;
}
.control-section-nav_menu .menu .sortable-placeholder {
margin-top: 0;
margin-bottom: 1px;
max-width: -webkit-calc(100% - 2px);
max-width: calc(100% - 2px);
float: left;
display: list-item;
border-color: #a0a5aa;
}
.control-section-nav_menu .menu ul.menu-item-transport dl {
margin-top: 0;
}
/*
* Add-menu-items mode.
*/
.wp-full-overlay-main {
right: auto; /* This overrides a right: 0; which causes the preview to resize rather than slide off screen at the normal size. */
width: 100%;
}
.adding-menu-items .control-section {
opacity: .4;
}
.adding-menu-items .control-panel.control-section,
.adding-menu-items .control-section.open {
opacity: 1;
}
/* Add-new button. */
#customize-theme-controls .add-new-menu-item {
cursor: pointer;
float: right;
margin-left: 10px;
-webkit-transition: all 0.2s;
transition: all 0.2s;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
outline: none;
}
.add-new-menu-item:before {
content: "\f132";
display: inline-block;
position: relative;
left: -2px;
top: -1px;
font: normal 20px/1 'dashicons';
vertical-align: middle;
-webkit-transition: all 0.2s;
transition: all 0.2s;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.adding-menu-items .add-new-menu-item,
.adding-menu-items .add-new-menu-item:hover,
.add-menu-toggle.open,
.add-menu-toggle.open:hover {
background: #eee;
border-color: #929793;
color: #32373c;
-webkit-box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5);
box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5);
}
.adding-menu-items .add-new-menu-item:before,
#accordion-section-add_menu .add-new-menu-item.open:before {
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.menu-item-bar .item-delete {
color: #a00;
position: absolute;
top: 2px;
right: -19px;
width: 30px;
height: 38px;
cursor: pointer;
display: none;
}
.menu-item-bar .item-delete:before {
content: "\f335";
font: normal 20px/1 dashicons;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
position: absolute;
top: 9px;
left: 5px;
}
.menu-item-bar .item-delete:hover,
.menu-item-bar .item-delete:focus {
color: #f00;
}
.adding-menu-items .menu-item-bar .item-edit {
display: none;
}
.adding-menu-items .menu-item-bar .item-delete {
display: block;
}
#available-menu-items .item {
position: static;
}
#available-menu-items {
position: absolute;
overflow: hidden;
top: 0;
bottom: 0;
left: -301px;
width: 300px;
margin: 0;
z-index: 4;
background: #eee;
-webkit-transition: all 0.2s;
transition: all 0.2s;
border-right: 1px solid #ddd;
}
#available-menu-items.allow-scroll {
overflow-y: auto;
}
#available-menu-items .accordion-section-title {
border-left: none;
border-right: none;
background: #fff;
}
#available-menu-items .open .accordion-section-title {
background: #eee;
}
#available-menu-items .open .accordion-section-title:after {
content: '\f142';
}
#available-menu-items .accordion-section-content {
overflow-y: auto;
max-height: 200px; /* This gets set in JS to fit the screen size, and based on # of sections. */
background: transparent;
}
button.not-a-button {
background: transparent;
border: none;
-webkit-box-shadow: none;
box-shadow: none;
-webkit-border-radius: 0;
border-radius: 0;
outline: 0;
padding: 0;
margin: 0;
}
#available-menu-items .accordion-section-title button:focus:before {
display: block;
content: "";
width: 28px;
height: 32px;
position: absolute;
right: 5px;
top: 5px;
-webkit-box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
}
#available-menu-items .accordion-section-content {
padding: 1px 15px 15px 15px;
min-height: 120px;
max-height: 290px;
}
#custom-menu-item-name.invalid,
#custom-menu-item-url.invalid {
border: 1px solid #f00;
}
#available-menu-items .item-tpl {
position: relative;
padding: 20px 15px 20px 60px;
border-bottom: 1px solid #e4e4e4;
cursor: pointer;
display: none;
}
#available-menu-items .item-tpl:hover,
#available-menu-items .item-tpl.selected {
background: #eee;
}
#available-menu-items .menu-item-handle .item-type {
padding-right: 0;
}
#available-menu-items .menu-item-handle .item-title {
padding-left: 20px;
}
#available-menu-items .menu-item-handle {
cursor: pointer;
}
#available-menu-items .item-top,
#available-menu-items .item-top:hover {
border: none;
background: transparent;
-webkit-box-shadow: none;
box-shadow: none;
}
#available-menu-items .menu-item-handle {
-webkit-box-shadow: none;
box-shadow: none;
margin-top: -1px;
}
#available-menu-items .menu-item-handle:hover {
z-index: 1;
}
#available-menu-items .item-title h4 {
padding: 0 0 5px;
font-size: 14px;
}
#available-menu-items .item-add {
position: absolute;
top: 1px;
left: 1px;
color: #82878c;
width: 30px;
height: 38px;
cursor: pointer;
}
#available-menu-items .menu-item-handle .item-add:focus {
color: #23282d;
-webkit-box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
}
#available-menu-items .item-add:before {
content: "\f132";
font: normal 20px/1 dashicons;
position: relative;
left: 2px;
top: 4px;
}
#available-menu-items .menu-item-handle.item-added .item-type,
#available-menu-items .menu-item-handle.item-added .item-title,
#available-menu-items .menu-item-handle.item-added:hover .item-add,
#available-menu-items .menu-item-handle.item-added .item-add:focus {
color: #82878c;
}
#available-menu-items .menu-item-handle.item-added .item-add:before {
content: "\f147";
}
#available-menu-items .accordion-section-title.loading .spinner,
#available-menu-items-search.loading .accordion-section-title .spinner {
visibility: visible;
margin: 0 20px;
}
#available-menu-items-search .spinner {
position: absolute;
top: 18px;
margin: 0 !important;
right: 20px;
}
#available-menu-items-search input {
padding: 6px 10px;
width: 100%;
}
#available-menu-items-search .accordion-section-title {
padding: 12px 15px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
#available-menu-items-search .accordion-section-title:after {
display: none;
}
#available-menu-items-search .accordion-section-content:empty {
min-height: 0;
padding: 0;
}
#available-menu-items-search.loading .accordion-section-content div {
opacity: .5;
}
#available-menu-items-search.loading.loading-more .accordion-section-content div {
opacity: 1;
}
#customize-preview {
-webkit-transition: all 0.2s;
transition: all 0.2s;
}
body.adding-menu-items #available-menu-items {
left: 0;
}
body.adding-menu-items .wp-full-overlay-main {
left: 300px;
}
body.adding-menu-items #customize-preview {
opacity: 0.4;
}
.menu-item-handle .spinner {
display: none;
float: left;
margin: 0 8px 0 0;
}
.nav-menu-inserted-item-loading .spinner {
display: block;
}
.nav-menu-inserted-item-loading .menu-item-handle .item-type {
padding: 0 0 0 8px;
}
.nav-menu-inserted-item-loading .menu-item-handle,
.added-menu-item .menu-item-handle.loading {
padding: 10px 15px 10px 8px;
cursor: default;
opacity: .5;
background: #fff;
color: #727773;
}
.added-menu-item .menu-item-handle {
-webkit-transition-property: opacity, background, color;
transition-property: opacity, background, color;
-webkit-transition-duration: 1.25s;
transition-duration: 1.25s;
-webkit-transition-timing-function: cubic-bezier( .25, -2.5, .75, 8 );
transition-timing-function: cubic-bezier( .25, -2.5, .75, 8 ); /* Replacement for .hide().fadeIn('slow') in JS to add emphasis when it's loaded. */
}
/* Add/delete Menus */
/* @todo update selector */
#accordion-section-add_menu {
margin: 15px 12px;
}
.new-menu-section-content {
display: none;
padding: 15px 0 0 0;
overflow: hidden;
clear: both;
}
/* @todo update selector */
#accordion-section-add_menu .accordion-section-title {
padding-left: 45px;
}
/* @todo update selector */
#accordion-section-add_menu .accordion-section-title:before {
font: normal 20px/1 dashicons;
position: absolute;
top: 12px;
left: 14px;
content: "\f132";
}
#create-new-menu-submit {
float: right;
margin: 0 0 12px 0;
}
.menu-delete-item {
display: block;
float: left;
padding: 1em 0;
width: 100%;
}
li.assigned-to-menu-location .menu-delete-item {
display: none;
}
li.assigned-to-menu-location .add-new-menu-item {
margin-bottom: 1em;
}
.menu-delete {
color: #a00;
cursor: pointer;
text-decoration: underline;
}
.menu-delete:hover,
.menu-delete:focus {
color: #f00;
text-decoration: none;
}
.menu-delete:focus {
-webkit-box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
}
.menu-item-handle {
margin-top: -1px;
}
.ui-sortable-disabled .menu-item-handle {
cursor: default;
}
.menu-item-handle:hover {
position: relative;
z-index: 10;
color: #0073aa;
}
.menu-item-handle:hover .item-type,
.menu-item-handle:hover .item-edit,
#available-menu-items .menu-item-handle:hover .item-add {
color: #0073aa;
}
.menu-item-edit-active .menu-item-handle {
border-color: #999;
border-bottom: none;
}
.customize-control-nav_menu_item {
margin-bottom: 0;
}
.customize-control-nav_menu {
margin-top: 12px;
}
#available-menu-items .customize-section-title {
display: none;
}
@media screen and ( max-width: 640px ) {
body.adding-menu-items div#available-menu-items {
top: 46px;
left: 0;
z-index: 10;
width: 100%;
}
#available-menu-items .customize-section-title {
display: block;
margin: 0;
}
#available-menu-items .customize-section-back {
height: 69px;
}
#available-menu-items .customize-section-title h3 {
font-size: 20px;
font-weight: 200;
padding: 9px 10px 12px 14px;
margin: 0;
line-height: 24px;
color: #555;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
#available-menu-items .customize-section-title .customize-action {
font-size: 13px;
display: block;
font-weight: 400;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1402,3 +1402,479 @@ class WP_Widget_Form_Customize_Control extends WP_Customize_Control {
return $this->manager->widgets->is_widget_rendered( $this->widget_id );
}
}
/**
* Customize Nav Menus Panel Class
*
* Needed to add screen options.
*
* @since 4.3.0
*/
class WP_Customize_Nav_Menus_Panel extends WP_Customize_Panel {
/**
* Control type.
*
* @since 4.3.0
*
* @access public
* @var string
*/
public $type = 'nav_menus';
/**
* Render screen options for Menus.
*
* @since 4.3.0
*/
public function render_screen_options() {
// Essentially adds the screen options.
add_filter( 'manage_nav-menus_columns', array( $this, 'wp_nav_menu_manage_columns' ) );
// Display screen options.
$screen = WP_Screen::get( 'nav-menus.php' );
$screen->render_screen_options();
}
/**
* Returns the advanced options for the nav menus page.
*
* Link title attribute added as it's a relatively advanced concept for new users.
*
* @since 4.3.0
*
* @return array The advanced menu properties.
*/
function wp_nav_menu_manage_columns() {
return array(
'_title' => __( 'Show advanced menu properties' ),
'cb' => '<input type="checkbox" />',
'link-target' => __( 'Link Target' ),
'attr-title' => __( 'Title Attribute' ),
'css-classes' => __( 'CSS Classes' ),
'xfn' => __( 'Link Relationship (XFN)' ),
'description' => __( 'Description' ),
);
}
/**
* An Underscore (JS) template for this panel's content (but not its container).
*
* Class variables for this panel class are available in the `data` JS object;
* export custom variables by overriding {@see WP_Customize_Panel::json()}.
*
* @since 4.3.0
*
* @see WP_Customize_Panel::print_template()
*
* @since 4.3.0
*/
protected function content_template() {
?>
<li class="panel-meta customize-info accordion-section <# if ( ! data.description ) { #> cannot-expand<# } #>">
<button type="button" class="customize-panel-back" tabindex="-1">
<span class="screen-reader-text"><?php _e( 'Back' ); ?></span>
</button>
<div class="accordion-section-title">
<span class="preview-notice">
<?php
/* translators: %s is the site/panel title in the Customizer */
printf( __( 'You are customizing %s' ), '<strong class="panel-title">{{ data.title }}</strong>' );
?>
</span>
<button type="button" class="customize-screen-options-toggle" aria-expanded="false">
<span class="screen-reader-text"><?php _e( 'Menu Options' ); ?></span>
</button>
<button type="button" class="customize-help-toggle dashicons dashicons-editor-help" aria-expanded="false">
<span class="screen-reader-text"><?php _e( 'Help' ); ?></span>
</button>
</div>
<# if ( data.description ) { #>
<div class="description customize-panel-description">{{{ data.description }}}</div>
<# } #>
<?php $this->render_screen_options(); ?>
</li>
<?php
}
}
/**
* Customize Nav Menu Control Class
*
* @since 4.3.0
*/
class WP_Customize_Nav_Menu_Control extends WP_Customize_Control {
/**
* Control type.
*
* @since 4.3.0
*
* @access public
* @var string
*/
public $type = 'nav_menu';
/**
* The nav menu setting.
*
* @since 4.3.0
*
* @var WP_Customize_Nav_Menu_Setting
*/
public $setting;
/**
* Don't render the control's content - it uses a JS template instead.
*
* @since 4.3.0
*/
public function render_content() {}
/**
* JS/Underscore template for the control UI.
*
* @since 4.3.0
*/
public function content_template() {
?>
<button type="button" class="button-secondary add-new-menu-item">
<?php _e( 'Add Items' ); ?>
</button>
<button type="button" class="not-a-button reorder-toggle">
<span class="reorder"><?php _ex( 'Reorder', 'Reorder menu items in Customizer' ); ?></span>
<span class="reorder-done"><?php _ex( 'Done', 'Cancel reordering menu items in Customizer' ); ?></span>
</button>
<span class="add-menu-item-loading spinner"></span>
<span class="menu-delete-item">
<button type="button" class="not-a-button menu-delete">
<?php _e( 'Delete menu' ); ?> <span class="screen-reader-text">{{ data.menu_name }}</span>
</button>
</span>
<?php if ( current_theme_supports( 'menus' ) ) : ?>
<ul class="menu-settings">
<li class="customize-control">
<span class="customize-control-title"><?php _e( 'Menu locations' ); ?></span>
</li>
<?php foreach ( get_registered_nav_menus() as $location => $description ) : ?>
<li class="customize-control customize-control-checkbox assigned-menu-location">
<label>
<input type="checkbox" data-menu-id="{{ data.menu_id }}" data-location-id="<?php echo esc_attr( $location ); ?>" class="menu-location" /> <?php echo $description; ?>
<span class="theme-location-set"><?php printf( _x( '(Current: %s)', 'Current menu location' ), '<span class="current-menu-location-name-' . esc_attr( $location ) . '"></span>' ); ?></span>
</label>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<p>
<label>
<input type="checkbox" class="auto_add">
<?php _e( 'Automatically add new top-level pages to this menu.' ) ?>
</label>
</p>
<?php
}
/**
* Return params for this control.
*
* @since 4.3.0
*
* @return array
*/
function json() {
$exported = parent::json();
$exported['menu_id'] = $this->setting->term_id;
return $exported;
}
}
/**
* Customize control to represent the name field for a given menu.
*
* @since 4.3.0
*/
class WP_Customize_Nav_Menu_Item_Control extends WP_Customize_Control {
/**
* Control type.
*
* @since 4.3.0
*
* @access public
* @var string
*/
public $type = 'nav_menu_item';
/**
* The nav menu item setting.
*
* @since 4.3.0
*
* @var WP_Customize_Nav_Menu_Item_Setting
*/
public $setting;
/**
* Constructor.
*
* @since 4.3.0
*
* @uses WP_Customize_Control::__construct()
*
* @param WP_Customize_Manager $manager An instance of the WP_Customize_Manager class.
* @param string $id The control ID.
* @param array $args Optional. Overrides class property defaults.
*/
public function __construct( $manager, $id, $args = array() ) {
parent::__construct( $manager, $id, $args );
}
/**
* Don't render the control's content - it's rendered with a JS template.
*
* @since 4.3.0
*/
public function render_content() {}
/**
* JS/Underscore template for the control UI.
*
* @since 4.3.0
*/
public function content_template() {
?>
<dl class="menu-item-bar">
<dt class="menu-item-handle">
<span class="item-type">{{ data.item_type_label }}</span>
<span class="item-title">
<span class="spinner"></span>
<span class="menu-item-title">{{ data.title }}</span>
</span>
<span class="item-controls">
<button type="button" class="not-a-button item-edit"><span class="screen-reader-text"><?php _e( 'Edit Menu Item' ); ?></span></button>
<button type="button" class="not-a-button item-delete submitdelete deletion"><span class="screen-reader-text"><?php _e( 'Remove Menu Item' ); ?></span></button>
</span>
</dt>
</dl>
<div class="menu-item-settings" id="menu-item-settings-{{ data.menu_item_id }}">
<# if ( 'custom' === data.item_type ) { #>
<p class="field-url description description-thin">
<label for="edit-menu-item-url-{{ data.menu_item_id }}">
<?php _e( 'URL' ); ?><br />
<input class="widefat code edit-menu-item-url" type="text" id="edit-menu-item-url-{{ data.menu_item_id }}" name="menu-item-url" />
</label>
</p>
<# } #>
<p class="description description-thin">
<label for="edit-menu-item-title-{{ data.menu_item_id }}">
<?php _e( 'Navigation Label' ); ?><br />
<input type="text" id="edit-menu-item-title-{{ data.menu_item_id }}" class="widefat edit-menu-item-title" name="menu-item-title" />
</label>
</p>
<p class="field-link-target description description-thin">
<label for="edit-menu-item-target-{{ data.menu_item_id }}">
<input type="checkbox" id="edit-menu-item-target-{{ data.menu_item_id }}" class="edit-menu-item-target" value="_blank" name="menu-item-target" />
<?php _e( 'Open link in a new tab' ); ?>
</label>
</p>
<p class="field-attr-title description description-thin">
<label for="edit-menu-item-attr-title-{{ data.menu_item_id }}">
<?php _e( 'Title Attribute' ); ?><br />
<input type="text" id="edit-menu-item-attr-title-{{ data.menu_item_id }}" class="widefat edit-menu-item-attr-title" name="menu-item-attr-title" />
</label>
</p>
<p class="field-css-classes description description-thin">
<label for="edit-menu-item-classes-{{ data.menu_item_id }}">
<?php _e( 'CSS Classes' ); ?><br />
<input type="text" id="edit-menu-item-classes-{{ data.menu_item_id }}" class="widefat code edit-menu-item-classes" name="menu-item-classes" />
</label>
</p>
<p class="field-xfn description description-thin">
<label for="edit-menu-item-xfn-{{ data.menu_item_id }}">
<?php _e( 'Link Relationship (XFN)' ); ?><br />
<input type="text" id="edit-menu-item-xfn-{{ data.menu_item_id }}" class="widefat code edit-menu-item-xfn" name="menu-item-xfn" />
</label>
</p>
<p class="field-description description description-thin">
<label for="edit-menu-item-description-{{ data.menu_item_id }}">
<?php _e( 'Description' ); ?><br />
<textarea id="edit-menu-item-description-{{ data.menu_item_id }}" class="widefat edit-menu-item-description" rows="3" cols="20" name="menu-item-description">{{ data.description }}</textarea>
<span class="description"><?php _e( 'The description will be displayed in the menu if the current theme supports it.' ); ?></span>
</label>
</p>
<div class="menu-item-actions description-thin submitbox">
<# if ( 'custom' != data.item_type && '' != data.original_title ) { #>
<p class="link-to-original">
<?php printf( __( 'Original: %s' ), '<a class="original-link" href="{{ data.url }}">{{{ data.original_title }}}</a>' ); ?>
</p>
<# } #>
<button type="button" class="not-a-button item-delete submitdelete deletion"><?php _e( 'Remove' ); ?></button>
<span class="spinner"></span>
</div>
<input type="hidden" name="menu-item-db-id[{{ data.menu_item_id }}]" class="menu-item-data-db-id" value="{{ data.menu_item_id }}" />
<input type="hidden" name="menu-item-parent-id[{{ data.menu_item_id }}]" class="menu-item-data-parent-id" value="{{ data.parent }}" />
</div><!-- .menu-item-settings-->
<ul class="menu-item-transport"></ul>
<?php
}
/**
* Return params for this control.
*
* @since 4.3.0
*
* @return array
*/
function json() {
$exported = parent::json();
$exported['menu_item_id'] = $this->setting->post_id;
return $exported;
}
}
/**
* Customize Menu Location Control Class
*
* This custom control is only needed for JS.
*
* @since 4.3.0
*/
class WP_Customize_Nav_Menu_Location_Control extends WP_Customize_Control {
/**
* Control type.
*
* @since 4.3.0
*
* @access public
* @var string
*/
public $type = 'nav_menu_location';
/**
* Location ID.
*
* @since 4.3.0
*
* @access public
* @var string
*/
public $location_id = '';
/**
* Refresh the parameters passed to JavaScript via JSON.
*
* @since 4.3.0
*
* @uses WP_Customize_Control::to_json()
*/
public function to_json() {
parent::to_json();
$this->json['locationId'] = $this->location_id;
}
/**
* Render content just like a normal select control.
*
* @since 4.3.0
*/
public function render_content() {
if ( empty( $this->choices ) ) {
return;
}
?>
<label>
<?php if ( ! empty( $this->label ) ) : ?>
<span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
<?php endif; ?>
<?php if ( ! empty( $this->description ) ) : ?>
<span class="description customize-control-description"><?php echo $this->description; ?></span>
<?php endif; ?>
<select <?php $this->link(); ?>>
<?php
foreach ( $this->choices as $value => $label ) :
echo '<option value="' . esc_attr( $value ) . '"' . selected( $this->value(), $value, false ) . '>' . $label . '</option>';
endforeach;
?>
</select>
</label>
<?php
}
}
/**
* Customize control to represent the name field for a given menu.
*
* @since 4.3.0
*/
class WP_Customize_Nav_Menu_Name_Control extends WP_Customize_Control {
/**
* Type of control, used by JS.
*
* @since 4.3.0
*
* @var string
*/
public $type = 'nav_menu_name';
/**
* No-op since we're using JS template.
*
* @since 4.3.0
*/
protected function render_content() {}
/**
* Render the Underscore template for this control.
*
* @since 4.3.0
*/
protected function content_template() {
?>
<label>
<input type="text" class="menu-name-field live-update-section-title" />
</label>
<?php
}
}
/**
* Customize control class for new menus.
*
* @since 4.3.0
*/
class WP_New_Menu_Customize_Control extends WP_Customize_Control {
/**
* Control type.
*
* @since 4.3.0
*
* @access public
* @var string
*/
public $type = 'new_menu';
/**
* Render the control's content.
*
* @since 4.3.0
*/
public function render_content() {
?>
<button type="button" class="button button-primary" id="create-new-menu-submit"><?php _e( 'Create Menu' ); ?></button>
<span class="spinner"></span>
<?php
}
}

View File

@ -49,6 +49,13 @@ final class WP_Customize_Manager {
*/
public $widgets;
/**
* Methods and properties deailing with managing nav menus in the Customizer.
*
* @var WP_Customize_Nav_Menus
*/
public $nav_menus;
protected $settings = array();
protected $containers = array();
protected $panels = array();
@ -104,8 +111,10 @@ final class WP_Customize_Manager {
require_once( ABSPATH . WPINC . '/class-wp-customize-section.php' );
require_once( ABSPATH . WPINC . '/class-wp-customize-control.php' );
require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
require_once( ABSPATH . WPINC . '/class-wp-customize-nav-menus.php' );
$this->widgets = new WP_Customize_Widgets( $this );
$this->nav_menus = new WP_Customize_Nav_Menus( $this );
add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
@ -1484,48 +1493,6 @@ final class WP_Customize_Manager {
}
}
/* Nav Menus */
$locations = get_registered_nav_menus();
$menus = wp_get_nav_menus();
$num_locations = count( array_keys( $locations ) );
if ( 1 == $num_locations ) {
$description = __( 'Your theme supports one menu. Select which menu you would like to use.' );
} else {
$description = sprintf( _n( 'Your theme supports %s menu. Select which menu appears in each location.', 'Your theme supports %s menus. Select which menu appears in each location.', $num_locations ), number_format_i18n( $num_locations ) );
}
$this->add_section( 'nav', array(
'title' => __( 'Navigation' ),
'theme_supports' => 'menus',
'priority' => 100,
'description' => $description . "\n\n" . __( 'You can edit your menu content on the Menus screen in the Appearance section.' ),
) );
if ( $menus ) {
$choices = array( '' => __( '&mdash; Select &mdash;' ) );
foreach ( $menus as $menu ) {
$choices[ $menu->term_id ] = wp_html_excerpt( $menu->name, 40, '&hellip;' );
}
foreach ( $locations as $location => $description ) {
$menu_setting_id = "nav_menu_locations[{$location}]";
$this->add_setting( $menu_setting_id, array(
'sanitize_callback' => 'absint',
'theme_supports' => 'menus',
) );
$this->add_control( $menu_setting_id, array(
'label' => $description,
'section' => 'nav',
'type' => 'select',
'choices' => $choices,
) );
}
}
/* Static Front Page */
// #WP19627

View File

@ -0,0 +1,882 @@
<?php
/**
* WordPress Customize Nav Menus classes
*
* @package WordPress
* @subpackage Customize
* @since 4.3.0
*/
/**
* Customize Nav Menus class.
*
* Implements menu management in the Customizer.
*
* @since 4.3.0
*
* @see WP_Customize_Manager
*/
final class WP_Customize_Nav_Menus {
/**
* WP_Customize_Manager instance.
*
* @since 4.3.0
*
* @access public
* @var WP_Customize_Manager
*/
public $manager;
/**
* Previewed Menus.
*
* @since 4.3.0
*
* @access public
* @var array
*/
public $previewed_menus;
/**
* Constructor.
*
* @since 4.3.0
*
* @access public
* @param object $manager An instance of the WP_Customize_Manager class.
*/
public function __construct( $manager ) {
$this->previewed_menus = array();
$this->manager = $manager;
add_action( 'wp_ajax_load-available-menu-items-customizer', array( $this, 'ajax_load_available_items' ) );
add_action( 'wp_ajax_search-available-menu-items-customizer', array( $this, 'ajax_search_available_items' ) );
add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
add_action( 'customize_register', array( $this, 'customize_register' ), 11 ); // Needs to run after core Navigation section is set up.
add_filter( 'customize_dynamic_setting_args', array( $this, 'filter_dynamic_setting_args' ), 10, 2 );
add_filter( 'customize_dynamic_setting_class', array( $this, 'filter_dynamic_setting_class' ), 10, 3 );
add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_templates' ) );
add_action( 'customize_controls_print_footer_scripts', array( $this, 'available_items_template' ) );
add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ) );
}
/**
* Ajax handler for loading available menu items.
*
* @since 4.3.0
*/
public function ajax_load_available_items() {
check_ajax_referer( 'customize-menus', 'customize-menus-nonce' );
if ( ! current_user_can( 'edit_theme_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Error: invalid user capabilities.' ) ) );
}
if ( empty( $_POST['obj_type'] ) || empty( $_POST['type'] ) ) {
wp_send_json_error( array( 'message' => __( 'Missing obj_type or type param.' ) ) );
}
$obj_type = sanitize_key( $_POST['obj_type'] );
if ( ! in_array( $obj_type, array( 'post_type', 'taxonomy' ) ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid obj_type param: ' . $obj_type ) ) );
}
$taxonomy_or_post_type = sanitize_key( $_POST['type'] );
$page = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 0;
$items = array();
if ( 'post_type' === $obj_type ) {
if ( ! get_post_type_object( $taxonomy_or_post_type ) ) {
wp_send_json_error( array( 'message' => __( 'Unknown post type.' ) ) );
}
if ( 0 === $page && 'page' === $taxonomy_or_post_type ) {
// Add "Home" link. Treat as a page, but switch to custom on add.
$items[] = array(
'id' => 'home',
'title' => _x( 'Home', 'nav menu home label' ),
'type' => 'custom',
'type_label' => __( 'Custom Link' ),
'object' => '',
'url' => home_url(),
);
}
$posts = get_posts( array(
'numberposts' => 10,
'offset' => 10 * $page,
'orderby' => 'date',
'order' => 'DESC',
'post_type' => $taxonomy_or_post_type,
) );
foreach ( $posts as $post ) {
$items[] = array(
'id' => "post-{$post->ID}",
'title' => html_entity_decode( get_the_title( $post ) ),
'type' => 'post_type',
'type_label' => get_post_type_object( $post->post_type )->labels->singular_name,
'object' => $post->post_type,
'object_id' => (int) $post->ID,
);
}
} else if ( 'taxonomy' === $obj_type ) {
$terms = get_terms( $taxonomy_or_post_type, array(
'child_of' => 0,
'exclude' => '',
'hide_empty' => false,
'hierarchical' => 1,
'include' => '',
'number' => 10,
'offset' => 10 * $page,
'order' => 'DESC',
'orderby' => 'count',
'pad_counts' => false,
) );
if ( is_wp_error( $terms ) ) {
wp_send_json_error( array( 'message' => wp_strip_all_tags( $terms->get_error_message(), true ) ) );
}
foreach ( $terms as $term ) {
$items[] = array(
'id' => "term-{$term->term_id}",
'title' => html_entity_decode( $term->name ),
'type' => 'taxonomy',
'type_label' => get_taxonomy( $term->taxonomy )->labels->singular_name,
'object' => $term->taxonomy,
'object_id' => $term->term_id,
);
}
}
wp_send_json_success( array( 'items' => $items ) );
}
/**
* Ajax handler for searching available menu items.
*
* @since 4.3.0
*/
public function ajax_search_available_items() {
check_ajax_referer( 'customize-menus', 'customize-menus-nonce' );
if ( ! current_user_can( 'edit_theme_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Error: invalid user capabilities.' ) ) );
}
if ( empty( $_POST['search'] ) ) {
wp_send_json_error( array( 'message' => __( 'Error: missing search parameter.' ) ) );
}
$p = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 0;
if ( $p < 1 ) {
$p = 1;
}
$s = sanitize_text_field( wp_unslash( $_POST['search'] ) );
$results = $this->search_available_items_query( array( 'pagenum' => $p, 's' => $s ) );
if ( empty( $results ) ) {
wp_send_json_error( array( 'message' => __( 'No results found.' ) ) );
} else {
wp_send_json_success( array( 'items' => $results ) );
}
}
/**
* Performs post queries for available-item searching.
*
* Based on WP_Editor::wp_link_query().
*
* @since 4.3.0
*
* @param array $args Optional. Accepts 'pagenum' and 's' (search) arguments.
* @return array Results.
*/
public function search_available_items_query( $args = array() ) {
$results = array();
$post_type_objects = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' );
$query = array(
'post_type' => array_keys( $post_type_objects ),
'suppress_filters' => true,
'update_post_term_cache' => false,
'update_post_meta_cache' => false,
'post_status' => 'publish',
'posts_per_page' => 20,
);
$args['pagenum'] = isset( $args['pagenum'] ) ? absint( $args['pagenum'] ) : 1;
$query['offset'] = $args['pagenum'] > 1 ? $query['posts_per_page'] * ( $args['pagenum'] - 1 ) : 0;
if ( isset( $args['s'] ) ) {
$query['s'] = $args['s'];
}
// Query posts.
$get_posts = new WP_Query( $query );
// Check if any posts were found.
if ( $get_posts->post_count ) {
foreach ( $get_posts->posts as $post ) {
$results[] = array(
'id' => 'post-' . $post->ID,
'type' => 'post_type',
'type_label' => $post_type_objects[ $post->post_type ]->labels->singular_name,
'object' => $post->post_type,
'object_id' => intval( $post->ID ),
'title' => html_entity_decode( get_the_title( $post ) ),
);
}
}
// Query taxonomy terms.
$taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'names' );
$terms = get_terms( $taxonomies, array(
'name__like' => $args['s'],
'number' => 20,
'offset' => 20 * ($args['pagenum'] - 1),
) );
// Check if any taxonomies were found.
if ( ! empty( $terms ) ) {
foreach ( $terms as $term ) {
$results[] = array(
'id' => 'term-' . $term->term_id,
'type' => 'taxonomy',
'type_label' => get_taxonomy( $term->taxonomy )->labels->singular_name,
'object' => $term->taxonomy,
'object_id' => intval( $term->term_id ),
'title' => html_entity_decode( $term->name ),
);
}
}
return $results;
}
/**
* Enqueue scripts and styles for Customizer pane.
*
* @since 4.3.0
*/
public function enqueue_scripts() {
wp_enqueue_style( 'customize-nav-menus' );
wp_enqueue_script( 'customize-nav-menus' );
$temp_nav_menu_setting = new WP_Customize_Nav_Menu_Setting( $this->manager, 'nav_menu[-1]' );
$temp_nav_menu_item_setting = new WP_Customize_Nav_Menu_Item_Setting( $this->manager, 'nav_menu_item[-1]' );
// Pass data to JS.
$settings = array(
'nonce' => wp_create_nonce( 'customize-menus' ),
'allMenus' => wp_get_nav_menus(),
'itemTypes' => $this->available_item_types(),
'l10n' => array(
'untitled' => _x( '(no label)', 'Missing menu item navigation label.' ),
'custom_label' => _x( 'Custom', 'Custom menu item type label.' ),
'menuLocation' => _x( '(Currently set to: %s)', 'Current menu location.' ),
'deleteWarn' => __( 'You are about to permanently delete this menu. "Cancel" to stop, "OK" to delete.' ),
'itemAdded' => __( 'Menu item added' ),
'itemDeleted' => __( 'Menu item deleted' ),
'menuAdded' => __( 'Menu created' ),
'menuDeleted' => __( 'Menu deleted' ),
'movedUp' => __( 'Menu item moved up' ),
'movedDown' => __( 'Menu item moved down' ),
'movedLeft' => __( 'Menu item moved out of submenu' ),
'movedRight' => __( 'Menu item is now a sub-item' ),
'customizingMenus' => _x( 'Customizing &#9656; Menus', '&#9656 is the unicode right-pointing triangle' ),
'invalidTitleTpl' => __( '%s (Invalid)' ),
'pendingTitleTpl' => __( '%s (Pending)' ),
'taxonomyTermLabel' => __( 'Taxonomy' ),
'postTypeLabel' => __( 'Post Type' ),
),
'menuItemTransport' => 'postMessage',
'phpIntMax' => PHP_INT_MAX,
'defaultSettingValues' => array(
'nav_menu' => $temp_nav_menu_setting->default,
'nav_menu_item' => $temp_nav_menu_item_setting->default,
),
);
$data = sprintf( 'var _wpCustomizeNavMenusSettings = %s;', wp_json_encode( $settings ) );
wp_scripts()->add_data( 'customize-nav-menus', 'data', $data );
// This is copied from nav-menus.php, and it has an unfortunate object name of `menus`.
$nav_menus_l10n = array(
'oneThemeLocationNoMenus' => null,
'moveUp' => __( 'Move up one' ),
'moveDown' => __( 'Move down one' ),
'moveToTop' => __( 'Move to the top' ),
/* translators: %s: previous item name */
'moveUnder' => __( 'Move under %s' ),
/* translators: %s: previous item name */
'moveOutFrom' => __( 'Move out from under %s' ),
/* translators: %s: previous item name */
'under' => __( 'Under %s' ),
/* translators: %s: previous item name */
'outFrom' => __( 'Out from under %s' ),
/* translators: 1: item name, 2: item position, 3: total number of items */
'menuFocus' => __( '%1$s. Menu item %2$d of %3$d.' ),
/* translators: 1: item name, 2: item position, 3: parent item name */
'subMenuFocus' => __( '%1$s. Sub item number %2$d under %3$s.' ),
);
wp_localize_script( 'nav-menu', 'menus', $nav_menus_l10n );
}
/**
* Filter a dynamic setting's constructor args.
*
* For a dynamic setting to be registered, this filter must be employed
* to override the default false value with an array of args to pass to
* the WP_Customize_Setting constructor.
*
* @since 4.3.0
*
* @param false|array $setting_args The arguments to the WP_Customize_Setting constructor.
* @param string $setting_id ID for dynamic setting, usually coming from `$_POST['customized']`.
* @return array|false
*/
public function filter_dynamic_setting_args( $setting_args, $setting_id ) {
if ( preg_match( WP_Customize_Nav_Menu_Setting::ID_PATTERN, $setting_id ) ) {
$setting_args = array(
'type' => WP_Customize_Nav_Menu_Setting::TYPE,
);
} else if ( preg_match( WP_Customize_Nav_Menu_Item_Setting::ID_PATTERN, $setting_id ) ) {
$setting_args = array(
'type' => WP_Customize_Nav_Menu_Item_Setting::TYPE,
);
}
return $setting_args;
}
/**
* Allow non-statically created settings to be constructed with custom WP_Customize_Setting subclass.
*
* @since 4.3.0
*
* @param string $setting_class WP_Customize_Setting or a subclass.
* @param string $setting_id ID for dynamic setting, usually coming from `$_POST['customized']`.
* @param array $setting_args WP_Customize_Setting or a subclass.
* @return string
*/
public function filter_dynamic_setting_class( $setting_class, $setting_id, $setting_args ) {
unset( $setting_id );
if ( ! empty( $setting_args['type'] ) && WP_Customize_Nav_Menu_Setting::TYPE === $setting_args['type'] ) {
$setting_class = 'WP_Customize_Nav_Menu_Setting';
} else if ( ! empty( $setting_args['type'] ) && WP_Customize_Nav_Menu_Item_Setting::TYPE === $setting_args['type'] ) {
$setting_class = 'WP_Customize_Nav_Menu_Item_Setting';
}
return $setting_class;
}
/**
* Add the customizer settings and controls.
*
* @since 4.3.0
*/
public function customize_register() {
// Require JS-rendered control types.
$this->manager->register_panel_type( 'WP_Customize_Nav_Menus_Panel' );
$this->manager->register_control_type( 'WP_Customize_Nav_Menu_Control' );
$this->manager->register_control_type( 'WP_Customize_Nav_Menu_Name_Control' );
$this->manager->register_control_type( 'WP_Customize_Nav_Menu_Item_Control' );
// Create a panel for Menus.
$this->manager->add_panel( new WP_Customize_Nav_Menus_Panel( $this->manager, 'nav_menus', array(
'title' => __( 'Menus' ),
'description' => '<p>' . __( 'This panel is used for managing navigation menus for content you have already published on your site. You can create menus and add items for existing content such as pages, posts, categories, tags, formats, or custom links.' ) . '</p><p>' . __( 'Menus can be displayed in locations defined by your theme or in widget areas by adding a "Custom Menu" widget.' ) . '</p>',
'priority' => 100,
// 'theme_supports' => 'menus|widgets', @todo allow multiple theme supports
) ) );
$menus = wp_get_nav_menus();
// Menu loactions.
$locations = get_registered_nav_menus();
$num_locations = count( array_keys( $locations ) );
$description = '<p>' . sprintf( _n( 'Your theme contains %s menu location. Select which menu you would like to use.', 'Your theme contains %s menu locations. Select which menu appears in each location.', $num_locations ), number_format_i18n( $num_locations ) );
$description .= '</p><p>' . __( 'You can also place menus in widget areas with the Custom Menu widget.' ) . '</p>';
$this->manager->add_section( 'menu_locations', array(
'title' => __( 'Menu Locations' ),
'panel' => 'nav_menus',
'priority' => 5,
'description' => $description,
) );
// @todo if ( ! $menus ) : make a "default" menu
if ( $menus ) {
$choices = array( '0' => __( '&mdash; Select &mdash;' ) );
foreach ( $menus as $menu ) {
$choices[ $menu->term_id ] = wp_html_excerpt( $menu->name, 40, '&hellip;' );
}
foreach ( $locations as $location => $description ) {
$setting_id = "nav_menu_locations[{$location}]";
$setting = $this->manager->get_setting( $setting_id );
if ( $setting ) {
$setting->transport = 'postMessage';
remove_filter( "customize_sanitize_{$setting_id}", 'absint' );
add_filter( "customize_sanitize_{$setting_id}", array( $this, 'intval_base10' ) );
} else {
$this->manager->add_setting( $setting_id, array(
'sanitize_callback' => array( $this, 'intval_base10' ),
'theme_supports' => 'menus',
'type' => 'theme_mod',
'transport' => 'postMessage',
) );
}
$this->manager->add_control( new WP_Customize_Nav_Menu_Location_Control( $this->manager, $setting_id, array(
'label' => $description,
'location_id' => $location,
'section' => 'menu_locations',
'choices' => $choices,
) ) );
}
}
// Register each menu as a Customizer section, and add each menu item to each menu.
foreach ( $menus as $menu ) {
$menu_id = $menu->term_id;
// Create a section for each menu.
$section_id = 'nav_menu[' . $menu_id . ']';
$this->manager->add_section( new WP_Customize_Nav_Menu_Section( $this->manager, $section_id, array(
'title' => html_entity_decode( $menu->name ),
'priority' => 10,
'panel' => 'nav_menus',
) ) );
$nav_menu_setting_id = 'nav_menu[' . $menu_id . ']';
$this->manager->add_setting( new WP_Customize_Nav_Menu_Setting( $this->manager, $nav_menu_setting_id ) );
// Add the menu contents.
$menu_items = (array) wp_get_nav_menu_items( $menu_id );
foreach ( array_values( $menu_items ) as $i => $item ) {
// Create a setting for each menu item (which doesn't actually manage data, currently).
$menu_item_setting_id = 'nav_menu_item[' . $item->ID . ']';
$this->manager->add_setting( new WP_Customize_Nav_Menu_Item_Setting( $this->manager, $menu_item_setting_id ) );
// Create a control for each menu item.
$this->manager->add_control( new WP_Customize_Nav_Menu_Item_Control( $this->manager, $menu_item_setting_id, array(
'label' => $item->title,
'section' => $section_id,
'priority' => 10 + $i,
) ) );
}
// Note: other controls inside of this section get added dynamically in JS via the MenuSection.ready() function.
}
// Add the add-new-menu section and controls.
$this->manager->add_section( new WP_Customize_New_Menu_Section( $this->manager, 'add_menu', array(
'title' => __( 'Add a Menu' ),
'panel' => 'nav_menus',
'priority' => 999,
) ) );
$this->manager->add_setting( 'new_menu_name', array(
'type' => 'new_menu',
'default' => '',
'transport' => 'postMessage',
) );
$this->manager->add_control( 'new_menu_name', array(
'label' => '',
'section' => 'add_menu',
'type' => 'text',
'input_attrs' => array(
'class' => 'menu-name-field',
'placeholder' => __( 'New menu name' ),
),
) );
$this->manager->add_setting( 'create_new_menu', array(
'type' => 'new_menu',
) );
$this->manager->add_control( new WP_New_Menu_Customize_Control( $this->manager, 'create_new_menu', array(
'section' => 'add_menu',
) ) );
}
/**
* Get the base10 intval.
*
* This is used as a setting's sanitize_callback; we can't use just plain
* intval because the second argument is not what intval() expects.
*
* @since 4.3.0
*
* @param mixed $value Number to convert.
*
* @return int
*/
function intval_base10( $value ) {
return intval( $value, 10 );
}
/**
* Return an array of all the available item types.
*
* @since 4.3.0
*/
public function available_item_types() {
$items = array(
'postTypes' => array(),
'taxonomies' => array(),
);
$post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' );
foreach ( $post_types as $slug => $post_type ) {
$items['postTypes'][ $slug ] = array(
'label' => $post_type->labels->singular_name,
);
}
$taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'objects' );
foreach ( $taxonomies as $slug => $taxonomy ) {
if ( 'post_format' === $taxonomy && ! current_theme_supports( 'post-formats' ) ) {
continue;
}
$items['taxonomies'][ $slug ] = array(
'label' => $taxonomy->labels->singular_name,
);
}
return $items;
}
/**
* Print the JavaScript templates used to render Menu Customizer components.
*
* Templates are imported into the JS use wp.template.
*
* @since 4.3.0
*/
public function print_templates() {
?>
<script type="text/html" id="tmpl-available-menu-item">
<div id="menu-item-tpl-{{ data.id }}" class="menu-item-tpl" data-menu-item-id="{{ data.id }}">
<dl class="menu-item-bar">
<dt class="menu-item-handle">
<span class="item-type">{{ data.type_label }}</span>
<span class="item-title">{{ data.title || wp.customize.Menus.data.l10n.untitled }}</span>
<button type="button" class="not-a-button item-add"><span class="screen-reader-text"><?php _e( 'Add Menu Item' ) ?></span></button>
</dt>
</dl>
</div>
</script>
<script type="text/html" id="tmpl-available-menu-item-type">
<div id="available-menu-items-{{ data.type }}" class="accordion-section">
<h4 class="accordion-section-title">{{ data.type_label }}</h4>
<div class="accordion-section-content">
</div>
</div>
</script>
<script type="text/html" id="tmpl-menu-item-reorder-nav">
<div class="menu-item-reorder-nav">
<?php
printf(
'<button type="button" class="menus-move-up">%1$s</button><button type="button" class="menus-move-down">%2$s</button><button type="button" class="menus-move-left">%3$s</button><button type="button" class="menus-move-right">%4$s</button>',
esc_html( 'Move up' ),
esc_html( 'Move down' ),
esc_html( 'Move one level up' ),
esc_html( 'Move one level down' )
);
?>
</div>
</script>
<?php
}
/**
* Print the html template used to render the add-menu-item frame.
*
* @since 4.3.0
*/
public function available_items_template() {
?>
<div id="available-menu-items" class="accordion-container">
<div class="customize-section-title">
<button type="button" class="customize-section-back" tabindex="-1">
<span class="screen-reader-text"><?php _e( 'Back' ); ?></span>
</button>
<h3>
<span class="customize-action">
<?php
/* translators: &#9656; is the unicode right-pointing triangle, and %s is the section title in the Customizer */
printf( __( 'Customizing &#9656; %s' ), esc_html( $this->manager->get_panel( 'nav_menus' )->title ) );
?>
</span>
<?php _e( 'Add Menu Items' ); ?>
</h3>
</div>
<div id="available-menu-items-search" class="accordion-section cannot-expand">
<div class="accordion-section-title">
<label class="screen-reader-text" for="menu-items-search"><?php _e( 'Search Menu Items' ); ?></label>
<input type="text" id="menu-items-search" placeholder="<?php esc_attr_e( 'Search menu items&hellip;' ) ?>" />
<span class="spinner"></span>
</div>
<div class="accordion-section-content" data-type="search"></div>
</div>
<div id="new-custom-menu-item" class="accordion-section">
<h4 class="accordion-section-title"><?php _e( 'Links' ); ?><button type="button" class="not-a-button"><span class="screen-reader-text"><?php _e( 'Toggle' ); ?></span></button></h4>
<div class="accordion-section-content">
<input type="hidden" value="custom" id="custom-menu-item-type" name="menu-item[-1][menu-item-type]" />
<p id="menu-item-url-wrap">
<label class="howto" for="custom-menu-item-url">
<span><?php _e( 'URL' ); ?></span>
<input id="custom-menu-item-url" name="menu-item[-1][menu-item-url]" type="text" class="code menu-item-textbox" value="http://">
</label>
</p>
<p id="menu-item-name-wrap">
<label class="howto" for="custom-menu-item-name">
<span><?php _e( 'Link Text' ); ?></span>
<input id="custom-menu-item-name" name="menu-item[-1][menu-item-title]" type="text" class="regular-text menu-item-textbox">
</label>
</p>
<p class="button-controls">
<span class="add-to-menu">
<input type="submit" class="button-secondary submit-add-to-menu right" value="<?php esc_attr_e( 'Add to Menu' ); ?>" name="add-custom-menu-item" id="custom-menu-item-submit">
<span class="spinner"></span>
</span>
</p>
</div>
</div>
<?php
// @todo: consider using add_meta_box/do_accordion_section and making screen-optional?
// Containers for per-post-type item browsing; items added with JS.
$post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'object' );
if ( $post_types ) :
foreach ( $post_types as $type ) :
?>
<div id="available-menu-items-<?php echo esc_attr( $type->name ); ?>" class="accordion-section">
<h4 class="accordion-section-title"><?php echo esc_html( $type->label ); ?> <span class="spinner"></span> <button type="button" class="not-a-button"><span class="screen-reader-text"><?php _e( 'Toggle' ); ?></span></button></h4>
<div class="accordion-section-content" data-type="<?php echo esc_attr( $type->name ); ?>" data-obj_type="post_type"></div>
</div>
<?php
endforeach;
endif;
$taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'object' );
if ( $taxonomies ) :
foreach ( $taxonomies as $tax ) :
?>
<div id="available-menu-items-<?php echo esc_attr( $tax->name ); ?>" class="accordion-section">
<h4 class="accordion-section-title"><?php echo esc_html( $tax->label ); ?> <span class="spinner"></span> <button type="button" class="not-a-button"><span class="screen-reader-text"><?php _e( 'Toggle' ); ?></span></button></h4>
<div class="accordion-section-content" data-type="<?php echo esc_attr( $tax->name ); ?>" data-obj_type="taxonomy"></div>
</div>
<?php
endforeach;
endif;
?>
</div><!-- #available-menu-items -->
<?php
}
// Start functionality specific to partial-refresh of menu changes in Customizer preview.
const RENDER_AJAX_ACTION = 'customize_render_menu_partial';
const RENDER_NONCE_POST_KEY = 'render-menu-nonce';
const RENDER_QUERY_VAR = 'wp_customize_menu_render';
/**
* The number of wp_nav_menu() calls which have happened in the preview.
*
* @since 4.3.0
*
* @var int
*/
public $preview_nav_menu_instance_number = 0;
/**
* Nav menu args used for each instance.
*
* @since 4.3.0
*
* @var array
*/
public $preview_nav_menu_instance_args = array();
/**
* Add hooks for the Customizer preview.
*
* @since 4.3.0
*/
function customize_preview_init() {
add_action( 'template_redirect', array( $this, 'render_menu' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue_deps' ) );
if ( ! isset( $_REQUEST[ self::RENDER_QUERY_VAR ] ) ) {
add_filter( 'wp_nav_menu_args', array( $this, 'filter_wp_nav_menu_args' ), 1000 );
add_filter( 'wp_nav_menu', array( $this, 'filter_wp_nav_menu' ), 10, 2 );
}
}
/**
* Keep track of the arguments that are being passed to wp_nav_menu().
*
* @since 4.3.0
*
* @see wp_nav_menu()
*
* @param array $args An array containing wp_nav_menu() arguments.
* @return array
*/
function filter_wp_nav_menu_args( $args ) {
$this->preview_nav_menu_instance_number += 1;
$args['instance_number'] = $this->preview_nav_menu_instance_number;
$can_partial_refresh = (
$args['echo']
&&
is_string( $args['fallback_cb'] )
&&
is_string( $args['walker'] )
);
$args['can_partial_refresh'] = $can_partial_refresh;
if ( ! $can_partial_refresh ) {
unset( $args['fallback_cb'] );
unset( $args['walker'] );
}
ksort( $args );
$args['args_hash'] = $this->hash_nav_menu_args( $args );
$this->preview_nav_menu_instance_args[ $this->preview_nav_menu_instance_number ] = $args;
return $args;
}
/**
* Prepare wp_nav_menu() calls for partial refresh. Wraps output in container for refreshing.
*
* @since 4.3.0
*
* @see wp_nav_menu()
*
* @param string $nav_menu_content The HTML content for the navigation menu.
* @param object $args An object containing wp_nav_menu() arguments.
* @return null
*/
function filter_wp_nav_menu( $nav_menu_content, $args ) {
if ( ! empty( $args->can_partial_refresh ) && ! empty( $args->instance_number ) ) {
$nav_menu_content = sprintf(
'<div id="partial-refresh-menu-container-%1$d" class="partial-refresh-menu-container" data-instance-number="%1$d">%2$s</div>',
$args->instance_number,
$nav_menu_content
);
}
return $nav_menu_content;
}
/**
* Hash (hmac) the arguments with the nonce and secret auth key to ensure they
* are not tampered with when submitted in the Ajax request.
*
* @since 4.3.0
*
* @param array $args The arguments to hash.
* @return string
*/
function hash_nav_menu_args( $args ) {
return wp_hash( wp_create_nonce( self::RENDER_AJAX_ACTION ) . serialize( $args ) );
}
/**
* Enqueue scripts for the Customizer preview.
*
* @since 4.3.0
*/
function customize_preview_enqueue_deps() {
wp_enqueue_script( 'customize-preview-nav-menus' );
wp_enqueue_style( 'customize-preview' );
add_action( 'wp_print_footer_scripts', array( $this, 'export_preview_data' ) );
}
/**
* Export data from PHP to JS.
*
* @since 4.3.0
*/
function export_preview_data() {
// Why not wp_localize_script? Because we're not localizing, and it forces values into strings.
$exports = array(
'renderQueryVar' => self::RENDER_QUERY_VAR,
'renderNonceValue' => wp_create_nonce( self::RENDER_AJAX_ACTION ),
'renderNoncePostKey' => self::RENDER_NONCE_POST_KEY,
'requestUri' => '/',
'theme' => array(
'stylesheet' => $this->manager->get_stylesheet(),
'active' => $this->manager->is_theme_active(),
),
'previewCustomizeNonce' => wp_create_nonce( 'preview-customize_' . $this->manager->get_stylesheet() ),
'navMenuInstanceArgs' => $this->preview_nav_menu_instance_args,
);
if ( ! empty( $_SERVER['REQUEST_URI'] ) ) {
$exports['requestUri'] = esc_url_raw( home_url( wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
}
printf( '<script>var _wpCustomizePreviewNavMenusExports = %s;</script>', wp_json_encode( $exports ) );
}
/**
* Render a specific menu via wp_nav_menu() using the supplied arguments.
*
* @since 4.3.0
*
* @see wp_nav_menu()
*/
function render_menu() {
if ( empty( $_POST[ self::RENDER_QUERY_VAR ] ) ) {
return;
}
$this->manager->remove_preview_signature();
if ( empty( $_POST[ self::RENDER_NONCE_POST_KEY ] ) ) {
wp_send_json_error( 'missing_nonce_param' );
}
if ( ! is_customize_preview() ) {
wp_send_json_error( 'expected_customize_preview' );
}
if ( ! check_ajax_referer( self::RENDER_AJAX_ACTION, self::RENDER_NONCE_POST_KEY, false ) ) {
wp_send_json_error( 'nonce_check_fail' );
}
if ( ! current_user_can( 'edit_theme_options' ) ) {
wp_send_json_error( 'unauthorized' );
}
if ( ! isset( $_POST['wp_nav_menu_args'] ) ) {
wp_send_json_error( 'missing_param' );
}
if ( ! isset( $_POST['wp_nav_menu_args_hash'] ) ) {
wp_send_json_error( 'missing_param' );
}
$wp_nav_menu_args = json_decode( wp_unslash( $_POST['wp_nav_menu_args'] ), true );
if ( ! is_array( $wp_nav_menu_args ) ) {
wp_send_json_error( 'wp_nav_menu_args_not_array' );
}
$wp_nav_menu_args_hash = sanitize_text_field( wp_unslash( $_POST['wp_nav_menu_args_hash'] ) );
if ( ! hash_equals( $this->hash_nav_menu_args( $wp_nav_menu_args ), $wp_nav_menu_args_hash ) ) {
wp_send_json_error( 'wp_nav_menu_args_hash_mismatch' );
}
$wp_nav_menu_args['echo'] = false;
wp_send_json_success( wp_nav_menu( $wp_nav_menu_args ) );
}
}

View File

@ -501,3 +501,74 @@ class WP_Customize_Sidebar_Section extends WP_Customize_Section {
return $this->manager->widgets->is_sidebar_rendered( $this->sidebar_id );
}
}
/**
* Customize Menu Section Class
*
* Custom section only needed in JS.
*
* @since 4.3.0
*/
class WP_Customize_Nav_Menu_Section extends WP_Customize_Section {
/**
* Control type.
*
* @since 4.3.0
*
* @access public
* @var string
*/
public $type = 'nav_menu';
/**
* Get section params for JS.
*
* @since 4.3.0
*
* @return array
*/
function json() {
$exported = parent::json();
$exported['menu_id'] = intval( preg_replace( '/^nav_menu\[(\d+)\]/', '$1', $this->id ) );
return $exported;
}
}
/**
* Customize Menu Section Class
*
* Implements the new-menu-ui toggle button instead of a regular section.
*
* @since 4.3.0
*/
class WP_Customize_New_Menu_Section extends WP_Customize_Section {
/**
* Control type.
*
* @since 4.3.0
*
* @access public
* @var string
*/
public $type = 'new_menu';
/**
* Render the section, and the controls that have been added to it.
*
* @since 4.3.0
*/
protected function render() {
?>
<li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="accordion-section-new-menu">
<button type="button" class="button-secondary add-new-menu-item add-menu-toggle">
<?php echo esc_html( $this->title ); ?>
<span class="screen-reader-text"><?php _e( 'Press return or enter to open' ); ?></span>
</button>
<ul class="new-menu-section-content"></ul>
</li>
<?php
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
.customize-partial-refreshing {
opacity: 0.25;
-webkit-transition: opacity 0.25s;
transition: opacity 0.25s;
cursor: progress;
}

View File

@ -0,0 +1,242 @@
/*global jQuery, JSON, _wpCustomizePreviewNavMenusExports, _ */
wp.customize.menusPreview = ( function( $, api ) {
'use strict';
var self;
self = {
renderQueryVar: null,
renderNonceValue: null,
renderNoncePostKey: null,
previewCustomizeNonce: null,
previewReady: $.Deferred(),
requestUri: '/',
theme: {
active: false,
stylesheet: ''
},
navMenuInstanceArgs: {},
refreshDebounceDelay: 200
};
api.bind( 'preview-ready', function() {
self.previewReady.resolve();
} );
self.previewReady.done( function() {
self.init();
} );
/**
* Bootstrap functionality.
*/
self.init = function() {
var self = this;
if ( 'undefined' !== typeof _wpCustomizePreviewNavMenusExports ) {
$.extend( self, _wpCustomizePreviewNavMenusExports );
}
self.previewReady.done( function() {
api.each( function( setting, id ) {
setting.id = id;
self.bindListener( setting );
} );
api.preview.bind( 'setting', function( args ) {
var id, value, setting;
args = args.slice();
id = args.shift();
value = args.shift();
if ( ! api.has( id ) ) {
// Currently customize-preview.js is not creating settings for dynamically-created settings in the pane; so we have to do it
setting = api.create( id, value ); // @todo This should be in core
setting.id = id;
if ( self.bindListener( setting ) ) {
setting.callbacks.fireWith( setting, [ setting(), setting() ] );
}
}
} );
} );
};
/**
*
* @param {wp.customize.Value} setting
* @returns {boolean} Whether the setting was bound.
*/
self.bindListener = function( setting ) {
var matches, themeLocation;
matches = setting.id.match( /^nav_menu\[(-?\d+)]$/ );
if ( matches ) {
setting.navMenuId = parseInt( matches[1], 10 );
setting.bind( self.onChangeNavMenuSetting );
return true;
}
matches = setting.id.match( /^nav_menu_item\[(-?\d+)]$/ );
if ( matches ) {
setting.navMenuItemId = parseInt( matches[1], 10 );
setting.bind( self.onChangeNavMenuItemSetting );
return true;
}
matches = setting.id.match( /^nav_menu_locations\[(.+?)]/ );
if ( matches ) {
themeLocation = matches[1];
setting.bind( function() {
self.refreshMenuLocation( themeLocation );
} );
return true;
}
return false;
};
/**
* Handle changing of a nav_menu setting.
*
* @this {wp.customize.Setting}
*/
self.onChangeNavMenuSetting = function() {
var setting = this;
if ( ! setting.navMenuId ) {
throw new Error( 'Expected navMenuId property to be set.' );
}
self.refreshMenu( setting.navMenuId );
};
/**
* Handle changing of a nav_menu_item setting.
*
* @this {wp.customize.Setting}
* @param {object} to
* @param {object} from
*/
self.onChangeNavMenuItemSetting = function( to, from ) {
if ( from && from.nav_menu_term_id && ( ! to || from.nav_menu_term_id !== to.nav_menu_term_id ) ) {
self.refreshMenu( from.nav_menu_term_id );
}
if ( to && to.nav_menu_term_id ) {
self.refreshMenu( to.nav_menu_term_id );
}
};
/**
* Update a given menu rendered in the preview.
*
* @param {int} menuId
*/
self.refreshMenu = function( menuId ) {
var self = this, assignedLocations = [];
api.each(function( setting, id ) {
var matches = id.match( /^nav_menu_locations\[(.+?)]/ );
if ( matches && menuId === setting() ) {
assignedLocations.push( matches[1] );
}
});
_.each( self.navMenuInstanceArgs, function( navMenuArgs, instanceNumber ) {
if ( menuId === navMenuArgs.menu || -1 !== _.indexOf( assignedLocations, navMenuArgs.theme_location ) ) {
self.refreshMenuInstanceDebounced( instanceNumber );
}
} );
};
self.refreshMenuLocation = function( location ) {
var foundInstance = false;
_.each( self.navMenuInstanceArgs, function( navMenuArgs, instanceNumber ) {
if ( location === navMenuArgs.theme_location ) {
self.refreshMenuInstanceDebounced( instanceNumber );
foundInstance = true;
}
} );
if ( ! foundInstance ) {
api.preview.send( 'refresh' );
}
};
/**
* Update a specific instance of a given menu on the page.
*
* @param {int} instanceNumber
*/
self.refreshMenuInstance = function( instanceNumber ) {
var self = this, data, customized, container, request, wpNavArgs, instance;
if ( ! self.navMenuInstanceArgs[ instanceNumber ] ) {
throw new Error( 'unknown_instance_number' );
}
instance = self.navMenuInstanceArgs[ instanceNumber ];
container = $( '#partial-refresh-menu-container-' + String( instanceNumber ) );
if ( ! instance.can_partial_refresh || 0 === container.length ) {
api.preview.send( 'refresh' );
return;
}
data = {
nonce: self.previewCustomizeNonce, // for Customize Preview
wp_customize: 'on'
};
if ( ! self.theme.active ) {
data.theme = self.theme.stylesheet;
}
data[ self.renderQueryVar ] = '1';
customized = {};
api.each( function( setting, id ) {
// @todo We need to limit this to just the menu items that are associated with this menu/location.
if ( /^(nav_menu|nav_menu_locations)/.test( id ) ) {
customized[ id ] = setting.get();
}
} );
data.customized = JSON.stringify( customized );
data[ self.renderNoncePostKey ] = self.renderNonceValue;
wpNavArgs = $.extend( {}, instance );
data.wp_nav_menu_args_hash = wpNavArgs.args_hash;
delete wpNavArgs.args_hash;
data.wp_nav_menu_args = JSON.stringify( wpNavArgs );
container.addClass( 'customize-partial-refreshing' );
request = wp.ajax.send( null, {
data: data,
url: self.requestUri
} );
request.done( function( data ) {
var eventParam;
container.empty().append( $( data ) );
eventParam = {
instanceNumber: instanceNumber,
wpNavArgs: wpNavArgs
};
$( document ).trigger( 'customize-preview-menu-refreshed', [ eventParam ] );
} );
request.fail( function() {
// @todo provide some indication for why
} );
request.always( function() {
container.removeClass( 'customize-partial-refreshing' );
} );
};
self.currentRefreshMenuInstanceDebouncedCalls = {};
self.refreshMenuInstanceDebounced = function( instanceNumber ) {
if ( self.currentRefreshMenuInstanceDebouncedCalls[ instanceNumber ] ) {
clearTimeout( self.currentRefreshMenuInstanceDebouncedCalls[ instanceNumber ] );
}
self.currentRefreshMenuInstanceDebouncedCalls[ instanceNumber ] = setTimeout(
function() {
self.refreshMenuInstance( instanceNumber );
},
self.refreshDebounceDelay
);
};
return self;
}( jQuery, wp.customize ) );

View File

@ -406,6 +406,9 @@ function wp_default_scripts( &$scripts ) {
$scripts->add( 'customize-widgets', "/wp-admin/js/customize-widgets$suffix.js", array( 'jquery', 'jquery-ui-sortable', 'jquery-ui-droppable', 'wp-backbone', 'customize-controls' ), false, 1 );
$scripts->add( 'customize-preview-widgets', "/wp-includes/js/customize-preview-widgets$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );
$scripts->add( 'customize-nav-menus', "/wp-admin/js/customize-nav-menus$suffix.js", array( 'jquery', 'wp-backbone', 'customize-controls', 'accordion', 'nav-menu', 'wp-a11y' ), false, 1 );
$scripts->add( 'customize-preview-nav-menus', "/wp-includes/js/customize-preview-nav-menus$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );
$scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 );
$scripts->add( 'shortcode', "/wp-includes/js/shortcode$suffix.js", array( 'underscore' ), false, 1 );
@ -656,15 +659,16 @@ function wp_default_styles( &$styles ) {
$suffix = SCRIPT_DEBUG ? '' : '.min';
// Admin CSS
$styles->add( 'wp-admin', "/wp-admin/css/wp-admin$suffix.css", array( 'open-sans', 'dashicons' ) );
$styles->add( 'login', "/wp-admin/css/login$suffix.css", array( 'buttons', 'open-sans', 'dashicons' ) );
$styles->add( 'install', "/wp-admin/css/install$suffix.css", array( 'buttons', 'open-sans' ) );
$styles->add( 'wp-color-picker', "/wp-admin/css/color-picker$suffix.css" );
$styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie', 'imgareaselect' ) );
$styles->add( 'customize-widgets', "/wp-admin/css/customize-widgets$suffix.css", array( 'wp-admin', 'colors' ) );
$styles->add( 'press-this', "/wp-admin/css/press-this$suffix.css", array( 'open-sans', 'buttons' ) );
$styles->add( 'wp-admin', "/wp-admin/css/wp-admin$suffix.css", array( 'open-sans', 'dashicons' ) );
$styles->add( 'login', "/wp-admin/css/login$suffix.css", array( 'buttons', 'open-sans', 'dashicons' ) );
$styles->add( 'install', "/wp-admin/css/install$suffix.css", array( 'buttons', 'open-sans' ) );
$styles->add( 'wp-color-picker', "/wp-admin/css/color-picker$suffix.css" );
$styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie', 'imgareaselect' ) );
$styles->add( 'customize-widgets', "/wp-admin/css/customize-widgets$suffix.css", array( 'wp-admin', 'colors' ) );
$styles->add( 'customize-nav-menus', "/wp-admin/css/customize-nav-menus$suffix.css", array( 'wp-admin', 'colors' ) );
$styles->add( 'press-this', "/wp-admin/css/press-this$suffix.css", array( 'open-sans', 'buttons' ) );
$styles->add( 'ie', "/wp-admin/css/ie$suffix.css" );
$styles->add( 'ie', "/wp-admin/css/ie$suffix.css" );
$styles->add_data( 'ie', 'conditional', 'lte IE 7' );
// Common dependencies
@ -673,11 +677,12 @@ function wp_default_styles( &$styles ) {
$styles->add( 'open-sans', $open_sans_font_url );
// Includes CSS
$styles->add( 'admin-bar', "/wp-includes/css/admin-bar$suffix.css", array( 'open-sans', 'dashicons' ) );
$styles->add( 'wp-auth-check', "/wp-includes/css/wp-auth-check$suffix.css", array( 'dashicons' ) );
$styles->add( 'editor-buttons', "/wp-includes/css/editor$suffix.css", array( 'dashicons' ) );
$styles->add( 'media-views', "/wp-includes/css/media-views$suffix.css", array( 'buttons', 'dashicons', 'wp-mediaelement' ) );
$styles->add( 'wp-pointer', "/wp-includes/css/wp-pointer$suffix.css", array( 'dashicons' ) );
$styles->add( 'admin-bar', "/wp-includes/css/admin-bar$suffix.css", array( 'open-sans', 'dashicons' ) );
$styles->add( 'wp-auth-check', "/wp-includes/css/wp-auth-check$suffix.css", array( 'dashicons' ) );
$styles->add( 'editor-buttons', "/wp-includes/css/editor$suffix.css", array( 'dashicons' ) );
$styles->add( 'media-views', "/wp-includes/css/media-views$suffix.css", array( 'buttons', 'dashicons', 'wp-mediaelement' ) );
$styles->add( 'wp-pointer', "/wp-includes/css/wp-pointer$suffix.css", array( 'dashicons' ) );
$styles->add( 'customize-preview', "/wp-includes/css/customize-preview$suffix.css" );
// External libraries and friends
$styles->add( 'imgareaselect', '/wp-includes/js/imgareaselect/imgareaselect.css', array(), '0.9.8' );
@ -695,7 +700,7 @@ function wp_default_styles( &$styles ) {
// RTL CSS
$rtl_styles = array(
// wp-admin
'wp-admin', 'install', 'wp-color-picker', 'customize-controls', 'customize-widgets', 'ie', 'login', 'press-this',
'wp-admin', 'install', 'wp-color-picker', 'customize-controls', 'customize-widgets', 'customize-nav-menus', 'ie', 'login', 'press-this',
// wp-includes
'buttons', 'admin-bar', 'wp-auth-check', 'editor-buttons', 'media-views', 'wp-pointer',
'wp-jquery-ui-dialog',

View File

@ -0,0 +1,625 @@
<?php
/**
* Tests WP_Customize_Nav_Menu_Item_Setting.
*
* @group customize
*/
class Test_WP_Customize_Nav_Menu_Item_Setting extends WP_UnitTestCase {
/**
* Instance of WP_Customize_Manager which is reset for each test.
*
* @var WP_Customize_Manager
*/
public $wp_customize;
/**
* Set up a test case.
*
* @see WP_UnitTestCase::setup()
*/
function setUp() {
parent::setUp();
require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
wp_set_current_user( $this->factory->user->create( array( 'role' => 'administrator' ) ) );
global $wp_customize;
$this->wp_customize = new WP_Customize_Manager();
$wp_customize = $this->wp_customize;
}
/**
* Delete the $wp_customize global when cleaning up scope.
*/
function clean_up_global_scope() {
global $wp_customize;
$wp_customize = null;
parent::clean_up_global_scope();
}
/**
* Test constants and statics.
*/
function test_constants() {
do_action( 'customize_register', $this->wp_customize );
$this->assertTrue( post_type_exists( WP_Customize_Nav_Menu_Item_Setting::POST_TYPE ) );
}
/**
* Test constructor.
*
* @see WP_Customize_Nav_Menu_Item_Setting::__construct()
*/
function test_construct() {
do_action( 'customize_register', $this->wp_customize );
$setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, 'nav_menu_item[123]' );
$this->assertEquals( 'nav_menu_item', $setting->type );
$this->assertEquals( 'postMessage', $setting->transport );
$this->assertEquals( 123, $setting->post_id );
$this->assertNull( $setting->previous_post_id );
$this->assertNull( $setting->update_status );
$this->assertNull( $setting->update_error );
$this->assertInternalType( 'array', $setting->default );
$default = array(
'object_id' => 0,
'object' => '',
'menu_item_parent' => 0,
'position' => 0,
'type' => 'custom',
'title' => '',
'url' => '',
'target' => '',
'attr_title' => '',
'description' => '',
'classes' => '',
'xfn' => '',
'status' => 'publish',
'original_title' => '',
'nav_menu_term_id' => 0,
);
$this->assertEquals( $default, $setting->default );
$exception = null;
try {
$bad_setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, 'foo_bar_baz' );
unset( $bad_setting );
} catch ( Exception $e ) {
$exception = $e;
}
$this->assertInstanceOf( 'Exception', $exception );
}
/**
* Test empty constructor.
*/
function test_construct_empty_menus() {
do_action( 'customize_register', $this->wp_customize );
$_wp_customize = $this->wp_customize;
unset($_wp_customize->nav_menus);
$exception = null;
try {
$bad_setting = new WP_Customize_Nav_Menu_Item_Setting( $_wp_customize, 'nav_menu_item[123]' );
unset( $bad_setting );
} catch ( Exception $e ) {
$exception = $e;
}
$this->assertInstanceOf( 'Exception', $exception );
}
/**
* Test constructor for placeholder (draft) menu.
*
* @see WP_Customize_Nav_Menu_Item_Setting::__construct()
*/
function test_construct_placeholder() {
do_action( 'customize_register', $this->wp_customize );
$default = array(
'title' => 'Lorem',
'description' => 'ipsum',
'menu_item_parent' => 123,
);
$setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, 'nav_menu_item[-5]', compact( 'default' ) );
$this->assertEquals( -5, $setting->post_id );
$this->assertNull( $setting->previous_post_id );
$this->assertEquals( $default, $setting->default );
}
/**
* Test value method with post.
*
* @see WP_Customize_Nav_Menu_Item_Setting::value()
*/
function test_value_type_post_type() {
do_action( 'customize_register', $this->wp_customize );
$post_id = $this->factory->post->create( array( 'post_title' => 'Hello World' ) );
$menu_id = wp_create_nav_menu( 'Menu' );
$item_title = 'Greetings';
$item_id = wp_update_nav_menu_item( $menu_id, 0, array(
'menu-item-type' => 'post_type',
'menu-item-object' => 'post',
'menu-item-object-id' => $post_id,
'menu-item-title' => $item_title,
'menu-item-status' => 'publish',
) );
$post = get_post( $item_id );
$menu_item = wp_setup_nav_menu_item( $post );
$this->assertEquals( $item_title, $menu_item->title );
$setting_id = "nav_menu_item[$item_id]";
$setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, $setting_id );
$value = $setting->value();
$this->assertEquals( $menu_item->title, $value['title'] );
$this->assertEquals( $menu_item->type, $value['type'] );
$this->assertEquals( $menu_item->object_id, $value['object_id'] );
$this->assertEquals( $menu_id, $value['nav_menu_term_id'] );
$this->assertEquals( 'Hello World', $value['original_title'] );
$other_menu_id = wp_create_nav_menu( 'Menu2' );
wp_update_nav_menu_item( $other_menu_id, $item_id, array(
'menu-item-title' => 'Hola',
) );
$value = $setting->value();
$this->assertEquals( 'Hola', $value['title'] );
$this->assertEquals( $other_menu_id, $value['nav_menu_term_id'] );
}
/**
* Test value method with taxonomy.
*
* @see WP_Customize_Nav_Menu_Item_Setting::value()
*/
function test_value_type_taxonomy() {
do_action( 'customize_register', $this->wp_customize );
$tax_id = $this->factory->category->create( array( 'name' => 'Salutations' ) );
$menu_id = wp_create_nav_menu( 'Menu' );
$item_title = 'Greetings';
$item_id = wp_update_nav_menu_item( $menu_id, 0, array(
'menu-item-type' => 'taxonomy',
'menu-item-object' => 'category',
'menu-item-object-id' => $tax_id,
'menu-item-title' => $item_title,
'menu-item-status' => 'publish',
) );
$post = get_post( $item_id );
$menu_item = wp_setup_nav_menu_item( $post );
$this->assertEquals( $item_title, $menu_item->title );
$setting_id = "nav_menu_item[$item_id]";
$setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, $setting_id );
$value = $setting->value();
$this->assertEquals( $menu_item->title, $value['title'] );
$this->assertEquals( $menu_item->type, $value['type'] );
$this->assertEquals( $menu_item->object_id, $value['object_id'] );
$this->assertEquals( $menu_id, $value['nav_menu_term_id'] );
$this->assertEquals( 'Salutations', $value['original_title'] );
}
/**
* Test value method returns zero for nav_menu_term_id when previewing a new menu.
*
* @see WP_Customize_Nav_Menu_Item_Setting::value()
*/
function test_value_nav_menu_term_id_returns_zero() {
do_action( 'customize_register', $this->wp_customize );
$menu_id = -123;
$post_value = array(
'name' => 'Secondary',
'description' => '',
'parent' => 0,
'auto_add' => false,
);
$setting_id = "nav_menu[$menu_id]";
$menu = new WP_Customize_Nav_Menu_Setting( $this->wp_customize, $setting_id );
$this->wp_customize->set_post_value( $menu->id, $post_value );
$menu->preview();
$value = $menu->value();
$this->assertEquals( $post_value, $value );
$post_id = $this->factory->post->create( array( 'post_title' => 'Hello World' ) );
$item_id = wp_update_nav_menu_item( $menu_id, 0, array(
'menu-item-type' => 'post_type',
'menu-item-object' => 'post',
'menu-item-object-id' => $post_id,
'menu-item-title' => 'Hello World',
'menu-item-status' => 'publish',
) );
$post = get_post( $item_id );
$menu_item = wp_setup_nav_menu_item( $post );
$setting_id = "nav_menu_item[$item_id]";
$setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, $setting_id );
$value = $setting->value();
$this->assertEquals( 0, $value['nav_menu_term_id'] );
}
/**
* Test preview method for updated menu.
*
* @see WP_Customize_Nav_Menu_Item_Setting::preview()
*/
function test_preview_updated() {
do_action( 'customize_register', $this->wp_customize );
$first_post_id = $this->factory->post->create( array( 'post_title' => 'Hello World' ) );
$second_post_id = $this->factory->post->create( array( 'post_title' => 'Hola Muno' ) );
$primary_menu_id = wp_create_nav_menu( 'Primary' );
$secondary_menu_id = wp_create_nav_menu( 'Secondary' );
$item_title = 'Greetings';
$item_id = wp_update_nav_menu_item( $primary_menu_id, 0, array(
'menu-item-type' => 'post_type',
'menu-item-object' => 'post',
'menu-item-object-id' => $first_post_id,
'menu-item-title' => $item_title,
'menu-item-status' => 'publish',
) );
$this->assertNotEmpty( wp_get_nav_menu_items( $primary_menu_id, array( 'post_status' => 'publish,draft' ) ) );
$post_value = array(
'type' => 'post_type',
'object' => 'post',
'object_id' => $second_post_id,
'title' => 'Saludos',
'status' => 'publish',
'nav_menu_term_id' => $secondary_menu_id,
);
$setting_id = "nav_menu_item[$item_id]";
$setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, $setting_id );
$this->wp_customize->set_post_value( $setting_id, $post_value );
unset( $post_value['nav_menu_term_id'] );
$setting->preview();
// Make sure the menu item appears in the new menu.
$this->assertNotContains( $item_id, wp_list_pluck( wp_get_nav_menu_items( $primary_menu_id ), 'db_id' ) );
$menu_items = wp_get_nav_menu_items( $secondary_menu_id );
$db_ids = wp_list_pluck( $menu_items, 'db_id' );
$this->assertContains( $item_id, $db_ids );
$i = array_search( $item_id, $db_ids );
$updated_item = $menu_items[ $i ];
$post_value['post_status'] = $post_value['status'];
unset( $post_value['status'] );
foreach ( $post_value as $key => $value ) {
$this->assertEquals( $value, $updated_item->$key, "Key $key mismatch" );
}
}
/**
* Test preview method for inserted menu.
*
* @see WP_Customize_Nav_Menu_Item_Setting::preview()
*/
function test_preview_inserted() {
do_action( 'customize_register', $this->wp_customize );
$menu_id = wp_create_nav_menu( 'Primary' );
$post_id = $this->factory->post->create( array( 'post_title' => 'Hello World' ) );
$item_ids = array();
for ( $i = 0; $i < 5; $i += 1 ) {
$item_id = wp_update_nav_menu_item( $menu_id, 0, array(
'menu-item-type' => 'post_type',
'menu-item-object' => 'post',
'menu-item-object-id' => $post_id,
'menu-item-title' => "Item $i",
'menu-item-status' => 'publish',
'menu-item-position' => $i + 1,
) );
$item_ids[] = $item_id;
}
$post_value = array(
'type' => 'post_type',
'object' => 'post',
'object_id' => $post_id,
'title' => 'Inserted item',
'status' => 'publish',
'nav_menu_term_id' => $menu_id,
'position' => count( $item_ids ) + 1,
);
$new_item_id = -10;
$setting_id = "nav_menu_item[$new_item_id]";
$setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, $setting_id );
$this->wp_customize->set_post_value( $setting_id, $post_value );
unset( $post_value['nav_menu_term_id'] );
$current_items = wp_get_nav_menu_items( $menu_id );
$setting->preview();
$preview_items = wp_get_nav_menu_items( $menu_id );
$this->assertNotEquals( count( $current_items ), count( $preview_items ) );
$last_item = array_pop( $preview_items );
$this->assertEquals( $new_item_id, $last_item->db_id );
$post_value['post_status'] = $post_value['status'];
unset( $post_value['status'] );
$post_value['menu_order'] = $post_value['position'];
unset( $post_value['position'] );
foreach ( $post_value as $key => $value ) {
$this->assertEquals( $value, $last_item->$key, "Mismatch for $key property." );
}
}
/**
* Test preview method for deleted menu.
*
* @see WP_Customize_Nav_Menu_Item_Setting::preview()
*/
function test_preview_deleted() {
do_action( 'customize_register', $this->wp_customize );
$menu_id = wp_create_nav_menu( 'Primary' );
$post_id = $this->factory->post->create( array( 'post_title' => 'Hello World' ) );
$item_ids = array();
for ( $i = 0; $i < 5; $i += 1 ) {
$item_id = wp_update_nav_menu_item( $menu_id, 0, array(
'menu-item-type' => 'post_type',
'menu-item-object' => 'post',
'menu-item-object-id' => $post_id,
'menu-item-title' => "Item $i",
'menu-item-status' => 'publish',
'menu-item-position' => $i + 1,
) );
$item_ids[] = $item_id;
}
$delete_item_id = $item_ids[2];
$setting_id = "nav_menu_item[$delete_item_id]";
$setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, $setting_id );
$this->wp_customize->set_post_value( $setting_id, false );
$current_items = wp_get_nav_menu_items( $menu_id );
$this->assertContains( $delete_item_id, wp_list_pluck( $current_items, 'db_id' ) );
$setting->preview();
$preview_items = wp_get_nav_menu_items( $menu_id );
$this->assertNotEquals( count( $current_items ), count( $preview_items ) );
$this->assertContains( $delete_item_id, wp_list_pluck( $current_items, 'db_id' ) );
}
/**
* Test sanitize method.
*
* @see WP_Customize_Nav_Menu_Item_Setting::sanitize()
*/
function test_sanitize() {
do_action( 'customize_register', $this->wp_customize );
$setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, 'nav_menu_item[123]' );
$this->assertNull( $setting->sanitize( 'not an array' ) );
$this->assertNull( $setting->sanitize( 123 ) );
$unsanitized = array(
'object_id' => 'bad',
'object' => '<b>hello</b>',
'menu_item_parent' => 'asdasd',
'position' => -123,
'type' => 'custom<b>',
'title' => 'Hi<script>alert(1)</script>',
'url' => 'javascript:alert(1)',
'target' => '" onclick="',
'attr_title' => '<b>evil</b>',
'description' => '<b>Hello world</b>',
'classes' => 'hello " inject="',
'xfn' => 'hello " inject="',
'status' => 'forbidden',
'original_title' => 'Hi<script>alert(1)</script>',
'nav_menu_term_id' => 'heilo',
);
$sanitized = $setting->sanitize( $unsanitized );
$this->assertEqualSets( array_keys( $unsanitized ), array_keys( $sanitized ) );
$this->assertEquals( 0, $sanitized['object_id'] );
$this->assertEquals( 'bhellob', $sanitized['object'] );
$this->assertEquals( 0, $sanitized['menu_item_parent'] );
$this->assertEquals( 0, $sanitized['position'] );
$this->assertEquals( 'customb', $sanitized['type'] );
$this->assertEquals( 'Hi', $sanitized['title'] );
$this->assertEquals( '', $sanitized['url'] );
$this->assertEquals( 'onclick', $sanitized['target'] );
$this->assertEquals( 'evil', $sanitized['attr_title'] );
$this->assertEquals( 'Hello world', $sanitized['description'] );
$this->assertEquals( 'hello inject', $sanitized['classes'] );
$this->assertEquals( 'hello inject', $sanitized['xfn'] );
$this->assertEquals( 'publish', $sanitized['status'] );
$this->assertEquals( 'Hi', $sanitized['original_title'] );
$this->assertEquals( 0, $sanitized['nav_menu_term_id'] );
}
/**
* Test protected update() method via the save() method, for updated menu.
*
* @see WP_Customize_Nav_Menu_Item_Setting::update()
*/
function test_save_updated() {
do_action( 'customize_register', $this->wp_customize );
$first_post_id = $this->factory->post->create( array( 'post_title' => 'Hello World' ) );
$second_post_id = $this->factory->post->create( array( 'post_title' => 'Hola Muno' ) );
$primary_menu_id = wp_create_nav_menu( 'Primary' );
$secondary_menu_id = wp_create_nav_menu( 'Secondary' );
$item_title = 'Greetings';
$item_id = wp_update_nav_menu_item( $primary_menu_id, 0, array(
'menu-item-type' => 'post_type',
'menu-item-object' => 'post',
'menu-item-object-id' => $first_post_id,
'menu-item-title' => $item_title,
'menu-item-status' => 'publish',
) );
$this->assertNotEmpty( wp_get_nav_menu_items( $primary_menu_id, array( 'post_status' => 'publish,draft' ) ) );
$post_value = array(
'type' => 'post_type',
'object' => 'post',
'object_id' => $second_post_id,
'title' => 'Saludos',
'status' => 'publish',
'nav_menu_term_id' => $secondary_menu_id,
);
$setting_id = "nav_menu_item[$item_id]";
$setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, $setting_id );
$this->wp_customize->set_post_value( $setting_id, $post_value );
unset( $post_value['nav_menu_term_id'] );
$setting->save();
// Make sure the menu item appears in the new menu.
$this->assertNotContains( $item_id, wp_list_pluck( wp_get_nav_menu_items( $primary_menu_id ), 'db_id' ) );
$menu_items = wp_get_nav_menu_items( $secondary_menu_id );
$db_ids = wp_list_pluck( $menu_items, 'db_id' );
$this->assertContains( $item_id, $db_ids );
$i = array_search( $item_id, $db_ids );
$updated_item = $menu_items[ $i ];
$post_value['post_status'] = $post_value['status'];
unset( $post_value['status'] );
foreach ( $post_value as $key => $value ) {
$this->assertEquals( $value, $updated_item->$key, "Key $key mismatch" );
}
// Verify the Ajax responses is being amended.
$save_response = apply_filters( 'customize_save_response', array() );
$this->assertArrayHasKey( 'nav_menu_item_updates', $save_response );
$update_result = array_shift( $save_response['nav_menu_item_updates'] );
$this->assertArrayHasKey( 'post_id', $update_result );
$this->assertArrayHasKey( 'previous_post_id', $update_result );
$this->assertArrayHasKey( 'error', $update_result );
$this->assertArrayHasKey( 'status', $update_result );
$this->assertEquals( $item_id, $update_result['post_id'] );
$this->assertNull( $update_result['previous_post_id'] );
$this->assertNull( $update_result['error'] );
$this->assertEquals( 'updated', $update_result['status'] );
}
/**
* Test protected update() method via the save() method, for inserted menu.
*
* @see WP_Customize_Nav_Menu_Item_Setting::update()
*/
function test_save_inserted() {
do_action( 'customize_register', $this->wp_customize );
$menu_id = wp_create_nav_menu( 'Primary' );
$post_id = $this->factory->post->create( array( 'post_title' => 'Hello World' ) );
$item_ids = array();
for ( $i = 0; $i < 5; $i += 1 ) {
$item_id = wp_update_nav_menu_item( $menu_id, 0, array(
'menu-item-type' => 'post_type',
'menu-item-object' => 'post',
'menu-item-object-id' => $post_id,
'menu-item-title' => "Item $i",
'menu-item-status' => 'publish',
'menu-item-position' => $i + 1,
) );
$item_ids[] = $item_id;
}
$post_value = array(
'type' => 'post_type',
'object' => 'post',
'object_id' => $post_id,
'title' => 'Inserted item',
'status' => 'publish',
'nav_menu_term_id' => $menu_id,
'position' => count( $item_ids ) + 1,
);
$new_item_id = -10;
$setting_id = "nav_menu_item[$new_item_id]";
$setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, $setting_id );
$this->wp_customize->set_post_value( $setting_id, $post_value );
unset( $post_value['nav_menu_term_id'] );
$current_items = wp_get_nav_menu_items( $menu_id );
$setting->save();
$preview_items = wp_get_nav_menu_items( $menu_id );
$this->assertNotEquals( count( $current_items ), count( $preview_items ) );
$last_item = array_pop( $preview_items );
$this->assertEquals( $setting->post_id, $last_item->db_id );
$post_value['post_status'] = $post_value['status'];
unset( $post_value['status'] );
$post_value['menu_order'] = $post_value['position'];
unset( $post_value['position'] );
foreach ( $post_value as $key => $value ) {
$this->assertEquals( $value, $last_item->$key, "Mismatch for $key property." );
}
// Verify the Ajax responses is being amended.
$save_response = apply_filters( 'customize_save_response', array() );
$this->assertArrayHasKey( 'nav_menu_item_updates', $save_response );
$update_result = array_shift( $save_response['nav_menu_item_updates'] );
$this->assertArrayHasKey( 'post_id', $update_result );
$this->assertArrayHasKey( 'previous_post_id', $update_result );
$this->assertArrayHasKey( 'error', $update_result );
$this->assertArrayHasKey( 'status', $update_result );
$this->assertEquals( $setting->post_id, $update_result['post_id'] );
$this->assertEquals( $new_item_id, $update_result['previous_post_id'] );
$this->assertNull( $update_result['error'] );
$this->assertEquals( 'inserted', $update_result['status'] );
}
/**
* Test protected update() method via the save() method, for deleted menu.
*
* @see WP_Customize_Nav_Menu_Item_Setting::update()
*/
function test_save_deleted() {
do_action( 'customize_register', $this->wp_customize );
$menu_id = wp_create_nav_menu( 'Primary' );
$post_id = $this->factory->post->create( array( 'post_title' => 'Hello World' ) );
$item_ids = array();
for ( $i = 0; $i < 5; $i += 1 ) {
$item_id = wp_update_nav_menu_item( $menu_id, 0, array(
'menu-item-type' => 'post_type',
'menu-item-object' => 'post',
'menu-item-object-id' => $post_id,
'menu-item-title' => "Item $i",
'menu-item-status' => 'publish',
'menu-item-position' => $i + 1,
) );
$item_ids[] = $item_id;
}
$delete_item_id = $item_ids[2];
$setting_id = "nav_menu_item[$delete_item_id]";
$setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, $setting_id );
$this->wp_customize->set_post_value( $setting_id, false );
$current_items = wp_get_nav_menu_items( $menu_id );
$this->assertContains( $delete_item_id, wp_list_pluck( $current_items, 'db_id' ) );
$setting->save();
$preview_items = wp_get_nav_menu_items( $menu_id );
$this->assertNotEquals( count( $current_items ), count( $preview_items ) );
$this->assertContains( $delete_item_id, wp_list_pluck( $current_items, 'db_id' ) );
// Verify the Ajax responses is being amended.
$save_response = apply_filters( 'customize_save_response', array() );
$this->assertArrayHasKey( 'nav_menu_item_updates', $save_response );
$update_result = array_shift( $save_response['nav_menu_item_updates'] );
$this->assertArrayHasKey( 'post_id', $update_result );
$this->assertArrayHasKey( 'previous_post_id', $update_result );
$this->assertArrayHasKey( 'error', $update_result );
$this->assertArrayHasKey( 'status', $update_result );
$this->assertEquals( $delete_item_id, $update_result['post_id'] );
$this->assertNull( $update_result['previous_post_id'] );
$this->assertNull( $update_result['error'] );
$this->assertEquals( 'deleted', $update_result['status'] );
}
}

View File

@ -0,0 +1,461 @@
<?php
/**
* Tests WP_Customize_Nav_Menu_Setting.
*
* @group customize
*/
class Test_WP_Customize_Nav_Menu_Setting extends WP_UnitTestCase {
/**
* Instance of WP_Customize_Manager which is reset for each test.
*
* @var WP_Customize_Manager
*/
public $wp_customize;
/**
* Set up a test case.
*
* @see WP_UnitTestCase::setup()
*/
function setUp() {
parent::setUp();
require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
wp_set_current_user( $this->factory->user->create( array( 'role' => 'administrator' ) ) );
global $wp_customize;
$this->wp_customize = new WP_Customize_Manager();
$wp_customize = $this->wp_customize;
}
/**
* Delete the $wp_customize global when cleaning up scope.
*/
function clean_up_global_scope() {
global $wp_customize;
$wp_customize = null;
parent::clean_up_global_scope();
}
/**
* Helper for getting the nav_menu_options option.
*
* @return array
*/
function get_nav_menu_items_option() {
return get_option( 'nav_menu_options', array( 'auto_add' => array() ) );
}
/**
* Test constants and statics.
*/
function test_constants() {
do_action( 'customize_register', $this->wp_customize );
$this->assertTrue( taxonomy_exists( WP_Customize_Nav_Menu_Setting::TAXONOMY ) );
}
/**
* Test constructor.
*
* @see WP_Customize_Nav_Menu_Setting::__construct()
*/
function test_construct() {
do_action( 'customize_register', $this->wp_customize );
$setting = new WP_Customize_Nav_Menu_Setting( $this->wp_customize, 'nav_menu[123]' );
$this->assertEquals( 'nav_menu', $setting->type );
$this->assertEquals( 'postMessage', $setting->transport );
$this->assertEquals( 123, $setting->term_id );
$this->assertNull( $setting->previous_term_id );
$this->assertNull( $setting->update_status );
$this->assertNull( $setting->update_error );
$this->assertInternalType( 'array', $setting->default );
foreach ( array( 'name', 'description', 'parent' ) as $key ) {
$this->assertArrayHasKey( $key, $setting->default );
}
$this->assertEquals( '', $setting->default['name'] );
$this->assertEquals( '', $setting->default['description'] );
$this->assertEquals( 0, $setting->default['parent'] );
$exception = null;
try {
$bad_setting = new WP_Customize_Nav_Menu_Setting( $this->wp_customize, 'foo_bar_baz' );
unset( $bad_setting );
} catch ( Exception $e ) {
$exception = $e;
}
$this->assertInstanceOf( 'Exception', $exception );
}
/**
* Test empty constructor.
*/
function test_construct_empty_menus() {
do_action( 'customize_register', $this->wp_customize );
$_wp_customize = $this->wp_customize;
unset($_wp_customize->nav_menus);
$exception = null;
try {
$bad_setting = new WP_Customize_Nav_Menu_Setting( $_wp_customize, 'nav_menu_item[123]' );
unset( $bad_setting );
} catch ( Exception $e ) {
$exception = $e;
}
$this->assertInstanceOf( 'Exception', $exception );
}
/**
* Test constructor for placeholder (draft) menu.
*
* @see WP_Customize_Nav_Menu_Setting::__construct()
*/
function test_construct_placeholder() {
do_action( 'customize_register', $this->wp_customize );
$default = array(
'name' => 'Lorem',
'description' => 'ipsum',
'parent' => 123,
);
$setting = new WP_Customize_Nav_Menu_Setting( $this->wp_customize, 'nav_menu[-5]', compact( 'default' ) );
$this->assertEquals( -5, $setting->term_id );
$this->assertEquals( $default, $setting->default );
}
/**
* Test value method.
*
* @see WP_Customize_Nav_Menu_Setting::value()
*/
function test_value() {
do_action( 'customize_register', $this->wp_customize );
$menu_name = 'Test 123';
$parent_menu_id = wp_create_nav_menu( "Parent $menu_name" );
$description = 'Hello my world.';
$menu_id = wp_update_nav_menu_object( 0, array(
'menu-name' => $menu_name,
'parent' => $parent_menu_id,
'description' => $description,
) );
$setting_id = "nav_menu[$menu_id]";
$setting = new WP_Customize_Nav_Menu_Setting( $this->wp_customize, $setting_id );
$value = $setting->value();
$this->assertInternalType( 'array', $value );
foreach ( array( 'name', 'description', 'parent' ) as $key ) {
$this->assertArrayHasKey( $key, $value );
}
$this->assertEquals( $menu_name, $value['name'] );
$this->assertEquals( $description, $value['description'] );
$this->assertEquals( $parent_menu_id, $value['parent'] );
$new_menu_name = 'Foo';
wp_update_nav_menu_object( $menu_id, array( 'menu-name' => $new_menu_name ) );
$updated_value = $setting->value();
$this->assertEquals( $new_menu_name, $updated_value['name'] );
}
/**
* Test preview method for updated menu.
*
* @see WP_Customize_Nav_Menu_Setting::preview()
*/
function test_preview_updated() {
do_action( 'customize_register', $this->wp_customize );
$menu_id = wp_update_nav_menu_object( 0, array(
'menu-name' => 'Name 1',
'description' => 'Description 1',
'parent' => 0,
) );
$setting_id = "nav_menu[$menu_id]";
$setting = new WP_Customize_Nav_Menu_Setting( $this->wp_customize, $setting_id );
$nav_menu_options = $this->get_nav_menu_items_option();
$this->assertNotContains( $menu_id, $nav_menu_options['auto_add'] );
$post_value = array(
'name' => 'Name 2',
'description' => 'Description 2',
'parent' => 1,
'auto_add' => true,
);
$this->wp_customize->set_post_value( $setting_id, $post_value );
$value = $setting->value();
$this->assertEquals( 'Name 1', $value['name'] );
$this->assertEquals( 'Description 1', $value['description'] );
$this->assertEquals( 0, $value['parent'] );
$term = (array) wp_get_nav_menu_object( $menu_id );
$this->assertEqualSets(
wp_array_slice_assoc( $value, array( 'name', 'description', 'parent' ) ),
wp_array_slice_assoc( $term, array( 'name', 'description', 'parent' ) )
);
$setting->preview();
$value = $setting->value();
$this->assertEquals( 'Name 2', $value['name'] );
$this->assertEquals( 'Description 2', $value['description'] );
$this->assertEquals( 1, $value['parent'] );
$term = (array) wp_get_nav_menu_object( $menu_id );
$this->assertEqualSets( $value, wp_array_slice_assoc( $term, array_keys( $value ) ) );
$menu_object = wp_get_nav_menu_object( $menu_id );
$this->assertEquals( (object) $term, $menu_object );
$this->assertEquals( $post_value['name'], $menu_object->name );
$nav_menu_options = get_option( 'nav_menu_options', array( 'auto_add' => array() ) );
$this->assertContains( $menu_id, $nav_menu_options['auto_add'] );
}
/**
* Test preview method for inserted menu.
*
* @see WP_Customize_Nav_Menu_Setting::preview()
*/
function test_preview_inserted() {
do_action( 'customize_register', $this->wp_customize );
$menu_id = -123;
$post_value = array(
'name' => 'New Menu Name 1',
'description' => 'New Menu Description 1',
'parent' => 0,
'auto_add' => false,
);
$setting_id = "nav_menu[$menu_id]";
$setting = new WP_Customize_Nav_Menu_Setting( $this->wp_customize, $setting_id );
$this->wp_customize->set_post_value( $setting->id, $post_value );
$setting->preview();
$value = $setting->value();
$this->assertEquals( $post_value, $value );
$term = (array) wp_get_nav_menu_object( $menu_id );
$this->assertNotEmpty( $term );
$this->assertNotInstanceOf( 'WP_Error', $term );
$this->assertEqualSets( $post_value, wp_array_slice_assoc( $term, array_keys( $value ) ) );
$this->assertEquals( $menu_id, $term['term_id'] );
$this->assertEquals( $menu_id, $term['term_taxonomy_id'] );
$menu_object = wp_get_nav_menu_object( $menu_id );
$this->assertEquals( (object) $term, $menu_object );
$this->assertEquals( $post_value['name'], $menu_object->name );
$nav_menu_options = $this->get_nav_menu_items_option();
$this->assertNotContains( $menu_id, $nav_menu_options['auto_add'] );
}
/**
* Test preview method for deleted menu.
*
* @see WP_Customize_Nav_Menu_Setting::preview()
*/
function test_preview_deleted() {
do_action( 'customize_register', $this->wp_customize );
$menu_id = wp_update_nav_menu_object( 0, array(
'menu-name' => 'Name 1',
'description' => 'Description 1',
'parent' => 0,
) );
$setting_id = "nav_menu[$menu_id]";
$setting = new WP_Customize_Nav_Menu_Setting( $this->wp_customize, $setting_id );
$nav_menu_options = $this->get_nav_menu_items_option();
$nav_menu_options['auto_add'][] = $menu_id;
update_option( 'nav_menu_options', $nav_menu_options );
$nav_menu_options = $this->get_nav_menu_items_option();
$this->assertContains( $menu_id, $nav_menu_options['auto_add'] );
$this->wp_customize->set_post_value( $setting_id, false );
$this->assertInternalType( 'array', $setting->value() );
$this->assertInternalType( 'object', wp_get_nav_menu_object( $menu_id ) );
$setting->preview();
$this->assertFalse( $setting->value() );
$this->assertFalse( wp_get_nav_menu_object( $menu_id ) );
$nav_menu_options = $this->get_nav_menu_items_option();
$this->assertNotContains( $menu_id, $nav_menu_options['auto_add'] );
}
/**
* Test sanitize method.
*
* @see WP_Customize_Nav_Menu_Setting::sanitize()
*/
function test_sanitize() {
do_action( 'customize_register', $this->wp_customize );
$setting = new WP_Customize_Nav_Menu_Setting( $this->wp_customize, 'nav_menu[123]' );
$this->assertNull( $setting->sanitize( 'not an array' ) );
$this->assertNull( $setting->sanitize( 123 ) );
$value = array(
'name' => ' Hello <b>world</b> ',
'description' => "New\nline",
'parent' => -12,
'auto_add' => true,
'extra' => 'ignored',
);
$sanitized = $setting->sanitize( $value );
$this->assertEquals( 'Hello &lt;b&gt;world&lt;/b&gt;', $sanitized['name'] );
$this->assertEquals( 'New line', $sanitized['description'] );
$this->assertEquals( 0, $sanitized['parent'] );
$this->assertEquals( true, $sanitized['auto_add'] );
$this->assertEqualSets( array( 'name', 'description', 'parent', 'auto_add' ), array_keys( $sanitized ) );
}
/**
* Test protected update() method via the save() method, for updated menu.
*
* @see WP_Customize_Nav_Menu_Setting::update()
*/
function test_save_updated() {
do_action( 'customize_register', $this->wp_customize );
$menu_id = wp_update_nav_menu_object( 0, array(
'menu-name' => 'Name 1',
'description' => 'Description 1',
'parent' => 0,
) );
$nav_menu_options = $this->get_nav_menu_items_option();
$nav_menu_options['auto_add'][] = $menu_id;
update_option( 'nav_menu_options', $nav_menu_options );
$setting_id = "nav_menu[$menu_id]";
$setting = new WP_Customize_Nav_Menu_Setting( $this->wp_customize, $setting_id );
$auto_add = false;
$new_value = array(
'name' => 'Name 2',
'description' => 'Description 2',
'parent' => 1,
'auto_add' => $auto_add,
);
$this->wp_customize->set_post_value( $setting_id, $new_value );
$setting->save();
$menu_object = wp_get_nav_menu_object( $menu_id );
foreach ( array( 'name', 'description', 'parent' ) as $key ) {
$this->assertEquals( $new_value[ $key ], $menu_object->$key );
}
$this->assertEqualSets(
wp_array_slice_assoc( $new_value, array( 'name', 'description', 'parent' ) ),
wp_array_slice_assoc( (array) $menu_object, array( 'name', 'description', 'parent' ) )
);
$this->assertEquals( $new_value, $setting->value() );
$save_response = apply_filters( 'customize_save_response', array() );
$this->assertArrayHasKey( 'nav_menu_updates', $save_response );
$update_result = array_shift( $save_response['nav_menu_updates'] );
$this->assertArrayHasKey( 'term_id', $update_result );
$this->assertArrayHasKey( 'previous_term_id', $update_result );
$this->assertArrayHasKey( 'error', $update_result );
$this->assertArrayHasKey( 'status', $update_result );
$this->assertEquals( $menu_id, $update_result['term_id'] );
$this->assertNull( $update_result['previous_term_id'] );
$this->assertNull( $update_result['error'] );
$this->assertEquals( 'updated', $update_result['status'] );
$nav_menu_options = $this->get_nav_menu_items_option();
$this->assertNotContains( $menu_id, $nav_menu_options['auto_add'] );
}
/**
* Test protected update() method via the save() method, for inserted menu.
*
* @see WP_Customize_Nav_Menu_Setting::update()
*/
function test_save_inserted() {
do_action( 'customize_register', $this->wp_customize );
$menu_id = -123;
$post_value = array(
'name' => 'New Menu Name 1',
'description' => 'New Menu Description 1',
'parent' => 0,
'auto_add' => true,
);
$setting_id = "nav_menu[$menu_id]";
$setting = new WP_Customize_Nav_Menu_Setting( $this->wp_customize, $setting_id );
$this->wp_customize->set_post_value( $setting->id, $post_value );
$this->assertNull( $setting->previous_term_id );
$this->assertLessThan( 0, $setting->term_id );
$setting->save();
$this->assertEquals( $menu_id, $setting->previous_term_id );
$this->assertGreaterThan( 0, $setting->term_id );
$nav_menu_options = $this->get_nav_menu_items_option();
$this->assertContains( $setting->term_id, $nav_menu_options['auto_add'] );
$menu = wp_get_nav_menu_object( $setting->term_id );
unset( $post_value['auto_add'] );
$this->assertEqualSets( $post_value, wp_array_slice_assoc( (array) $menu, array_keys( $post_value ) ) );
$save_response = apply_filters( 'customize_save_response', array() );
$this->assertArrayHasKey( 'nav_menu_updates', $save_response );
$update_result = array_shift( $save_response['nav_menu_updates'] );
$this->assertArrayHasKey( 'term_id', $update_result );
$this->assertArrayHasKey( 'previous_term_id', $update_result );
$this->assertArrayHasKey( 'error', $update_result );
$this->assertArrayHasKey( 'status', $update_result );
$this->assertEquals( $menu->term_id, $update_result['term_id'] );
$this->assertEquals( $menu_id, $update_result['previous_term_id'] );
$this->assertNull( $update_result['error'] );
$this->assertEquals( 'inserted', $update_result['status'] );
}
/**
* Test protected update() method via the save() method, for deleted menu.
*
* @see WP_Customize_Nav_Menu_Setting::update()
*/
function test_save_deleted() {
do_action( 'customize_register', $this->wp_customize );
$menu_name = 'Lorem Ipsum';
$menu_id = wp_create_nav_menu( $menu_name );
$setting_id = "nav_menu[$menu_id]";
$setting = new WP_Customize_Nav_Menu_Setting( $this->wp_customize, $setting_id );
$nav_menu_options = $this->get_nav_menu_items_option();
$nav_menu_options['auto_add'][] = $menu_id;
update_option( 'nav_menu_options', $nav_menu_options );
$menu = wp_get_nav_menu_object( $menu_id );
$this->assertEquals( $menu_name, $menu->name );
$this->wp_customize->set_post_value( $setting_id, false );
$setting->save();
$this->assertFalse( wp_get_nav_menu_object( $menu_id ) );
$save_response = apply_filters( 'customize_save_response', array() );
$this->assertArrayHasKey( 'nav_menu_updates', $save_response );
$update_result = array_shift( $save_response['nav_menu_updates'] );
$this->assertArrayHasKey( 'term_id', $update_result );
$this->assertArrayHasKey( 'previous_term_id', $update_result );
$this->assertArrayHasKey( 'error', $update_result );
$this->assertArrayHasKey( 'status', $update_result );
$this->assertEquals( $menu_id, $update_result['term_id'] );
$this->assertNull( $update_result['previous_term_id'] );
$this->assertNull( $update_result['error'] );
$this->assertEquals( 'deleted', $update_result['status'] );
$nav_menu_options = $this->get_nav_menu_items_option();
$this->assertNotContains( $menu_id, $nav_menu_options['auto_add'] );
}
}

View File

@ -0,0 +1,456 @@
<?php
/**
* Tests WP_Customize_Nav_Menus.
*
* @group customize
*/
class Test_WP_Customize_Nav_Menus extends WP_UnitTestCase {
/**
* Instance of WP_Customize_Manager which is reset for each test.
*
* @var WP_Customize_Manager
*/
public $wp_customize;
/**
* Set up a test case.
*
* @see WP_UnitTestCase::setup()
*/
function setUp() {
parent::setUp();
require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
wp_set_current_user( $this->factory->user->create( array( 'role' => 'administrator' ) ) );
global $wp_customize;
$this->wp_customize = new WP_Customize_Manager();
$wp_customize = $this->wp_customize;
}
/**
* Delete the $wp_customize global when cleaning up scope.
*/
function clean_up_global_scope() {
global $wp_customize;
$wp_customize = null;
parent::clean_up_global_scope();
}
/**
* Test constructor.
*
* @see WP_Customize_Nav_Menus::__construct()
*/
function test_construct() {
do_action( 'customize_register', $this->wp_customize );
$menus = new WP_Customize_Nav_Menus( $this->wp_customize );
$this->assertInstanceOf( 'WP_Customize_Manager', $menus->manager );
}
/**
* Test the test_load_available_items_ajax method.
*
* @see WP_Customize_Nav_Menus::load_available_items_ajax()
*/
function test_load_available_items_ajax() {
$this->markTestIncomplete( 'This test has not been implemented.' );
}
/**
* Test the search_available_items_ajax method.
*
* @see WP_Customize_Nav_Menus::search_available_items_ajax()
*/
function test_search_available_items_ajax() {
$this->markTestIncomplete( 'This test has not been implemented.' );
}
/**
* Test the search_available_items_query method.
*
* @see WP_Customize_Nav_Menus::search_available_items_query()
*/
function test_search_available_items_query() {
$menus = new WP_Customize_Nav_Menus( $this->wp_customize );
// Create posts
$post_ids = array();
$post_ids[] = $this->factory->post->create( array( 'post_title' => 'Search & Test' ) );
$post_ids[] = $this->factory->post->create( array( 'post_title' => 'Some Other Title' ) );
// Create terms
$term_ids = array();
$term_ids[] = $this->factory->category->create( array( 'name' => 'Dogs Are Cool' ) );
$term_ids[] = $this->factory->category->create( array( 'name' => 'Cats Drool' ) );
// Test empty results
$expected = array();
$results = $menus->search_available_items_query( array( 'pagenum' => 1, 's' => 'This Does NOT Exist' ) );
$this->assertEquals( $expected, $results );
// Test posts
foreach ( $post_ids as $post_id ) {
$expected = array(
'id' => 'post-' . $post_id,
'type' => 'post_type',
'type_label' => get_post_type_object( 'post' )->labels->singular_name,
'object' => 'post',
'object_id' => intval( $post_id ),
'title' => html_entity_decode( get_the_title( $post_id ) ),
);
wp_set_object_terms( $post_id, $term_ids, 'category' );
$search = $post_id === $post_ids[0] ? 'test & search' : 'other title';
$s = sanitize_text_field( wp_unslash( $search ) );
$results = $menus->search_available_items_query( array( 'pagenum' => 1, 's' => $s ) );
$this->assertEquals( $expected, $results[0] );
}
// Test terms
foreach ( $term_ids as $term_id ) {
$term = get_term_by( 'id', $term_id, 'category' );
$expected = array(
'id' => 'term-' . $term_id,
'type' => 'taxonomy',
'type_label' => get_taxonomy( 'category' )->labels->singular_name,
'object' => 'category',
'object_id' => intval( $term_id ),
'title' => $term->name,
);
$s = sanitize_text_field( wp_unslash( $term->name ) );
$results = $menus->search_available_items_query( array( 'pagenum' => 1, 's' => $s ) );
$this->assertEquals( $expected, $results[0] );
}
}
/**
* Test the enqueue method.
*
* @see WP_Customize_Nav_Menus::enqueue_scripts()
*/
function test_enqueue_scripts() {
do_action( 'customize_register', $this->wp_customize );
$menus = new WP_Customize_Nav_Menus( $this->wp_customize );
$menus->enqueue_scripts();
$this->assertTrue( wp_script_is( 'customize-nav-menus' ) );
}
/**
* Test the filter_dynamic_setting_args method.
*
* @see WP_Customize_Nav_Menus::filter_dynamic_setting_args()
*/
function test_filter_dynamic_setting_args() {
$menus = new WP_Customize_Nav_Menus( $this->wp_customize );
$expected = array( 'type' => 'nav_menu_item' );
$results = $menus->filter_dynamic_setting_args( $this->wp_customize, 'nav_menu_item[123]' );
$this->assertEquals( $expected, $results );
$expected = array( 'type' => 'nav_menu' );
$results = $menus->filter_dynamic_setting_args( $this->wp_customize, 'nav_menu[123]' );
$this->assertEquals( $expected, $results );
}
/**
* Test the filter_dynamic_setting_class method.
*
* @see WP_Customize_Nav_Menus::filter_dynamic_setting_class()
*/
function test_filter_dynamic_setting_class() {
do_action( 'customize_register', $this->wp_customize );
$menus = new WP_Customize_Nav_Menus( $this->wp_customize );
$expected = 'WP_Customize_Nav_Menu_Item_Setting';
$results = $menus->filter_dynamic_setting_class( 'WP_Customize_Setting', 'nav_menu_item[123]', array( 'type' => 'nav_menu_item' ) );
$this->assertEquals( $expected, $results );
$expected = 'WP_Customize_Nav_Menu_Setting';
$results = $menus->filter_dynamic_setting_class( 'WP_Customize_Setting', 'nav_menu[123]', array( 'type' => 'nav_menu' ) );
$this->assertEquals( $expected, $results );
}
/**
* Test the customize_register method.
*
* @see WP_Customize_Nav_Menus::customize_register()
*/
function test_customize_register() {
do_action( 'customize_register', $this->wp_customize );
$menu_id = wp_create_nav_menu( 'Primary' );
$post_id = $this->factory->post->create( array( 'post_title' => 'Hello World' ) );
$item_id = wp_update_nav_menu_item( $menu_id, 0, array(
'menu-item-type' => 'post_type',
'menu-item-object' => 'post',
'menu-item-object-id' => $post_id,
'menu-item-title' => 'Hello World',
'menu-item-status' => 'publish',
) );
$setting = new WP_Customize_Nav_Menu_Item_Setting( $this->wp_customize, "nav_menu_item[$item_id]" );
do_action( 'customize_register', $this->wp_customize );
$this->assertEquals( 'Primary', $this->wp_customize->get_section( "nav_menu[$menu_id]" )->title );
$this->assertEquals( 'Hello World', $this->wp_customize->get_control( "nav_menu_item[$item_id]" )->label );
}
/**
* Test the intval_base10 method.
*
* @see WP_Customize_Nav_Menus::intval_base10()
*/
function test_intval_base10() {
$menus = new WP_Customize_Nav_Menus( $this->wp_customize );
$this->assertEquals( 2, $menus->intval_base10( 2 ) );
$this->assertEquals( 4, $menus->intval_base10( 4.1 ) );
$this->assertEquals( 4, $menus->intval_base10( '4' ) );
$this->assertEquals( 4, $menus->intval_base10( '04' ) );
$this->assertEquals( 42, $menus->intval_base10( +42 ) );
$this->assertEquals( -42, $menus->intval_base10( -42 ) );
$this->assertEquals( 26, $menus->intval_base10( 0x1A ) );
$this->assertEquals( 0, $menus->intval_base10( array() ) );
}
/**
* Test the available_item_types method.
*
* @see WP_Customize_Nav_Menus::available_item_types()
*/
function test_available_item_types() {
$menus = new WP_Customize_Nav_Menus( $this->wp_customize );
$expected = array(
'postTypes' => array(
'post' => array( 'label' => 'Post' ),
'page' => array( 'label' => 'Page' ),
),
'taxonomies' => array(
'category' => array( 'label' => 'Category' ),
'post_tag' => array( 'label' => 'Tag' ),
),
);
if ( current_theme_supports( 'post-formats' ) ) {
$expected['taxonomies']['post_format'] = array( 'label' => 'Format' );
}
$this->assertEquals( $expected, $menus->available_item_types() );
register_taxonomy( 'wptests_tax', array( 'post' ), array( 'labels' => array( 'name' => 'Foo' ) ) );
$expected = array(
'postTypes' => array(
'post' => array( 'label' => 'Post' ),
'page' => array( 'label' => 'Page' ),
),
'taxonomies' => array(
'category' => array( 'label' => 'Category' ),
'post_tag' => array( 'label' => 'Tag' ),
'wptests_tax' => array( 'label' => 'Foo' ),
),
);
if ( current_theme_supports( 'post-formats' ) ) {
$wptests_tax = array_pop( $expected['taxonomies'] );
$expected['taxonomies']['post_format'] = array( 'label' => 'Format' );
$expected['taxonomies']['wptests_tax'] = $wptests_tax;
}
$this->assertEquals( $expected, $menus->available_item_types() );
}
/**
* Test the print_templates method.
*
* @see WP_Customize_Nav_Menus::print_templates()
*/
function test_print_templates() {
do_action( 'customize_register', $this->wp_customize );
$menus = new WP_Customize_Nav_Menus( $this->wp_customize );
ob_start();
$menus->print_templates();
$template = ob_get_clean();
$expected = sprintf(
'<button type="button" class="menus-move-up">%1$s</button><button type="button" class="menus-move-down">%2$s</button><button type="button" class="menus-move-left">%3$s</button><button type="button" class="menus-move-right">%4$s</button>',
esc_html( 'Move up' ),
esc_html( 'Move down' ),
esc_html( 'Move one level up' ),
esc_html( 'Move one level down' )
);
$this->assertContains( $expected, $template );
}
/**
* Test the available_items_template method.
*
* @see WP_Customize_Nav_Menus::available_items_template()
*/
function test_available_items_template() {
do_action( 'customize_register', $this->wp_customize );
$menus = new WP_Customize_Nav_Menus( $this->wp_customize );
ob_start();
$menus->available_items_template();
$template = ob_get_clean();
$expected = sprintf( 'Customizing &#9656; %s', esc_html( $this->wp_customize->get_panel( 'nav_menus' )->title ) );
$this->assertContains( $expected, $template );
$post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'object' );
if ( $post_types ) {
foreach ( $post_types as $type ) {
$this->assertContains( 'available-menu-items-' . esc_attr( $type->name ), $template );
$this->assertContains( '<h4 class="accordion-section-title">' . esc_html( $type->label ), $template );
$this->assertContains( 'data-type="' . esc_attr( $type->name ) . '" data-obj_type="post_type"', $template );
}
}
$taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'object' );
if ( $taxonomies ) {
foreach ( $taxonomies as $tax ) {
$this->assertContains( 'available-menu-items-' . esc_attr( $tax->name ), $template );
$this->assertContains( '<h4 class="accordion-section-title">' . esc_html( $tax->label ), $template );
$this->assertContains( 'data-type="' . esc_attr( $tax->name ) . '" data-obj_type="taxonomy"', $template );
}
}
}
/**
* Test the customize_preview_init method.
*
* @see WP_Customize_Nav_Menus::customize_preview_init()
*/
function test_customize_preview_init() {
do_action( 'customize_register', $this->wp_customize );
$menus = new WP_Customize_Nav_Menus( $this->wp_customize );
$menus->customize_preview_init();
$this->assertEquals( 10, has_action( 'template_redirect', array( $menus, 'render_menu' ) ) );
$this->assertEquals( 10, has_action( 'wp_enqueue_scripts', array( $menus, 'customize_preview_enqueue_deps' ) ) );
if ( ! isset( $_REQUEST[ WP_Customize_Nav_Menus::RENDER_QUERY_VAR ] ) ) {
$this->assertEquals( 1000, has_filter( 'wp_nav_menu_args', array( $menus, 'filter_wp_nav_menu_args' ) ) );
$this->assertEquals( 10, has_filter( 'wp_nav_menu', array( $menus, 'filter_wp_nav_menu' ) ) );
}
}
/**
* Test the filter_wp_nav_menu_args method.
*
* @see WP_Customize_Nav_Menus::filter_wp_nav_menu_args()
*/
function test_filter_wp_nav_menu_args() {
do_action( 'customize_register', $this->wp_customize );
$menus = new WP_Customize_Nav_Menus( $this->wp_customize );
$results = $menus->filter_wp_nav_menu_args( array(
'echo' => true,
'fallback_cb' => 'wp_page_menu',
'walker' => '',
) );
$this->assertEquals( 1, $results['can_partial_refresh'] );
$expected = array(
'echo',
'args_hash',
'can_partial_refresh',
'instance_number',
);
$results = $menus->filter_wp_nav_menu_args( array(
'echo' => false,
'fallback_cb' => 'wp_page_menu',
'walker' => new Walker_Nav_Menu(),
) );
$this->assertEqualSets( $expected, array_keys( $results ) );
$this->assertEquals( 0, $results['can_partial_refresh'] );
}
/**
* Test the filter_wp_nav_menu method.
*
* @see WP_Customize_Nav_Menus::filter_wp_nav_menu()
*/
function test_filter_wp_nav_menu() {
do_action( 'customize_register', $this->wp_customize );
$menus = new WP_Customize_Nav_Menus( $this->wp_customize );
$args = $menus->filter_wp_nav_menu_args( array(
'echo' => true,
'fallback_cb' => 'wp_page_menu',
'walker' => '',
) );
ob_start();
wp_nav_menu( $args );
$nav_menu_content = ob_get_clean();
$object_args = json_decode( json_encode( $args ), false );
$result = $menus->filter_wp_nav_menu( $nav_menu_content, $object_args );
$expected = sprintf(
'<div id="partial-refresh-menu-container-%1$d" class="partial-refresh-menu-container" data-instance-number="%1$d">%2$s</div>',
$args['instance_number'],
$nav_menu_content
);
$this->assertEquals( $expected, $result );
}
/**
* Test the customize_preview_enqueue_deps method.
*
* @see WP_Customize_Nav_Menus::customize_preview_enqueue_deps()
*/
function test_customize_preview_enqueue_deps() {
do_action( 'customize_register', $this->wp_customize );
$menus = new WP_Customize_Nav_Menus( $this->wp_customize );
$menus->customize_preview_enqueue_deps();
$this->assertTrue( wp_script_is( 'customize-preview-nav-menus' ) );
$this->assertEquals( 10, has_action( 'wp_print_footer_scripts', array( $menus, 'export_preview_data' ) ) );
}
/**
* Test the export_preview_data method.
*
* @see WP_Customize_Nav_Menus::export_preview_data()
*/
function test_export_preview_data() {
do_action( 'customize_register', $this->wp_customize );
$menus = new WP_Customize_Nav_Menus( $this->wp_customize );
$request_uri = $_SERVER['REQUEST_URI'];
ob_start();
$_SERVER['REQUEST_URI'] = '/wp-admin';
$menus->export_preview_data();
$data = ob_get_clean();
$_SERVER['REQUEST_URI'] = $request_uri;
$this->assertContains( '_wpCustomizePreviewNavMenusExports', $data );
$this->assertContains( 'renderQueryVar', $data );
$this->assertContains( 'renderNonceValue', $data );
$this->assertContains( 'renderNoncePostKey', $data );
$this->assertContains( 'requestUri', $data );
$this->assertContains( 'theme', $data );
$this->assertContains( 'previewCustomizeNonce', $data );
$this->assertContains( 'navMenuInstanceArgs', $data );
$this->assertContains( 'requestUri', $data );
}
/**
* Test the render_menu method.
*
* @see WP_Customize_Nav_Menus::render_menu()
*/
function test_render_menu() {
$this->markTestIncomplete( 'This test has not been implemented.' );
}
}