Ticket #37661: 37661.3.diff
File 37661.3.diff, 80.8 KB (added by , 8 years ago) |
---|
-
src/wp-admin/css/customize-controls.css
467 467 transition: left 0s; 468 468 } 469 469 470 .wp-full-overlay.section-open #customize-controls .wp-full-overlay-sidebar-content { 470 .wp-full-overlay.section-open #customize-controls .wp-full-overlay-sidebar-content, 471 .wp-full-overlay.in-themes-panel #customize-controls .wp-full-overlay-sidebar-content, 472 .wp-full-overlay.in-themes-panel #customize-controls .wp-full-overlay-header, 473 .wp-full-overlay.in-themes-panel #customize-controls .wp-full-overlay-footer, 474 .wp-full-overlay.in-themes-panel #customize-preview { 471 475 visibility: hidden; 472 476 overflow-y: hidden; 473 477 } 474 478 475 .wp-full-overlay.section-open .wp-full-overlay-sidebar-content .accordion-section.open { 479 .wp-full-overlay.section-open .wp-full-overlay-sidebar-content .accordion-section.open, 480 .wp-full-overlay.in-themes-panel .control-panel-themes { 476 481 visibility: visible; 477 482 } 478 483 … … 987 992 animation: customize-reload .75s; 988 993 } 989 994 990 #customize-theme-controls .control-section-themes .accordion-section-title:hover, /* Not a focusable element. */ 991 #customize-theme-controls .control-section-themes .accordion-section-title { 995 #customize-theme-controls .control-panel-themes { 996 border-bottom: none; 997 } 998 999 #customize-theme-controls .control-panel-themes > .accordion-section-title:hover, /* Not a focusable element. */ 1000 #customize-theme-controls .control-panel-themes > .accordion-section-title { 992 1001 cursor: default; 993 1002 background: #fff; 994 1003 color: #555; … … 995 1004 border-top: 1px solid #ddd; 996 1005 border-bottom: 1px solid #ddd; 997 1006 border-left: none; 998 margin-top: 0; 1007 border-right: none; 1008 margin: 0 0 15px 0; 1009 padding-right: 100px; /* Space for the button */ 999 1010 } 1000 1011 1001 1012 #customize-theme-controls .control-section-themes .customize-themes-panel .accordion-section-title:first-child:hover, /* Not a focusable element. */ … … 1003 1014 border-top: 0; 1004 1015 } 1005 1016 1006 #customize-theme-controls .control-section-themes > .accordion-section-title:hover, /* Not a focusable element. */ 1007 #customize-theme-controls .control-section-themes > .accordion-section-title { 1008 margin: 15px 0; 1009 } 1010 1011 .customize-themes-panel .accordion-section-title { 1012 margin: 15px -8px; 1013 } 1014 1015 .control-section-themes .accordion-section-title { 1016 padding-right: 100px; /* Space for the button */ 1017 } 1018 1019 .control-section-themes .accordion-section-title span.customize-action, 1017 .control-panel-themes .accordion-section-title span.customize-action, 1020 1018 #customize-controls .customize-section-title span.customize-action { 1021 1019 font-size: 13px; 1022 1020 display: block; … … 1023 1021 font-weight: 400; 1024 1022 } 1025 1023 1026 .control-section-themes .accordion-section-title .change-theme, 1027 .control-section-themes .accordion-section-title .customize-theme { 1024 .control-panel-themes .accordion-section-title .change-theme { 1028 1025 position: absolute; 1029 1026 right: 10px; 1030 1027 top: 50%; … … 1032 1029 font-weight: 400; 1033 1030 } 1034 1031 1035 .control-section-themes .accordion-section-title:before{1032 #customize-theme-controls .control-panel-themes > .accordion-section-title:after { 1036 1033 display: none; 1037 1034 } 1038 1035 1039 .c ustomize-themes-panel{1036 .control-panel-themes .control-panel-content { 1040 1037 display: none; 1041 padding: 0 8px; 1042 background: #f1f1f1; 1043 -webkit-box-sizing: border-box; 1044 -moz-box-sizing: border-box; 1045 box-sizing: border-box; 1038 position: fixed; 1039 width: 100%; 1040 height: 100%; 1041 top: 0; 1042 left: -100%; 1043 background: #eee; 1044 z-index: 10; 1045 transition: .2s left ease-in-out; 1046 margin: 0; 1047 padding: 0; 1048 overflow-y: auto; 1046 1049 } 1047 1050 1048 . customize-themes-panel .accordion-section-title:first-child{1049 margin-top: 0;1051 .in-themes-panel .control-panel-themes .control-panel-content { 1052 left: 0; 1050 1053 } 1051 1054 1052 #customize-controls .customize-themes-panel .accordion-section-title:nth-child(2) { 1053 font-size: 14px; 1054 font-weight: 600; 1055 #customize-controls, 1056 #customize-footer-actions, 1057 #customize-footer-actions .collapse-sidebar { 1058 left: 0; 1059 transition: .2s left ease-in-out; 1055 1060 } 1056 1061 1062 .in-themes-panel #customize-controls, 1063 .in-themes-panel #customize-preview, 1064 .in-themes-panel #customize-footer-actions, 1065 .in-themes-panel #customize-footer-actions .collapse-sidebar { 1066 left: 100%; 1067 } 1068 1057 1069 .customize-themes-panel > h2 { 1058 1070 padding: 15px 8px 0 8px; 1059 1071 } 1060 1072 1061 .control-section.open .customize-themes-panel { 1062 display: block; 1073 .control-panel-themes .panel-meta .current-theme { 1074 position: absolute; 1075 right: 0; 1076 top: 0; 1077 background: #fff; 1078 border-left: 1px solid #ddd; 1079 padding: 15px 125px 15px 25px; 1063 1080 } 1064 1081 1065 #customize-theme-controls .customize-themes-panel .accordion-section-content { 1082 .control-panel-themes .panel-meta .customize-theme { 1083 position: absolute; 1084 bottom: 15px; 1085 right: 25px; 1086 } 1087 1088 .control-panel-themes .panel-meta h2 { 1089 font-size: 32px; 1090 font-weight: 200; 1091 margin: 0 25px; 1092 padding: 15px 0; 1093 line-height: 50px; 1094 float: left; 1095 } 1096 1097 .control-panel-themes .panel-meta { 1098 min-height: 80px; 1099 overflow: hidden; 1100 } 1101 1102 .control-panel-themes .panel-meta .page-title-action { 1103 margin-top: 28px; 1104 } 1105 1106 /* Mobile header */ 1107 @media screen and (max-width:700px) { 1108 .control-panel-themes .panel-meta .current-theme { 1109 width: calc(100% - 150px); 1110 position: relative; 1111 border-bottom: 1px solid #ddd; 1112 } 1113 } 1114 1115 .control-panel-themes .customize-themes-notifications .notice { 1116 margin: 0 25px 15px 25px; 1117 } 1118 1119 #customize-theme-section-navigation { 1120 margin: 0 25px 15px 25px; 1121 width: calc(100% - 50px); 1122 } 1123 1124 #customize-theme-section-navigation .filter-links .customize-themes-section-title:only-child { 1125 display: none; /* Don't show the heading if there is only one section (presumably installed themes). */ 1126 } 1127 1128 .control-panel-themes .filter-count { 1129 padding: 12px 0; 1130 } 1131 1132 #customize-theme-section-navigation .themes-filter-container { 1133 display: inline-block; 1134 } 1135 1136 .control-panel-themes .wp-filter .customize-help-toggle { 1137 float: right; 1138 padding: 10px; 1139 height: 40px; 1140 width: 40px; 1141 margin: 5px; 1066 1142 background: transparent; 1067 display: block; 1143 border: none; 1144 box-shadow: none; 1145 cursor: pointer; 1146 color: #555; 1068 1147 } 1069 1148 1070 .c ustomize-control.customize-control-theme{1071 margin-bottom: 8px;1149 .control-panel-themes .wp-filter .customize-help-toggle:hover { 1150 color: #0073aa; 1072 1151 } 1073 1152 1153 .control-panel-themes .wp-filter .customize-help-toggle:focus, 1154 .control-panel-themes .wp-filter .customize-help-toggle.toggled { 1155 color: #0073aa; 1156 border-radius: 100%; 1157 box-shadow: 0 0 0 1px #5b9dd9, 1158 0 0 2px 1px rgba(30, 140, 190, .8); 1159 outline: 0; 1160 } 1161 1162 .control-panel-themes .customize-panel-description { 1163 position: absolute; 1164 right: -1px; 1165 top: 54px; 1166 z-index: 5; 1167 background: #fff; 1168 font-size: 14px; 1169 width: 300px; 1170 max-width: calc(100% - 35px); 1171 border: 1px solid #ddd; 1172 border-top: none; 1173 padding: 5px 25px 15px 10px; 1174 text-align: right; 1175 display: none; 1176 } 1177 1178 .control-panel-themes .theme-section { 1179 width: calc(100% - 50px); 1180 padding: 0 25px 25px 25px; 1181 margin: 0; 1182 display: none !important; /* There is unknown JS that perpetually tries to show all theme sections when more items are added. */ 1183 overflow: hidden; 1184 } 1185 1186 .control-panel-themes .theme-section.current-section { 1187 display: list-item !important; /* There is unknown JS that perpetually tries to show all theme sections when more items are added. */ 1188 } 1189 1190 #customize-theme-section-navigation .customize-themes-section-title { 1191 display: inline-block; 1192 margin: 0 10px; 1193 padding: 15px 0; 1194 background: #fff; 1195 border: 0; 1196 border-bottom: 4px solid #fff; 1197 color: #555; 1198 cursor: pointer; 1199 } 1200 1201 .filter-links .themes-section-feature_filter_themes:before { 1202 content: "\f111"; 1203 font: 16px/1 dashicons; 1204 position: relative; 1205 top: 2px; 1206 margin-right: 4px; 1207 } 1208 1209 #customize-theme-section-navigation .customize-themes-section-title.themes-section-search_themes { 1210 border-bottom: none; 1211 padding: 10px 0; 1212 max-width: calc(100% - 70px); 1213 } 1214 1215 #customize-theme-section-navigation .customize-themes-section-title.selected { 1216 border-bottom: 4px solid #555; 1217 } 1218 1219 #customize-theme-section-navigation .customize-themes-section-title:hover, 1220 #customize-theme-section-navigation .customize-themes-section-title:focus { 1221 color: #0073aa; 1222 border-bottom: 4px solid #0073aa; 1223 box-shadow: none; 1224 outline: none; 1225 } 1226 1227 #customize-theme-section-navigation .customize-themes-section-title.themes-section-search_themes.selected, 1228 #customize-theme-section-navigation .customize-themes-section-title.themes-section-search_themes:hover { 1229 border-bottom: none; 1230 } 1231 1074 1232 #customize-theme-controls .themes.accordion-section-content { 1075 1233 position: relative; 1076 1234 left: 0; … … 1078 1236 width: 100%; 1079 1237 } 1080 1238 1081 .wp-customizer .theme-browser .themes { 1082 padding-bottom: 8px; 1239 .theme-section.loading .spinner { 1240 display: block; 1241 visibility: visible; 1242 position: relative; 1243 clear: both; 1244 width: 20px; 1245 height: 20px; 1246 left: calc(50% - 10px); 1247 float: none; 1248 margin-top: 50px; 1083 1249 } 1084 1250 1085 .wp-customizer .theme-browser .theme { 1251 .customize-themes-section .filter-drawer { 1252 border-top: none; 1253 display: block; 1254 background: transparent; 1255 padding-top: 5px; 1256 } 1257 1258 .customize-themes-section .clear-filters { 1259 margin-left: 8px; 1260 display: none; 1261 } 1262 1263 #accordion-section-feature_filter_themes .theme-browser { 1264 display: none; /* Shown with JS once filters are applied */ 1265 } 1266 1267 .customize-themes-section .no-themes { 1268 display: none; 1269 } 1270 1271 #accordion-section-installed_themes .theme .notice-success { 1272 display: none; 1273 } 1274 1275 .control-panel-themes .theme-browser .theme .theme-actions .button-primary { 1276 margin: 0 0 0 8px; 1277 } 1278 1279 .customize-control-theme .theme { 1280 width: 100%; 1086 1281 margin: 0; 1087 width: 100%;1088 1282 } 1089 1283 1284 .customize-control.customize-control-theme { /* override most properties on .customize-control */ 1285 box-sizing: border-box; 1286 width: 18.4%; 1287 margin: 0 2% 2% 0; 1288 padding: 0; 1289 clear: none; 1290 } 1291 1292 /* 5 columns above 2100px */ 1293 @media screen and (min-width: 2101px) { 1294 .customize-control.customize-control-theme:nth-child(5n) { 1295 margin-right: 0; 1296 } 1297 } 1298 1299 /* 4 columns up to 2100px */ 1300 @media screen and (min-width: 1601px) and (max-width: 2100px) { 1301 .customize-control.customize-control-theme { 1302 width: 23.5%; 1303 } 1304 1305 .customize-control.customize-control-theme:nth-child(4n) { 1306 margin-right: 0; 1307 } 1308 } 1309 1310 /* 3 columns up to 1600px */ 1311 @media screen and (min-width: 1101px) and (max-width: 1600px) { 1312 .customize-control.customize-control-theme { 1313 width: 32%; 1314 } 1315 1316 .customize-control.customize-control-theme:nth-child(3n) { 1317 margin-right: 0; 1318 } 1319 } 1320 1321 /* 2 columns up to 1100px */ 1322 @media screen and (min-width: 501px) and (max-width: 1100px) { 1323 .customize-control.customize-control-theme { 1324 width: 49%; 1325 } 1326 1327 .customize-control.customize-control-theme:nth-child(even) { 1328 margin-right: 0; 1329 } 1330 } 1331 1332 /* 1 column up to 500 px */ 1333 @media screen and (max-width: 500px) { 1334 .customize-control.customize-control-theme { 1335 width: 100%; 1336 margin: 0 0 3% 0; 1337 } 1338 } 1339 1340 1341 .wp-customizer .theme-browser .themes { 1342 padding-bottom: 8px; 1343 } 1344 1090 1345 .wp-customizer .theme-browser .theme .theme-actions { 1091 1346 -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; 1092 1347 opacity: 1; … … 1107 1362 width: 100%; 1108 1363 } 1109 1364 1110 #accordion- section-themes .accordion-section-title:after {1365 #accordion-panel-themes .accordion-section-title:after { 1111 1366 display: none; 1112 1367 } 1113 1368 1114 #customize-theme-controls .control- section-themes.current-panel > h3.accordion-section-title {1369 #customize-theme-controls .control-panel-themes.current-panel > h3.accordion-section-title { 1115 1370 left: 0; 1116 1371 } 1117 1372 1118 .customize-themes-panel.control-panel-content {1119 position: absolute;1120 left: -100%;1121 top: 0;1122 width: 100%;1123 border-top: 1px solid #ddd;1124 }1125 1373 1126 .in-themes-panel #customize-info,1127 .in-themes-panel #customize-theme-controls > ul > .accordion-section {1128 left: 100%;1129 }1130 1131 1374 /* Details View */ 1132 1375 .wp-customizer .theme-overlay { 1133 1376 display: none; … … 1148 1391 z-index: 110; 1149 1392 } 1150 1393 1394 .wp-customizer .theme-overlay .star-rating { 1395 float: left; 1396 margin-right: 8px; 1397 } 1398 1399 .wp-customizer .theme-rating .num-ratings { 1400 line-height: 20px; 1401 } 1402 1151 1403 .wp-customizer .theme-overlay .theme-wrap { 1152 1404 left: 90px; 1153 1405 right: 90px; … … 1158 1410 } 1159 1411 1160 1412 .wp-customizer .theme-overlay .theme-actions { 1161 text-align: right; /* Because there's only one action, match the pattern of media modals and right-align the action. */ 1413 text-align: right; /* Because there're only one or two actions, match the UI pattern of media modals and right-align the action. */ 1414 padding: 10px 15px; 1162 1415 } 1163 1416 1417 .wp-customizer .theme-overlay .theme-actions .theme-install.preview { 1418 margin-left: 8px; 1419 } 1420 1421 .control-panel-themes .theme-actions .delete-theme { 1422 left: 15px; 1423 right: auto; 1424 } 1425 1164 1426 .modal-open .in-themes-panel #customize-controls .wp-full-overlay-sidebar-content { 1165 1427 overflow: visible; /* Prevent the top-level Customizer controls from becoming visible when elements on the right of the details modal are focused. */ 1166 1428 } -
src/wp-admin/includes/theme.php
606 606 * @since 4.2.0 607 607 */ 608 608 function customize_themes_print_templates() { 609 $preview_url = esc_url( add_query_arg( 'theme', '__THEME__' ) ); // Token because esc_url() strips curly braces. 609 $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); 610 $active_url = esc_url( remove_query_arg( 'theme', $current_url ) ); 611 $preview_url = esc_url( add_query_arg( 'theme', '__THEME__', $current_url ) ); // Token because esc_url() strips curly braces. 610 612 $preview_url = str_replace( '__THEME__', '{{ data.id }}', $preview_url ); 611 613 ?> 612 614 <script type="text/html" id="tmpl-customize-themes-details-view"> … … 619 621 </div> 620 622 <div class="theme-about wp-clearfix"> 621 623 <div class="theme-screenshots"> 622 <# if ( data.screenshot [0] ) { #>624 <# if ( data.screenshot && data.screenshot[0] ) { #> 623 625 <div class="screenshot"><img src="{{ data.screenshot[0] }}" alt="" /></div> 624 626 <# } else { #> 625 627 <div class="screenshot blank"></div> … … 632 634 <# } #> 633 635 <h2 class="theme-name">{{{ data.name }}}<span class="theme-version"><?php printf( __( 'Version: %s' ), '{{ data.version }}' ); ?></span></h2> 634 636 <h3 class="theme-author"><?php printf( __( 'By %s' ), '{{{ data.authorAndUri }}}' ); ?></h3> 637 638 <# if ( data.stars && 0 != data.num_ratings ) { #> 639 <div class="theme-rating"> 640 {{{ data.stars }}} 641 <span class="num-ratings"><?php echo sprintf( __( '(%s ratings)' ), '{{ data.num_ratings }}' ); ?></span> 642 </div> 643 <# } #> 644 645 <# if ( data.hasUpdate ) { #> 646 <div class="notice notice-warning notice-alt notice-large" data-slug="{{ data.id }}"> 647 <h3 class="notice-title"><?php _e( 'Update Available' ); ?></h3> 648 {{{ data.update }}} 649 </div> 650 <# } #> 651 635 652 <p class="theme-description">{{{ data.description }}}</p> 636 653 637 654 <# if ( data.parent ) { #> 638 655 <p class="parent-theme"><?php printf( __( 'This is a child theme of %s.' ), '<strong>{{{ data.parent }}}</strong>' ); ?></p> 639 656 <# } #> 640 641 657 <# if ( data.tags ) { #> 642 <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{ data.tags}}</p>658 <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{{ data.tags }}}</p> 643 659 <# } #> 644 660 </div> 645 661 </div> 646 662 647 <# if ( ! data.active ) { #> 648 <div class="theme-actions"> 649 <div class="inactive-theme"> 650 <?php 651 /* translators: %s: Theme name */ 652 $aria_label = sprintf( __( 'Preview %s' ), '{{ data.name }}' ); 653 ?> 654 <a href="<?php echo $preview_url; ?>" target="_top" class="button button-primary" aria-label="<?php echo esc_attr( $aria_label ); ?>"><?php _e( 'Live Preview' ); ?></a> 655 </div> 656 </div> 657 <# } #> 663 <div class="theme-actions"> 664 <# if ( data.active ) { #> 665 <?php 666 /* translators: %s: Theme name */ 667 $aria_label = sprintf( __( 'Customize %s' ), '{{ data.name }}' ); 668 ?> 669 <button type="button" class="button button-primary customize-theme" aria-label="<?php echo esc_attr( $aria_label ); ?>"><?php _e( 'Customize' ); ?></a> 670 <# } else if ( 'installed' === data.type ) { #> 671 <?php if ( current_user_can( 'delete_themes' ) ) { ?> 672 <# if ( data.actions && data.actions['delete'] ) { #> 673 <a href="{{{ data.actions['delete'] }}}" data-slug="{{ data.id }}" class="button button-secondary delete-theme"><?php _e( 'Delete' ); ?></a> 674 <# } #> 675 <?php } ?> 676 <button type="button" class="button button-primary preview-theme" data-preview-url="<?php echo esc_attr( $preview_url ); ?>" aria-label="<?php echo esc_attr( $aria_label ); ?>"><?php _e( 'Live Preview' ); ?></span> 677 <# } else { #> 678 <button type="button" class="button theme-install" data-slug="{{ data.id }}"><?php _e( 'Install' ); ?></button> 679 <button type="button" class="button button-primary theme-install preview" data-slug="{{ data.id }}" data-previewurl="<?php echo esc_attr( $preview_url ); ?>"><?php _e( 'Install & Preview' ); ?></button> 680 <# } #> 681 </div> 658 682 </div> 659 683 </script> 660 684 <?php -
src/wp-admin/js/customize-controls.js
2 2 (function( exports, $ ){ 3 3 var Container, focus, api = wp.customize; 4 4 5 window.pagenow = 'customize'; // Needed for updates.js to function properly. 6 5 7 /** 6 8 * A Customizer Setting. 7 9 * … … 777 779 /** 778 780 * wp.customize.ThemesSection 779 781 * 780 * Custom section for themes that functions similarly to a backwards panel,781 * and alsohandles the theme-details view rendering and navigation.782 * Custom section for themes that loads themes by category, and also 783 * handles the theme-details view rendering and navigation. 782 784 * 783 785 * @constructor 784 786 * @augments wp.customize.Section … … 790 792 template: '', 791 793 screenshotQueue: null, 792 794 $window: $( window ), 795 loaded: 0, 796 loading: false, 797 fullyLoaded: false, 798 term: '', 793 799 794 800 /** 795 801 * @since 4.2.0 796 802 */ 797 initialize: function () {798 this.$customizeSidebar = $( '.wp-full-overlay-sidebar-content:first' );799 return api.Section.prototype.initialize.apply( this, arguments );800 },801 802 /**803 * @since 4.2.0804 */805 803 ready: function () { 806 804 var section = this; 807 805 section.overlay = section.container.find( '.theme-overlay' ); … … 829 827 } 830 828 }); 831 829 832 _.bindAll( this, 'renderScreenshots' );830 _.bindAll( this, 'renderScreenshots', 'loadMore', 'checkTerm', 'filtersChecked', 'clearFilters' ); 833 831 }, 834 832 835 833 /** … … 836 834 * Override Section.isContextuallyActive method. 837 835 * 838 836 * Ignore the active states' of the contained theme controls, and just 839 * use the section's own active state instead. This ensures empty search840 * results for theme s to causethe section to become inactive.837 * use the section's own active state instead. This prevents empty search 838 * results for theme sections from causing the section to become inactive. 841 839 * 842 840 * @since 4.2.0 843 841 * … … 853 851 attachEvents: function () { 854 852 var section = this; 855 853 856 // Expand/Collapse section/panel. 857 section.container.find( '.change-theme, .customize-theme' ).on( 'click keydown', function( event ) { 858 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 859 return; 860 } 861 event.preventDefault(); // Keep this AFTER the key filter above 862 863 if ( section.expanded() ) { 864 section.collapse(); 865 } else { 854 // Expand section/panel. Only collapse when opening another section. 855 $( '#customize-theme-section-navigation' ).on( 'click', '.themes-section-' + section.id, function( event ) { 856 if ( ! section.expanded() && 'search_themes' !== section.id ) { 866 857 section.expand(); 867 858 } 868 859 }); 869 860 870 // Theme navigation in details view. 871 section.container.on( 'click keydown', '.left', function( event ) { 872 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 873 return; 874 } 861 // Preview installed themes. 862 section.container.on( 'click', '.theme-actions .preview-theme', function() { 863 var previewUrl = $( this ).data( 'previewUrl' ); 875 864 876 event.preventDefault(); // Keep this AFTER the key filter above865 $( '.wp-full-overlay' ).addClass( 'customize-loading' ); 877 866 867 window.parent.location = previewUrl; 868 }); 869 870 // Theme navigation in details view. 871 section.container.on( 'click', '.left', function( event ) { 878 872 section.previousTheme(); 879 873 }); 880 874 881 section.container.on( 'click keydown', '.right', function( event ) { 882 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 883 return; 884 } 885 886 event.preventDefault(); // Keep this AFTER the key filter above 887 875 section.container.on( 'click', '.right', function( event ) { 888 876 section.nextTheme(); 889 877 }); 890 878 891 section.container.on( 'click keydown', '.theme-backdrop, .close', function( event ) { 892 if ( api.utils.isKeydownButNotEnterEvent( event ) ) { 893 return; 894 } 895 896 event.preventDefault(); // Keep this AFTER the key filter above 897 879 section.container.on( 'click', '.theme-backdrop, .close', function( event ) { 898 880 section.closeDetails(); 899 881 }); 900 882 901 883 var renderScreenshots = _.throttle( _.bind( section.renderScreenshots, this ), 100 ); 902 section.container.on( 'input', '#themes-filter', function( event ) {884 $( '#customize-theme-section-navigation' ).on( 'input', '#themes-filter', function( event ) { 903 885 var count, 904 886 term = event.currentTarget.value.toLowerCase().trim().replace( '-', ' ' ), 905 887 controls = section.controls(); … … 912 894 913 895 // Update theme count. 914 896 count = section.container.find( 'li.customize-control:visible' ).length; 915 section.container.find( '.theme-count' ).text( count );897 $( '.control-panel-themes' ).find( '.theme-count' ).text( count ); 916 898 }); 917 899 918 // Pre-load the first 3 theme screenshots.919 api.bind( 'ready', function () {920 _.each( section.controls().slice( 0, 3 ), function ( control ) {921 var img, src = control.params.theme.screenshot[0];922 if ( src ) {923 img = new Image();924 img.src = src;900 // Event listeners for queries with user-entered terms. 901 if ( 'search' === section.params.action ) { 902 var debounced = _.debounce( section.checkTerm, 500 ); // Wait until there is no input for 500 miliseconds to initiate a search. 903 $( '#customize-theme-section-navigation' ).on( 'input', '#wp-filter-search-input', function() { 904 debounced( section ); 905 if ( ! section.expanded() ) { 906 section.expand(); 925 907 } 926 908 }); 909 } else if ( 'favorites' === section.params.action ) { 910 section.container.on( 'click', '.favorites-form-submit', function() { 911 section.checkTerm( section ); 912 }); 913 section.container.on( 'keydown', '#wporg-username-input', function( e ) { 914 if ( api.utils.isKeydownButNotEnterEvent( e ) ) { 915 return; 916 } 917 section.checkTerm( section ); 918 }); 919 } else if ( 'feature_filter' === section.params.action ) { 920 section.container.on( 'click', '.filter-group input', function() { 921 section.filtersChecked(); 922 }); 923 section.container.on( 'click', '.clear-filters', function() { 924 section.clearFilters(); 925 }); 926 section.container.on( 'click', '.apply-filters', function() { 927 section.checkTerm( section ); 928 }); 929 section.container.on( 'click', '.filtered-by .tags, .filtered-by button', function() { 930 section.container.find( '.filter-group' ).show(); 931 section.container.find( '.buttons' ).show(); 932 section.container.find( '.filtered-by' ).hide(); 933 section.container.find( '.theme-browser' ).hide(); 934 }); 935 } 936 937 // Move section-expansion buttons to consolidated menu bar in themes panel. 938 api.bind( 'ready', function () { 939 section.container.find( '.customize-themes-section-title' ).appendTo( $( '#customize-theme-section-navigation .filter-links' ) ); 927 940 }); 928 941 }, 929 942 … … 948 961 } 949 962 950 963 // Note: there is a second argument 'args' passed 951 var position, scroll, 952 panel = this, 953 section = panel.container.closest( '.accordion-section' ), 954 overlay = section.closest( '.wp-full-overlay' ), 955 container = section.closest( '.wp-full-overlay-sidebar-content' ), 956 siblings = container.find( '.open' ), 957 customizeBtn = section.find( '.customize-theme' ), 958 changeBtn = section.find( '.change-theme' ), 959 content = section.find( '.control-panel-content' ); 964 var section = this, 965 overlay = section.container.closest( '.wp-full-overlay-sidebar-content' ), 966 container = section.container.closest( '.control-panel-content' ); 960 967 961 968 if ( expanded ) { 962 969 970 // Load controls if none are loaded yet. 971 if ( 0 === section.loaded ) { 972 section.loadControls(); 973 } 974 963 975 // Collapse any sibling sections/panels 964 976 api.section.each( function ( otherSection ) { 965 if ( otherSection !== panel) {977 if ( otherSection !== section ) { 966 978 otherSection.collapse( { duration: args.duration } ); 967 979 } 968 980 }); 969 api.panel.each( function ( otherPanel ) {970 otherPanel.collapse( { duration: 0 } );971 });972 981 973 content.show( 0, function() { 974 position = content.offset().top; 975 scroll = container.scrollTop(); 976 content.css( 'margin-top', ( $( '#customize-header-actions' ).height() - position - scroll ) ); 977 section.addClass( 'current-panel' ); 978 overlay.addClass( 'in-themes-panel' ); 979 container.scrollTop( 0 ); 980 _.delay( panel.renderScreenshots, 10 ); // Wait for the controls 981 panel.$customizeSidebar.on( 'scroll.customize-themes-section', _.throttle( panel.renderScreenshots, 300 ) ); 982 if ( args.completeCallback ) { 983 args.completeCallback(); 984 } 985 } ); 986 customizeBtn.focus(); 982 section.container.addClass( 'current-section' ); 983 $( '#customize-theme-section-navigation .themes-section-' + section.id ).addClass( 'selected' ); 984 container.on( 'scroll', _.throttle( section.renderScreenshots, 300 ) ); 985 container.on( 'scroll', _.throttle( section.loadMore, 300 ) ); 986 if ( args.completeCallback ) { 987 args.completeCallback(); 988 } 989 section.updateCount(); // Show this section's count. 987 990 } else { 988 siblings.removeClass( 'open' ); 989 section.removeClass( 'current-panel' ); 990 overlay.removeClass( 'in-themes-panel' ); 991 panel.$customizeSidebar.off( 'scroll.customize-themes-section' ); 992 content.delay( 180 ).hide( 0, function() { 993 content.css( 'margin-top', 'inherit' ); // Reset 994 if ( args.completeCallback ) { 995 args.completeCallback(); 996 } 997 } ); 998 customizeBtn.attr( 'tabindex', '0' ); 999 changeBtn.focus(); 1000 container.scrollTop( 0 ); 991 section.container.removeClass( 'current-section' ); 992 $( '#customize-theme-section-navigation .themes-section-' + section.id ).removeClass( 'selected' ); 993 container.off( 'scroll' ); 994 if ( args.completeCallback ) { 995 args.completeCallback(); 996 } 1001 997 } 1002 998 }, 1003 999 1004 1000 /** 1005 * Recalculate the top margin.1001 * Don't recalculate the top margin. 1006 1002 * 1007 * @since 4. 4.01003 * @since 4.7.0 1008 1004 * @private 1009 1005 */ 1010 _recalculateTopMargin: function() { 1011 api.Panel.prototype._recalculateTopMargin.call( this ); 1006 _recalculateTopMargin: function() {}, 1007 1008 /** 1009 * Load theme data via ajax and add themes to the section as controls. 1010 * 1011 * @since 4.7.0 1012 */ 1013 loadControls: function() { 1014 var section = this, params, page, request, search; 1015 1016 if ( section.loading ) { 1017 return; // We're already loading a batch of themes. 1018 } 1019 1020 // Parameters for every API query. Additional params are set in PHP. 1021 page = Math.ceil( section.loaded / 100 ) + 1; 1022 params = { 1023 'customize-themes-nonce': api.settings.nonce['customize-themes'], 1024 'wp_customize': 'on', 1025 'theme_action': section.params.action, 1026 'customized_theme': api.settings.theme.stylesheet, 1027 'page': page 1028 } 1029 1030 // Add fields for special request actions. 1031 if ( 'search' === section.params.action ) { 1032 if ( '' === section.term ) { 1033 return; 1034 } else { 1035 params.search = section.term; 1036 } 1037 } else if ( 'favorites' === section.params.action ) { 1038 if ( '' === section.term ) { 1039 return; 1040 } else { 1041 params.user = section.term; 1042 } 1043 } else if ( 'feature_filter' === section.params.action ) { 1044 if ( '' === section.term ) { 1045 return; 1046 } else { 1047 params.tags = section.term; 1048 } 1049 } 1050 1051 // Load themes. 1052 section.container.addClass( 'loading' ); 1053 section.loading = true; 1054 section.container.find( '.no-themes' ).hide(); 1055 request = wp.ajax.post( 'customize-load-themes', params ); 1056 request.done(function( data ) { 1057 var themes = data.themes, 1058 themeControl, newThemeControls; 1059 if ( 0 !== themes.length ) { 1060 newThemeControls = new Array(); 1061 // Add controls for each theme. 1062 _.each( themes, function ( theme ) { 1063 customizeId = section.params.action + '_theme_' + theme.id; 1064 themeControl = new api.controlConstructor.theme( customizeId, { 1065 params: { 1066 type: 'theme', 1067 content: '<li id="customize-control-theme-' + section.params.action + '_' + theme.id + '" class="customize-control customize-control-theme"></li>', 1068 section: section.params.id, 1069 active: true, 1070 theme: theme, 1071 priority: section.loaded + 1 1072 }, 1073 previewer: api.previewer 1074 } ); 1075 1076 api.control.add( customizeId, themeControl ); 1077 newThemeControls.push( themeControl ); 1078 section.loaded = section.loaded + 1; 1079 return; 1080 }); 1081 1082 if ( 1 === page ) { 1083 // Pre-load the first 3 theme screenshots. 1084 _.each( section.controls().slice( 0, 3 ), function ( control ) { 1085 var img, src = control.params.theme.screenshot[0]; 1086 if ( src ) { 1087 img = new Image(); 1088 img.src = src; 1089 } 1090 }); 1091 if ( 'search' === section.params.action ) { 1092 wp.a11y.speak( api.settings.l10n.themeSearchResults.replace( '%d', data.info.results ) ); 1093 } 1094 } else { 1095 Array.prototype.push.apply( section.screenshotQueue, newThemeControls ); // Add new themes to the screenshot queue. 1096 } 1097 _.delay( section.renderScreenshots, 100 ); // Wait for the controls to become visible. 1098 1099 if ( 'installed' === section.action || 100 > themes.length ) { // If we have less than the requested 100 themes, it's the end of the list. 1100 section.fullyLoaded = true; 1101 } 1102 } else { 1103 if ( 0 === section.loaded ) { 1104 section.container.find( '.no-themes' ).show(); 1105 wp.a11y.speak( section.container.find( '.no-themes' ).text() ); 1106 } else { 1107 section.fullyLoaded = true; 1108 } 1109 } 1110 if ( 'installed' === section.params.action ) { 1111 section.updateCount(); 1112 } else { 1113 section.updateCount( data.info.results ); 1114 } 1115 section.container.find( '.unexpected-error' ).hide(); // Hide error notice in case it was previously shown. 1116 1117 // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case. 1118 section.container.removeClass( 'loading' ); 1119 section.loading = false; 1120 }); 1121 request.fail(function( data ) { 1122 if ( 'undefined' === typeof data ) { 1123 section.container.find( '.unexpected-error' ).show(); 1124 wp.a11y.speak( section.container.find( '.unexpected-error' ).text() ); 1125 } else if ( typeof console !== 'undefined' && console.error ) { 1126 console.error( data ); 1127 } 1128 1129 // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case. 1130 section.container.removeClass( 'loading' ); 1131 section.loading = false; 1132 }); 1012 1133 }, 1013 1134 1014 1135 /** 1136 * Determines whether more themes should be loaded, and loads them. 1137 * 1138 * @since 4.7.0 1139 */ 1140 loadMore: function() { 1141 var section = this, container, bottom, threshold, page; 1142 if ( ! section.fullyLoaded && ! section.loading ) { 1143 container = section.container.closest( '.control-panel-content' ); 1144 1145 bottom = container.scrollTop() + container.height(); 1146 threshold = container.prop( 'scrollHeight' ) - 3000; // Use a fixed distance to the bottom of loaded results to avoid unnecessarily loading results sooner when using a percentage of scroll distance. 1147 1148 if ( bottom > threshold ) { 1149 section.loadControls(); 1150 } 1151 } 1152 }, 1153 1154 /** 1155 * Event handler for search, feature filter, and favorites input that determines if the term has changed and loads new controls as needed. 1156 * 1157 * @since 4.7.0 1158 * 1159 * @param api.ThemesSection section The current theme section, passed through the debouncer. 1160 */ 1161 checkTerm: function( section ) { 1162 var newTerm, filteringBy; 1163 1164 // Find term. 1165 if ( 'search' === section.params.action ) { 1166 newTerm = $( '#wp-filter-search-input' ).val(); 1167 } else if ( 'favorites' === section.params.action ) { 1168 newTerm = $( '#wporg-username-input' ).val(); 1169 } else if ( 'feature_filter' === section.params.action ) { 1170 newTerm = section.term; // Set separately by filtersChecked(), as they're changed. 1171 if ( '' === newTerm ) { 1172 return; 1173 } 1174 1175 section.container.find( '.filter-group' ).hide(); 1176 section.container.find( '.buttons' ).hide(); 1177 section.container.find( '.filtered-by' ).show(); 1178 section.container.find( '.theme-browser' ).show(); 1179 filteringBy = section.container.find( '.filtered-by .tags' ); 1180 filteringBy.empty(); 1181 1182 _.each( newTerm, function( tag ) { 1183 name = $( 'label[for="filter-id-' + tag + '"]' ).text(); 1184 filteringBy.append( '<span class="tag">' + name + '</span>' ); 1185 }); 1186 } else { 1187 return; 1188 } 1189 1190 if ( section.term === newTerm && 'feature_filter' !== section.params.action ) { 1191 return; 1192 } 1193 // Clear the controls in the section. 1194 _.each( section.controls(), function( control ) { 1195 control.container.remove(); 1196 api.control.remove( control.id ); 1197 }); 1198 section.loaded = 0; 1199 section.fullyLoaded = false; 1200 section.screenshotQueue = null; 1201 1202 if ( '' !== newTerm ) { // Empty term should not show any results. 1203 // Run a new query, with loadControls handling paging, etc. 1204 section.term = newTerm; 1205 section.loadControls(); 1206 } 1207 }, 1208 1209 /** 1210 * Check for filters checked in the feature filter list. 1211 * 1212 * @since 4.7.0 1213 */ 1214 filtersChecked: function() { 1215 var section = this, 1216 items = section.container.find( '.filter-group' ).find( ':checkbox' ), 1217 tags = []; 1218 1219 if ( 'feature_filter' !== section.params.action ) { 1220 return false; 1221 } 1222 1223 _.each( items.filter( ':checked' ), function( item ) { 1224 tags.push( $( item ).prop( 'value' ) ); 1225 }); 1226 1227 // When no filters are checked, restore initial state and return 1228 if ( tags.length === 0 ) { 1229 section.container.find( '.filter-drawer .apply-filters' ).find( 'span' ).text( '' ); 1230 section.container.find( '.filter-drawer .clear-filters' ).hide(); 1231 section.term = ''; 1232 } else { 1233 section.container.find( '.filter-drawer .apply-filters' ).find( 'span' ).text( tags.length ); 1234 section.container.find( '.filter-drawer .clear-filters' ).css( 'display', 'inline-block' ); // $.show() manually. 1235 section.term = tags; 1236 } 1237 }, 1238 1239 /** 1240 * Clear filters from the feature filter list. 1241 * 1242 * @since 4.7.0 1243 */ 1244 clearFilters: function() { 1245 var section = this, 1246 items = section.container.find( '.filter-group' ).find( ':checkbox' ); 1247 1248 _.each( items.filter( ':checked' ), function( item ) { 1249 $( item ).prop( 'checked', false ); 1250 }); 1251 section.filtersChecked(); 1252 }, 1253 1254 /** 1015 1255 * Render control's screenshot if the control comes into view. 1016 1256 * 1017 1257 * @since 4.2.0 … … 1019 1259 renderScreenshots: function( ) { 1020 1260 var section = this; 1021 1261 1022 // Fill queue initially. 1023 if ( section.screenshotQueue === null ) { 1024 section.screenshotQueue = section.controls(); 1262 // Fill queue initially, or check for more if empty. 1263 if ( section.screenshotQueue === null || 0 === section.screenshotQueue.length ) { 1264 // Add controls that haven't had their screenshots rendered. 1265 section.screenshotQueue = _.filter( section.controls(), function( control ) { 1266 return ! control.screenshotRendered; 1267 }); 1025 1268 } 1026 1269 1027 // Are all screenshots rendered ?1270 // Are all screenshots rendered (for now)? 1028 1271 if ( ! section.screenshotQueue.length ) { 1029 1272 return; 1030 1273 } … … 1060 1303 }, 1061 1304 1062 1305 /** 1306 * Update the number of themes in the section. 1307 * 1308 * @since 4.7.0 1309 */ 1310 updateCount: function ( count ) { 1311 if ( ! count ) { 1312 count = this.loaded; 1313 } 1314 this.container.closest( '.control-panel-content' ).find( '.theme-count' ).text( count ); 1315 }, 1316 1317 /** 1063 1318 * Advance the modal to the next theme. 1064 1319 * 1065 1320 * @since 4.2.0 … … 1079 1334 * @since 4.2.0 1080 1335 */ 1081 1336 getNextTheme: function () { 1082 var control, next;1083 control = api.control( 'theme_' + this.currentTheme );1337 var section = this, control, next; 1338 control = api.control( section.params.action + '_theme_' + this.currentTheme ); 1084 1339 next = control.container.next( 'li.customize-control-theme' ); 1085 1340 if ( ! next.length ) { 1086 1341 return false; 1087 1342 } 1088 next = next[0].id.replace( 'customize-control- ', '' );1343 next = next[0].id.replace( 'customize-control-theme-' + section.params.action, section.params.action + '_theme' ); 1089 1344 control = api.control( next ); 1090 1345 1091 1346 return control.params.theme; … … 1111 1366 * @since 4.2.0 1112 1367 */ 1113 1368 getPreviousTheme: function () { 1114 var control, previous;1115 control = api.control( 'theme_' + this.currentTheme );1369 var section = this, control, previous; 1370 control = api.control( section.params.action + '_theme_' + this.currentTheme ); 1116 1371 previous = control.container.prev( 'li.customize-control-theme' ); 1117 1372 if ( ! previous.length ) { 1118 1373 return false; 1119 1374 } 1120 previous = previous[0].id.replace( 'customize-control- ', '' );1375 previous = previous[0].id.replace( 'customize-control-theme-' + section.params.action, section.params.action + '_theme' ); 1121 1376 control = api.control( previous ); 1122 1377 1123 1378 return control.params.theme; … … 1165 1420 closeDetails: function () { 1166 1421 $( 'body' ).removeClass( 'modal-open' ); 1167 1422 this.overlay.fadeOut( 'fast' ); 1168 api.control( 'theme_' + this.currentTheme).focus();1423 api.control( this.params.action + '_theme_' + this.currentTheme ).container.find( '.theme' ).focus(); 1169 1424 }, 1170 1425 1171 1426 /** … … 1455 1710 } 1456 1711 }); 1457 1712 1713 1458 1714 /** 1715 * wp.customize.ThemesPanel 1716 * 1717 * Custom section for themes that displays without the customize preview. 1718 * 1719 * @constructor 1720 * @augments wp.customize.Panel 1721 * @augments wp.customize.Container 1722 */ 1723 api.ThemesPanel = api.Panel.extend({ 1724 installingThemes: [], 1725 1726 /** 1727 * @since 4.7.0 1728 */ 1729 attachEvents: function () { 1730 var panel = this; 1731 1732 // Expand/Collapse panel. 1733 panel.container.on( 'click', '.change-theme, .customize-theme', function( event ) { 1734 if ( panel.expanded() ) { 1735 panel.collapse(); 1736 } else { 1737 panel.expand(); 1738 } 1739 }); 1740 1741 // Toggle help display. 1742 panel.container.on( 'click', '.customize-help-toggle', function() { 1743 var button = $( this ), 1744 content = button.next( '.customize-panel-description' ); 1745 button.toggleClass( 'toggled' ); 1746 content.toggle(); 1747 if ( button.hasClass( 'toggled' ) ) { 1748 button.attr( 'aria-expanded', 'true' ); 1749 } else { 1750 button.attr( 'aria-expanded', 'false' ); 1751 } 1752 }); 1753 1754 api.bind( 'saved', function() { 1755 panel.container.find( '.customize-themes-unsaved-changes' ).hide(); 1756 }); 1757 1758 // Save & publish customizer changes. 1759 panel.container.on( 'click', '#customize-themes-save', function() { 1760 $( '#save' ).click(); // Trigger customizer save. 1761 panel.container.find( '.customize-themes-unsaved-changes' ).hide(); 1762 api.section( 'installed_themes' ).focus(); 1763 }); 1764 1765 // Toggle theme upload view. 1766 panel.container.on( 'click', '.upload-toggle', function() { 1767 var button = $( this ); 1768 panel.container.find( '.upload-theme' ).toggle(); 1769 if ( 'true' === button.attr( 'aria-expanded' ) ) { 1770 button.attr( 'aria-expanded', 'false' ); 1771 } else { 1772 button.attr( 'aria-expanded', 'true' ); 1773 } 1774 }); 1775 1776 // Install theme. 1777 panel.container.on( 'click', '.theme-install', function( event ) { 1778 panel.installTheme( event ); 1779 }); 1780 1781 // Update theme. Theme cards have the class, the details modal has the id. 1782 panel.container.on( 'click', '.update-theme, #update-theme', function( event ) { 1783 // #update-theme is a link. 1784 event.preventDefault(); 1785 event.stopPropagation(); 1786 1787 panel.updateTheme( event ); 1788 }); 1789 1790 // Update theme. 1791 panel.container.on( 'click', '.delete-theme', function( event ) { 1792 panel.deleteTheme( event ); 1793 }); 1794 1795 _.bindAll( this, 'installTheme', 'updateTheme' ); 1796 }, 1797 1798 /** 1799 * Update UI to reflect expanded state 1800 * 1801 * @since 4.7.0 1802 * 1803 * @param {Boolean} expanded 1804 * @param {Object} args 1805 * @param {Boolean} args.unchanged 1806 * @param {Callback} args.completeCallback 1807 */ 1808 onChangeExpanded: function ( expanded, args ) { 1809 1810 // Immediately call the complete callback if there were no changes 1811 if ( args.unchanged ) { 1812 if ( args.completeCallback ) { 1813 args.completeCallback(); 1814 } 1815 return; 1816 } 1817 1818 // Note: there is a second argument 'args' passed 1819 var panel = this, 1820 section = panel.container.closest( '.accordion-section' ), 1821 overlay = section.closest( '.wp-full-overlay' ), 1822 customizeBtn = section.find( '.panel-meta .customize-theme' ), 1823 changeBtn = section.find( '.change-theme' ), 1824 content = section.find( '.control-panel-content' ); 1825 1826 if ( expanded ) { 1827 content.show( 0, function() { 1828 overlay.addClass( 'in-themes-panel' ); 1829 section.addClass( 'current-panel' ); 1830 customizeBtn.attr( 'tabindex', '0' ); 1831 customizeBtn.focus(); 1832 if ( false === api.state( 'saved' ).get() ) { 1833 panel.container.find( '.customize-themes-unsaved-changes' ).show(); 1834 } 1835 }); 1836 // Automatically open the installed themes section. 1837 api.section( 'installed_themes' ).expand(); 1838 } else { 1839 section.removeClass( 'current-panel' ); 1840 overlay.removeClass( 'in-themes-panel' ); 1841 content.delay( 200 ).hide( 0, function() { 1842 if ( args.completeCallback ) { 1843 args.completeCallback(); 1844 } 1845 } ); 1846 customizeBtn.attr( 'tabindex', '-1' ); 1847 changeBtn.focus(); 1848 } 1849 }, 1850 1851 /** 1852 * Don't recalculate the top margin. 1853 * 1854 * @since 4.7.0 1855 * @private 1856 */ 1857 _recalculateTopMargin: function() {}, 1858 1859 /** 1860 * Install a theme via wp.updates. 1861 * 1862 * @since 4.7.0 1863 */ 1864 installTheme: function( event ) { 1865 var preview = false, previewUrl, 1866 slug = $( event.target ).data( 'slug' ); 1867 1868 if ( -1 !== $.inArray( this.installingThemes, slug ) ) { 1869 return; // Theme is already being installed. 1870 } 1871 1872 wp.updates.maybeRequestFilesystemCredentials( event ); 1873 1874 $( document ).on( 'wp-theme-install-success', function( event, response ) { 1875 var theme = false, customizeId, themeControl; 1876 if ( preview ) { 1877 window.parent.location = previewUrl; 1878 } else { 1879 api.control.each( function( control ) { 1880 if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) { 1881 theme = control.params.theme; // Used below to add theme control. 1882 control.rerenderAsInstalled( true ); 1883 } 1884 }); 1885 1886 // Don't add the same theme more than once. 1887 if ( ! theme || 'undefined' !== typeof api.control( 'installed_theme_' + theme.id ) ) { 1888 return; 1889 } 1890 1891 // Add theme control to installed section. 1892 theme.type = 'installed'; 1893 customizeId = 'installed_theme_' + theme.id; 1894 themeControl = new api.controlConstructor.theme( customizeId, { 1895 params: { 1896 type: 'theme', 1897 content: '<li id="customize-control-theme-installed_' + theme.id + '" class="customize-control customize-control-theme"></li>', 1898 section: 'installed_themes', 1899 active: true, 1900 theme: theme, 1901 priority: 0 // Add all newly-installed themes to the top. 1902 }, 1903 previewer: api.previewer 1904 } ); 1905 1906 api.control.add( customizeId, themeControl ); 1907 api.control( customizeId ).container.trigger( 'render-screenshot' ); 1908 1909 // Close the details modal if it's open to the installed theme. 1910 api.section.each( function( section ) { 1911 if ( 'themes' !== section.params.type ) { 1912 if ( theme.id === section.currentTheme ) { // Don't close the modal if the user has navigated elsewhere. 1913 section.closeDetails(); 1914 } 1915 } 1916 }); 1917 } 1918 } ); 1919 1920 $( document ).on( 'wp-theme-install-failure', function( event, response ) { 1921 if ( preview ) { 1922 $( '.wp-full-overlay' ).removeClass( 'customize-loading' ); 1923 } 1924 }); 1925 1926 this.installingThemes.push( $( event.target ).data( 'slug' ) ); // Note: we don't remove elements from installingThemes, since they shouldn't be installed again. 1927 wp.updates.installTheme( { 1928 slug: $( event.target ).data( 'slug' ) 1929 } ); 1930 1931 // Also preview the theme as the event is triggered on Install & Preview. 1932 if ( $( event.target ).hasClass( 'preview' ) ) { 1933 preview = true; 1934 $( '.wp-full-overlay' ).addClass( 'customize-loading' ); 1935 previewUrl = $( event.target ).data( 'previewurl' ); 1936 } 1937 }, 1938 1939 /** 1940 * Update a theme via wp.updates. 1941 * 1942 * @since 4.7.0 1943 */ 1944 updateTheme: function( event ) { 1945 wp.updates.maybeRequestFilesystemCredentials( event ); 1946 1947 $( document ).on( 'wp-theme-update-success', function( event, response ) { 1948 // Rerender the control to reflect the update. 1949 api.control.each( function( control ) { 1950 if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) { 1951 control.params.theme.hasUpdate = false; 1952 control.rerenderAsInstalled( true ); 1953 } 1954 }); 1955 } ); 1956 1957 wp.updates.updateTheme( { 1958 slug: $( event.target ).closest( '.notice' ).data( 'slug' ) 1959 } ); 1960 }, 1961 1962 /** 1963 * Delete a theme via wp.updates. 1964 * 1965 * @since 4.7.0 1966 */ 1967 deleteTheme: function( event ) { 1968 var panel = this, 1969 theme = $( event.target ).data( 'slug' ), 1970 section = api.section( 'installed_themes' ); 1971 1972 event.preventDefault(); 1973 1974 // Confirmation dialog for deleting a theme. 1975 if ( ! window.confirm( api.settings.l10n.confirmDeleteTheme ) ) { 1976 return; 1977 } 1978 1979 wp.updates.maybeRequestFilesystemCredentials( event ); 1980 1981 $( document ).one( 'wp-theme-delete-success', function( event, response ) { 1982 var control = api.control( 'installed_theme_' + theme ); 1983 1984 // Remove theme control. 1985 control.container.remove(); 1986 api.control.remove( control.id ); 1987 1988 // Update installed count. 1989 section.loaded = section.loaded - 1; 1990 section.updateCount(); 1991 1992 // Rerender any other theme controls as uninstalled. 1993 api.control.each( function( control ) { 1994 if ( 'theme' === control.params.type && control.params.theme.id === theme ) { 1995 control.rerenderAsInstalled( false ); 1996 } 1997 }); 1998 } ); 1999 2000 wp.updates.deleteTheme( { 2001 slug: theme 2002 } ); 2003 2004 // Close modal and focus the section. 2005 section.closeDetails(); 2006 section.focus(); 2007 } 2008 2009 }); 2010 2011 2012 /** 1459 2013 * A Customizer Control. 1460 2014 * 1461 2015 * A control provides a UI element that allows a user to modify a Customizer Setting. … … 2773 3327 api.ThemeControl = api.Control.extend({ 2774 3328 2775 3329 touchDrag: false, 2776 isRendered: false,3330 screenshotRendered: false, 2777 3331 2778 3332 /** 2779 * Defer rendering the theme control until the section is displayed.2780 *2781 3333 * @since 4.2.0 2782 3334 */ 2783 renderContent: function () {2784 var control = this,2785 renderContentArgs = arguments;2786 2787 api.section( control.section(), function( section ) {2788 if ( section.expanded() ) {2789 api.Control.prototype.renderContent.apply( control, renderContentArgs );2790 control.isRendered = true;2791 } else {2792 section.expanded.bind( function( expanded ) {2793 if ( expanded && ! control.isRendered ) {2794 api.Control.prototype.renderContent.apply( control, renderContentArgs );2795 control.isRendered = true;2796 }2797 } );2798 }2799 } );2800 },2801 2802 /**2803 * @since 4.2.02804 */2805 3335 ready: function() { 2806 3336 var control = this; 2807 3337 … … 2821 3351 } 2822 3352 2823 3353 // Prevent the modal from showing when the user clicks the action button. 2824 if ( $( event.target ).is( '.theme-actions .button ' ) ) {3354 if ( $( event.target ).is( '.theme-actions .button, .update-theme' ) ) { 2825 3355 return; 2826 3356 } 2827 3357 2828 var previewUrl = $( this ).data( 'previewUrl' );2829 2830 $( '.wp-full-overlay' ).addClass( 'customize-loading' );2831 2832 window.parent.location = previewUrl;2833 });2834 2835 control.container.on( 'click keydown', '.theme-actions .theme-details', function( event ) {2836 if ( api.utils.isKeydownButNotEnterEvent( event ) ) {2837 return;2838 }2839 2840 event.preventDefault(); // Keep this AFTER the key filter above2841 2842 3358 api.section( control.section() ).showDetails( control.params.theme ); 2843 3359 }); 2844 3360 … … 2849 3365 if ( source ) { 2850 3366 $screenshot.attr( 'src', source ); 2851 3367 } 3368 control.screenshotRendered = true; 2852 3369 }); 2853 3370 }, 2854 3371 2855 3372 /** 2856 * Show or hide the theme based on the presence of the term in the title, description, and author.3373 * Show or hide the theme based on the presence of the term in the title, description, tags, and author. 2857 3374 * 2858 3375 * @since 4.2.0 2859 3376 */ … … 2869 3386 } else { 2870 3387 control.deactivate(); 2871 3388 } 3389 }, 3390 3391 /** 3392 * Rerender the theme from its JS template with the installed type. 3393 * 3394 * @since 4.7.0 3395 */ 3396 rerenderAsInstalled: function( installed ) { 3397 var control = this, section; 3398 if ( installed ) { 3399 control.params.theme.type = 'installed'; 3400 } else { 3401 section = api.section( control.params.section ); 3402 control.params.theme.type = section.params.action; 3403 } 3404 control.renderContent(); // replaces existing content 3405 control.container.trigger( 'render-screenshot' ); 2872 3406 } 2873 3407 }); 2874 3408 … … 3432 3966 background: api.BackgroundControl, 3433 3967 theme: api.ThemeControl 3434 3968 }; 3435 api.panelConstructor = {}; 3969 api.panelConstructor = { 3970 themes: api.ThemesPanel 3971 }; 3436 3972 api.sectionConstructor = { 3437 3973 themes: api.ThemesSection 3438 3974 }; … … 4027 4563 // Collapse the most granular expanded object. 4028 4564 collapsedObject = expandedControls[0] || expandedSections[0] || expandedPanels[0]; 4029 4565 if ( collapsedObject ) { 4566 if ( 'themes' === collapsedObject.params.type ) { 4567 // Themes panel or section. 4568 if ( $( 'body' ).hasClass( 'modal-open' ) ) { 4569 collapsedObject.closeDetails(); 4570 } else { 4571 // If we're collapsing a section, collapse the panel also. 4572 wp.customize.panel( 'themes' ).collapse(); 4573 } 4574 return; 4575 } 4030 4576 collapsedObject.collapse(); 4031 4577 event.preventDefault(); 4032 4578 } -
src/wp-admin/js/updates.js
179 179 if ( $notice.length ) { 180 180 $notice.replaceWith( $adminNotice ); 181 181 } else { 182 $( '.wrap' ).find( '> h1' ).after( $adminNotice ); 182 if ( 'customize' === pagenow ) { 183 $( '.customize-themes-notifications' ).append( $adminNotice ); 184 } else { 185 $( '.wrap' ).find( '> h1' ).after( $adminNotice ); 186 } 183 187 } 184 188 185 189 $document.trigger( 'wp-updates-notice-added' ); … … 907 911 if ( 'themes-network' === pagenow ) { 908 912 $notice = $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' ); 909 913 914 } else if ( 'customize' === pagenow ) { 915 // Update the theme details UI. 916 $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' ); 917 918 $notice.find( 'h3' ).remove(); 919 920 // Add the top-level UI, and update both. 921 $notice = $notice.add( $( '#customize-control-theme-installed_' + args.slug ).find( '.update-message' ) ); 922 $notice = $notice.addClass( 'updating-message' ).find( 'p' ); 923 910 924 } else { 911 925 $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' ); 912 926 … … 949 963 }, 950 964 $notice, newText; 951 965 966 if ( 'customize' === pagenow ) { 967 $theme = wp.customize.control( 'installed_theme_' + response.slug ).container; 968 } 969 952 970 if ( 'themes-network' === pagenow ) { 953 971 $notice = $theme.find( '.update-message' ); 954 972 … … 1003 1021 return; 1004 1022 } 1005 1023 1024 if ( 'customize' === pagenow ) { 1025 $theme = wp.customize.control( 'installed_theme_' + response.slug ).container; 1026 } 1027 1006 1028 if ( 'themes-network' === pagenow ) { 1007 1029 $notice = $theme.find( '.update-message ' ); 1008 1030 } else { … … 1139 1161 return; 1140 1162 } 1141 1163 1142 if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) { 1143 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1144 $card = $( '.install-theme-info' ).prepend( $message ); 1164 if ( 'customize' === pagenow ) { 1165 if ( $document.find( 'body' ).hasClass( 'modal-open' ) ) { 1166 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1167 $card = $( '.theme-overlay .theme-info' ).prepend( $message ); 1168 } else { 1169 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1170 $card = $button.closest( '.theme' ).addClass( 'theme-install-failed' ).append( $message ); 1171 } 1145 1172 } else { 1146 $card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message ); 1147 $button = $card.find( '.theme-install' ); 1173 if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) { 1174 $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); 1175 $card = $( '.install-theme-info' ).prepend( $message ); 1176 } else { 1177 $card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message ); 1178 $button = $card.find( '.theme-install' ); 1179 } 1148 1180 } 1149 1181 1150 1182 $button -
src/wp-includes/class-wp-customize-manager.php
230 230 231 231 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' ); 232 232 233 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-panel.php' ); 233 234 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-section.php' ); 234 235 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-sidebar-section.php' ); 235 236 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-section.php' ); … … 289 290 290 291 add_action( 'wp_ajax_customize_save', array( $this, 'save' ) ); 291 292 add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) ); 293 add_action( 'wp_ajax_customize-load-themes', array( $this, 'load_themes_ajax' ) ); 292 294 293 295 add_action( 'customize_register', array( $this, 'register_controls' ) ); 294 296 add_action( 'customize_register', array( $this, 'register_dynamic_settings' ), 11 ); // allow code to create settings first … … 302 304 303 305 // Export the settings to JS via the _wpCustomizeSettings variable. 304 306 add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 ); 307 308 // Add theme update notices. 309 if ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) ) { 310 require_once( ABSPATH . '/wp-admin/includes/update.php' ); 311 add_action( 'customize_controls_print_footer_scripts', 'wp_print_admin_notice_templates' ); 312 } 305 313 } 306 314 307 315 /** … … 1626 1634 foreach ( $this->controls as $control ) { 1627 1635 $control->enqueue(); 1628 1636 } 1637 if ( ! is_multisite() && ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) || current_user_can( 'delete_themes' ) ) ) { 1638 wp_enqueue_script( 'updates' ); 1639 } 1629 1640 } 1630 1641 1631 1642 /** … … 1779 1790 $nonces = array( 1780 1791 'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ), 1781 1792 'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ), 1793 'customize-themes' => wp_create_nonce( 'customize-themes' ), 1782 1794 ); 1783 1795 1784 1796 /** … … 1859 1871 'autofocus' => $this->get_autofocus(), 1860 1872 'documentTitleTmpl' => $this->get_document_title_template(), 1861 1873 'previewableDevices' => $this->get_previewable_devices(), 1874 'l10n' => array( 1875 'confirmDeleteTheme' => __( 'Are you sure you want to delete this theme?' ), 1876 /* translators: %d is the number of theme search results, which cannot consider singular vs. plural forms */ 1877 'themeSearchResults' => __( '%d themes found' ), 1878 ), 1862 1879 ); 1863 1880 1864 1881 // Prepare Customize Section objects to pass to JavaScript. … … 1962 1979 1963 1980 /* Panel, Section, and Control Types */ 1964 1981 $this->register_panel_type( 'WP_Customize_Panel' ); 1982 $this->register_panel_type( 'WP_Customize_Themes_Panel' ); 1965 1983 $this->register_section_type( 'WP_Customize_Section' ); 1966 1984 $this->register_section_type( 'WP_Customize_Sidebar_Section' ); 1985 $this->register_section_type( 'WP_Customize_Themes_Section' ); 1967 1986 $this->register_control_type( 'WP_Customize_Color_Control' ); 1968 1987 $this->register_control_type( 'WP_Customize_Media_Control' ); 1969 1988 $this->register_control_type( 'WP_Customize_Upload_Control' ); … … 1973 1992 $this->register_control_type( 'WP_Customize_Site_Icon_Control' ); 1974 1993 $this->register_control_type( 'WP_Customize_Theme_Control' ); 1975 1994 1976 /* Themes */1995 /* Themes (controls are loaded via ajax) */ 1977 1996 1978 $this->add_ section( new WP_Customize_Themes_Section( $this, 'themes', array(1997 $this->add_panel( new WP_Customize_Themes_Panel( $this, 'themes', array( 1979 1998 'title' => $this->theme()->display( 'Name' ), 1999 'description' => __( 'Once themes are installed, you can live-preview them on your site, customize them, and publish your new design. Browse available themes via the categories in this menu or upload a theme from a <code>.zip</code> file.' ), 1980 2000 'capability' => 'switch_themes', 1981 2001 'priority' => 0, 1982 2002 ) ) ); 1983 2003 1984 // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience). 1985 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array( 2004 $this->add_section( new WP_Customize_Themes_Section( $this, 'installed_themes', array( 2005 'title' => __( 'Installed' ), 2006 'action' => 'installed', 1986 2007 'capability' => 'switch_themes', 2008 'panel' => 'themes', 2009 'priority' => 0, 1987 2010 ) ) ); 1988 2011 1989 require_once( ABSPATH . 'wp-admin/includes/theme.php' ); 2012 $this->add_section( new WP_Customize_Themes_Section( $this, 'featured_themes', array( 2013 'title' => __( 'Featured' ), 2014 'action' => 'featured', 2015 'capability' => 'install_themes', 2016 'panel' => 'themes', 2017 'priority' => 5, 2018 ) ) ); 1990 2019 1991 // Theme Controls. 2020 $this->add_section( new WP_Customize_Themes_Section( $this, 'popular_themes', array( 2021 'title' => __( 'Popular' ), 2022 'action' => 'popular', 2023 'capability' => 'install_themes', 2024 'panel' => 'themes', 2025 'priority' => 10, 2026 ) ) ); 1992 2027 1993 // Add a control for the active/original theme. 1994 if ( ! $this->is_theme_active() ) { 1995 $themes = wp_prepare_themes_for_js( array( wp_get_theme( $this->original_stylesheet ) ) ); 1996 $active_theme = current( $themes ); 1997 $active_theme['isActiveTheme'] = true; 1998 $this->add_control( new WP_Customize_Theme_Control( $this, $active_theme['id'], array( 1999 'theme' => $active_theme, 2000 'section' => 'themes', 2001 'settings' => 'active_theme', 2002 ) ) ); 2003 } 2028 $this->add_section( new WP_Customize_Themes_Section( $this, 'latest_themes', array( 2029 'title' => __( 'Latest' ), 2030 'action' => 'latest', 2031 'capability' => 'install_themes', 2032 'panel' => 'themes', 2033 'priority' => 15, 2034 ) ) ); 2004 2035 2005 $themes = wp_prepare_themes_for_js(); 2006 foreach ( $themes as $theme ) { 2007 if ( $theme['active'] || $theme['id'] === $this->original_stylesheet ) { 2008 continue; 2009 } 2036 $this->add_section( new WP_Customize_Themes_Section( $this, 'favorites_themes', array( 2037 'title' => __( 'Favorites' ), 2038 'action' => 'favorites', 2039 'capability' => 'install_themes', 2040 'panel' => 'themes', 2041 'priority' => 20, 2042 ) ) ); 2043 2044 $this->add_section( new WP_Customize_Themes_Section( $this, 'feature_filter_themes', array( 2045 'title' => __( 'Feature Filter' ), 2046 'action' => 'feature_filter', 2047 'capability' => 'install_themes', 2048 'panel' => 'themes', 2049 'priority' => 25, 2050 ) ) ); 2051 2052 $this->add_section( new WP_Customize_Themes_Section( $this, 'search_themes', array( 2053 'title' => __( 'Search themes…' ), 2054 'action' => 'search', 2055 'capability' => 'install_themes', 2056 'panel' => 'themes', 2057 'priority' => 30, 2058 ) ) ); 2059 2060 // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience). 2061 $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array( 2062 'capability' => 'switch_themes', 2063 ) ) ); 2010 2064 2011 $theme_id = 'theme_' . $theme['id'];2012 $theme['isActiveTheme'] = false;2013 $this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array(2014 'theme' => $theme,2015 'section' => 'themes',2016 'settings' => 'active_theme',2017 ) ) );2018 }2019 2065 2020 2066 /* Site Identity */ 2021 2067 … … 2343 2389 } 2344 2390 2345 2391 /** 2392 * Load themes into the theme browsing/installation UI. 2393 * 2394 * @since 4.7.0 2395 * @access public 2396 */ 2397 public function load_themes_ajax() { 2398 check_ajax_referer( 'customize-themes', 'customize-themes-nonce' ); 2399 2400 if ( ! current_user_can( 'switch_themes' ) ) { 2401 wp_die( -1 ); 2402 } 2403 2404 if ( empty( $_POST['theme_action'] ) ) { 2405 wp_send_json_error( 'missing_theme_action' ); 2406 } 2407 2408 if ( 'search' === $_POST['theme_action'] && ! array_key_exists( 'search', $_POST ) ) { 2409 wp_send_json_error( 'empty_search' ); 2410 } elseif ( 'favorites' === $_POST['theme_action'] && ! array_key_exists( 'user', $_POST ) ) { 2411 wp_send_json_error( 'empty_user' ); 2412 } elseif ( 'feature_filter' === $_POST['theme_action'] && ! array_key_exists( 'tags', $_POST ) ) { 2413 wp_send_json_error( 'no_features' ); 2414 } 2415 2416 require_once( ABSPATH . 'wp-admin/includes/theme.php' ); 2417 if ( 'installed' === $_POST['theme_action'] ) { 2418 $themes = array( 'themes' => wp_prepare_themes_for_js() ); 2419 foreach ( $themes['themes'] as &$theme ) { 2420 $theme['type'] = 'installed'; 2421 // Set active based on customized theme. 2422 if ( $_POST['customized_theme'] === $theme['id'] ) { 2423 $theme['active'] = true; 2424 } else { 2425 $theme['active'] = false; 2426 } 2427 } 2428 } else { 2429 if ( ! current_user_can( 'install_themes' ) ) { 2430 wp_die( -1 ); 2431 } 2432 2433 // Arguments for all queries. 2434 $args = array( 2435 'per_page' => 100, 2436 'page' => absint( $_POST['page'] ), 2437 'fields' => array( 2438 'slug' => true, 2439 'screenshot' => true, 2440 'description' => true, 2441 'requires' => true, 2442 'rating' => true, 2443 'downloaded' => true, 2444 'downloadLink' => true, 2445 'last_updated' => true, 2446 'homepage' => true, 2447 'num_ratings' => true, 2448 'tags' => true, 2449 ) 2450 ); 2451 2452 // Specialized handling for each query. 2453 switch ( $_POST['theme_action'] ) { 2454 case 'search': 2455 $args['search'] = wp_unslash( $_POST['search'] ); 2456 break; 2457 case 'favorites': 2458 $args['user'] = wp_unslash( $_POST['user'] ); 2459 case 'featured': 2460 case 'popular': 2461 $args['browse'] = $_POST['theme_action']; 2462 break; 2463 case 'latest': 2464 $args['browse'] = 'new'; 2465 break; 2466 case 'feature_filter': 2467 $args['tag'] = wp_unslash( $_POST['tags'] ); 2468 break; 2469 } 2470 2471 // Load themes from the .org API. 2472 $themes = themes_api( 'query_themes', $args ); 2473 if ( is_wp_error( $themes ) ) { 2474 wp_send_json_error(); 2475 } 2476 2477 // Prepare a list of installed themes to check against before the loop. 2478 $installed_themes = array(); 2479 $wp_themes = wp_get_themes(); 2480 foreach ( $wp_themes as $theme ) { 2481 $installed_themes[] = $theme->get_stylesheet(); 2482 } 2483 $update_php = network_admin_url( 'update.php?action=install-theme' ); 2484 foreach ( $themes->themes as &$theme ) { 2485 $theme->install_url = add_query_arg( array( 2486 'theme' => $theme->slug, 2487 '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ) 2488 ), $update_php ); 2489 2490 $theme->name = wp_kses( $theme->name, $themes_allowedtags ); 2491 $theme->author = wp_kses( $theme->author, $themes_allowedtags ); 2492 $theme->version = wp_kses( $theme->version, $themes_allowedtags ); 2493 $theme->description = wp_kses( $theme->description, $themes_allowedtags ); 2494 $theme->tags = implode( ', ', $theme->tags ); 2495 $theme->stars = wp_star_rating( array( 'rating' => $theme->rating, 'type' => 'percent', 'number' => $theme->num_ratings, 'echo' => false ) ); 2496 $theme->num_ratings = number_format_i18n( $theme->num_ratings ); 2497 $theme->preview_url = set_url_scheme( $theme->preview_url ); 2498 2499 // Handle themes that are already installed as installed themes. 2500 if ( in_array( $theme->slug, $installed_themes ) ) { 2501 $theme->type = 'installed'; 2502 } else { 2503 $theme->type = $_POST['theme_action']; 2504 } 2505 2506 // Set active based on customized theme. 2507 if ( $_POST['customized_theme'] === $theme->slug ) { 2508 $theme->active = true; 2509 } else { 2510 $theme->active = false; 2511 } 2512 2513 // Map available theme properties to installed theme properties. 2514 $theme->id = $theme->slug; 2515 $theme->screenshot = array( $theme->screenshot_url ); 2516 $theme->authorAndUri = $theme->author; 2517 unset( $theme->slug ); 2518 unset( $theme->screenshot_url ); 2519 unset( $theme->author ); 2520 } 2521 } 2522 wp_send_json_success( $themes ); 2523 } 2524 2525 2526 /** 2346 2527 * Callback for validating the header_textcolor value. 2347 2528 * 2348 2529 * Accepts 'blank', and otherwise uses sanitize_hex_color_no_hash(). -
src/wp-includes/customize/class-wp-customize-theme-control.php
67 67 $preview_url = esc_url( add_query_arg( 'theme', '__THEME__', $current_url ) ); // Token because esc_url() strips curly braces. 68 68 $preview_url = str_replace( '__THEME__', '{{ data.theme.id }}', $preview_url ); 69 69 ?> 70 <# if ( data.theme. isActiveTheme ) { #>71 <div class="theme active" tabindex="0" data-preview-url="<?php echo esc_attr( $active_url ); ?>"aria-describedby="{{ data.theme.id }}-action {{ data.theme.id }}-name">70 <# if ( data.theme.active ) { #> 71 <div class="theme active" tabindex="0" aria-describedby="{{ data.theme.id }}-action {{ data.theme.id }}-name"> 72 72 <# } else { #> 73 <div class="theme" tabindex="0" data-preview-url="<?php echo esc_attr( $preview_url ); ?>"aria-describedby="{{ data.theme.id }}-action {{ data.theme.id }}-name">73 <div class="theme" tabindex="0" aria-describedby="{{ data.theme.id }}-action {{ data.theme.id }}-name"> 74 74 <# } #> 75 75 76 <# if ( data.theme.screenshot [0] ) { #>76 <# if ( data.theme.screenshot && data.theme.screenshot[0] ) { #> 77 77 <div class="theme-screenshot"> 78 78 <img data-src="{{ data.theme.screenshot[0] }}" alt="" /> 79 79 </div> … … 81 81 <div class="theme-screenshot blank"></div> 82 82 <# } #> 83 83 84 <# if ( data.theme.isActiveTheme ) { #> 85 <span class="more-details" id="{{ data.theme.id }}-action"><?php _e( 'Customize' ); ?></span> 86 <# } else { #> 87 <span class="more-details" id="{{ data.theme.id }}-action"><?php _e( 'Live Preview' ); ?></span> 84 <span class="more-details theme-details" id="{{ data.theme.id }}-action"><?php _e( 'Theme Details' ); ?></span> 85 86 87 <# if ( 'installed' === data.theme.type && data.theme.hasUpdate ) { #> 88 <div class="update-message notice inline notice-warning notice-alt" data-slug="{{ data.theme.id }}"><p><?php _e( 'New version available. <button class="button-link update-theme" type="button">Update now</button>' ); ?></p></div> 88 89 <# } #> 89 90 90 <div class="theme-author"><?php printf( __( 'By %s' ), '{{ data.theme.author }}' ); ?></div> 91 92 <# if ( data.theme.isActiveTheme ) { #> 91 <# if ( data.theme.active ) { #> 93 92 <h3 class="theme-name" id="{{ data.theme.id }}-name"> 94 93 <?php 95 94 /* translators: %s: theme name */ 96 printf( __( '<span> Active:</span> %s' ), '{{{ data.theme.name }}}' );95 printf( __( '<span>Current:</span> %s' ), '{{ data.theme.name }}' ); 97 96 ?> 98 97 </h3> 98 <div class="theme-actions"> 99 <button type="button" class="button button-primary customize-theme"><?php _e( 'Customize' ); ?></span> 100 <# } else if ( 'installed' === data.theme.type ) { #> 101 <h3 class="theme-name" id="{{ data.theme.id }}-name">{{ data.theme.name }}</h3> 102 <div class="theme-actions"> 103 <button type="button" class="button button-primary preview-theme" data-preview-url="<?php echo esc_attr( $preview_url ); ?>"><?php _e( 'Live Preview' ); ?></span> 104 </div> 105 <div class="notice notice-success notice-alt"><p><?php _e( 'Installed' ); ?></p></div> 99 106 <# } else { #> 100 <h3 class="theme-name" id="{{ data.theme.id }}-name">{{ { data.theme.name }}}</h3>107 <h3 class="theme-name" id="{{ data.theme.id }}-name">{{ data.theme.name }}</h3> 101 108 <div class="theme-actions"> 102 <button type="button" class="button theme-details"><?php _e( 'Theme Details' ); ?></button> 109 <button type="button" class="button theme-install" data-slug="{{ data.theme.id }}"><?php _e( 'Install' ); ?></button> 110 <button type="button" class="button button-primary theme-install preview" data-slug="{{ data.theme.id }}" data-previewurl="<?php echo esc_attr( $preview_url ); ?>"><?php _e( 'Install & Preview' ); ?></button> 103 111 </div> 104 <# } #> 112 <# } #> 105 113 </div> 106 114 <?php 107 115 } -
src/wp-includes/customize/class-wp-customize-themes-panel.php
1 <?php 2 /** 3 * Customize API: WP_Customize_Themes_Panel class 4 * 5 * @package WordPress 6 * @subpackage Customize 7 * @since 4.7.0 8 */ 9 10 /** 11 * Customize Themes Panel Class 12 * 13 * @since 4.7.0 14 * 15 * @see WP_Customize_Panel 16 */ 17 class WP_Customize_Themes_Panel extends WP_Customize_Panel { 18 19 /** 20 * Panel type. 21 * 22 * @since 4.7.0 23 * @access public 24 * @var string 25 */ 26 public $type = 'themes'; 27 28 /** 29 * An Underscore (JS) template for rendering this panel's container. 30 * 31 * The themes panel renders a custom panel heading with the current theme and a switch themes button. 32 * 33 * @see WP_Customize_Panel::print_template() 34 * 35 * @since 4.7.0 36 * @access protected 37 */ 38 protected function render_template() { 39 ?> 40 <li id="accordion-section-{{ data.id }}" class="accordion-section control-panel-themes"> 41 <h3 class="accordion-section-title"> 42 <?php 43 if ( $this->manager->is_theme_active() ) { 44 echo '<span class="customize-action">' . __( 'Active theme' ) . '</span> {{ data.title }}'; 45 } else { 46 echo '<span class="customize-action">' . __( 'Previewing theme' ) . '</span> {{ data.title }}'; 47 } 48 ?> 49 50 <?php 51 if ( current_user_can( 'switch_themes' ) ) : ?> 52 <button type="button" class="button change-theme"><?php _ex( 'Change', 'theme' ); ?></button> 53 <?php endif; ?> 54 </h3> 55 <ul class="accordion-sub-container control-panel-content"></ul> 56 </li> 57 <?php 58 } 59 60 /** 61 * An Underscore (JS) template for this panel's content (but not its container). 62 * 63 * Class variables for this panel class are available in the `data` JS object; 64 * export custom variables by overriding WP_Customize_Panel::json(). 65 * 66 * @since 4.7.0 67 * @access protected 68 * 69 * @see WP_Customize_Panel::print_template() 70 */ 71 protected function content_template() { 72 ?> 73 <li class="panel-meta accordion-section customize-info"> 74 <div class="current-theme"> 75 <span class="preview-notice"> 76 <?php 77 /* translators: %s: the site/panel title in the Customizer */ 78 printf( __( 'Current Theme %s' ), '<strong class="panel-title">{{ data.title }}</strong>' ); 79 ?> 80 </span> 81 <button type="button" class="button customize-theme" tabindex="-1"><?php _e( 'Customize' ); ?></button> 82 </div> 83 <h2><?php _e( 'Themes' ); ?></h2> 84 <?php if ( current_user_can( 'install_themes' ) ) : ?> 85 <button type="button" class="upload-toggle page-title-action" aria-expanded="false"><?php _e( 'Upload Theme' ); ?></button> 86 <?php endif; ?> 87 </li> 88 <li class="upload-theme"> 89 <?php if ( current_user_can( 'install_themes' ) ) : ?> 90 <p class="install-help"><?php _e( 'If you have a theme in a <code>.zip</code> format, you may install it by uploading it here.' ); ?></p> 91 <div class="wp-upload-form"> 92 <input type="file" id="themezip" name="themezip"><label class="screen-reader-text" for="themezip"><?php _e( 'Theme zip file' ); ?></label> 93 <button type="button" id="upload-theme-submit" class="button"><?php _e( 'Install Now' ); ?></button> 94 </div> 95 <?php endif; ?> 96 </div> 97 </li> 98 <li id="customize-container"></li><?php // Used as a full-screen overlay transition after clicking to preview a theme. ?> 99 <li class="customize-themes-notifications"> 100 <div class="notice notice-warning customize-themes-unsaved-changes" style="display: none;"><p> 101 <?php _e( 'You have unsaved changes that will be lost if you preview a new theme.' ); ?> 102 <button type="button" id="customize-themes-save" class="button" ><?php _e( 'Save & Publish' ); ?></button> 103 </p></div> 104 </li> 105 <li id="customize-theme-section-navigation" class="wp-filter"> 106 <div class="filter-count"><span class="count theme-count">0</span></div> 107 <?php if ( current_user_can( 'install_themes' ) && ! is_multisite() ) : ?> 108 <# if ( data.description ) { #> 109 <button class="customize-help-toggle dashicons dashicons-editor-help" type="button" aria-expanded="false"><span class="screen-reader-text"><?php _e( 'Help' ); ?></span></button> 110 <div class="description customize-panel-description"> 111 {{{ data.description }}} 112 </div> 113 <# } #> 114 <div class="filter-links"></div> 115 <?php else: ?> 116 <p class="themes-filter-container"> 117 <label for="themes-filter"> 118 <span class="screen-reader-text"><?php _e( 'Search installed themes…' ); ?></span> 119 <input type="text" id="themes-filter" placeholder="<?php esc_attr_e( 'Search installed themes…' ); ?>" /> 120 </label> 121 </p> 122 <?php endif; ?> 123 </li> 124 <?php 125 } 126 } -
src/wp-includes/customize/class-wp-customize-themes-section.php
10 10 /** 11 11 * Customize Themes Section class. 12 12 * 13 * A UI container for theme controls, which behaves like a backwards Panel.13 * A UI container for theme controls, which are displayed in tabbed sections. 14 14 * 15 15 * @since 4.2.0 16 16 * … … 28 28 public $type = 'themes'; 29 29 30 30 /** 31 * Render the themes section, which behaves like a panel.31 * Theme section action. 32 32 * 33 * @since 4.2.0 33 * Defines the type of themes to load (installed, featured, latest, etc.). 34 * 35 * @since 4.7.0 36 * @access public 37 * @var string 38 */ 39 public $action = ''; 40 41 /** 42 * Get section parameters for JS. 43 * 44 * @since 4.7.0 45 * @access public 46 * @return array Exported parameters. 47 */ 48 public function json() { 49 $exported = parent::json(); 50 $exported['action'] = $this->action; 51 52 return $exported; 53 } 54 55 /** 56 * Render a themes section as a JS template. 57 * 58 * The template is only rendered by PHP once, so all actions are prepared at once on the server side. 59 * 60 * @since 4.7.0 34 61 * @access protected 35 62 */ 36 protected function render() { 37 $classes = 'accordion-section control-section control-section-' . $this->type; 63 protected function render_template() { 38 64 ?> 39 <li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="<?php echo esc_attr( $classes ); ?>"> 40 <h3 class="accordion-section-title"> 41 <?php 42 if ( $this->manager->is_theme_active() ) { 43 echo '<span class="customize-action">' . __( 'Active theme' ) . '</span> ' . $this->title; 44 } else { 45 echo '<span class="customize-action">' . __( 'Previewing theme' ) . '</span> ' . $this->title; 46 } 47 ?> 48 49 <?php if ( count( $this->controls ) > 0 ) : ?> 50 <button type="button" class="button change-theme" tabindex="0"><?php _ex( 'Change', 'theme' ); ?></button> 51 <?php endif; ?> 52 </h3> 53 <div class="customize-themes-panel control-panel-content themes-php"> 54 <h3 class="accordion-section-title customize-section-title"> 55 <span class="customize-action"><?php _e( 'Customizing' ); ?></span> 56 <?php _e( 'Themes' ); ?> 57 <span class="title-count theme-count"><?php echo count( $this->controls ) + 1 /* Active theme */; ?></span> 58 </h3> 59 <h3 class="accordion-section-title customize-section-title"> 65 <li id="accordion-section-{{ data.id }}" class="theme-section"> 66 <# if ( 'search' === data.action ) { #> 67 <div class="search-form customize-themes-section-title themes-section-search_themes"> 68 <label class="screen-reader-text" for="wp-filter-search-input">{{ data.title }}</label> 69 <input placeholder="{{ data.title }}" type="search" aria-describedby="live-search-desc" id="wp-filter-search-input" class="wp-filter-search"> 70 <span id="live-search-desc" class="screen-reader-text"><?php _e( 'The search results will be updated as you type.' ); ?></span> 71 </div> 72 <# } else { #> 73 <button type="button" class="customize-themes-section-title themes-section-{{ data.id }}">{{ data.title }}</button> 74 <# } #> 75 <div class="customize-themes-section control-section-content themes-php"> 76 <# if ( 'favorites' === data.action ) { #> 77 <div class="favorites-form"> 78 <p class="install-help"><?php _e( 'If you have marked themes as favorites on WordPress.org, you can browse them here.' ); ?></p> 79 <p> 80 <label for="wporg-username-input"><?php _e( 'Your WordPress.org username:' ); ?></label> 81 <input type="search" id="wporg-username-input" value=""> 82 <button type="button" class="button button-secondary favorites-form-submit"><?php _e( 'Get Favorites' ); ?></button> 83 </p> 84 </div> 85 <# } else if ( 'feature_filter' === data.action ) { #> 86 <div class="filter-drawer"> 87 <div class="buttons"> 88 <button type="button" class="apply-filters button button-secondary"><?php _e( 'Apply Filters' ); ?><span></span></a> 89 <button type="button" class="clear-filters button button-secondary"><?php _e( 'Clear' ); ?></a> 90 </div> 60 91 <?php 61 if ( $this->manager->is_theme_active() ) { 62 echo '<span class="customize-action">' . __( 'Active theme' ) . '</span> ' . $this->title; 63 } else { 64 echo '<span class="customize-action">' . __( 'Previewing theme' ) . '</span> ' . $this->title; 92 $feature_list = get_theme_feature_list(); 93 foreach ( $feature_list as $feature_name => $features ) { 94 echo '<fieldset class="filter-group">'; 95 $feature_name = esc_html( $feature_name ); 96 echo '<legend>' . $feature_name . '</legend>'; 97 echo '<div class="filter-group-feature">'; 98 foreach ( $features as $feature => $feature_name ) { 99 $feature = esc_attr( $feature ); 100 echo '<input type="checkbox" id="filter-id-' . $feature . '" value="' . $feature . '" /> '; 101 echo '<label for="filter-id-' . $feature . '">' . $feature_name . '</label><br>'; 102 } 103 echo '</div>'; 104 echo '</fieldset>'; 65 105 } 66 106 ?> 67 <button type="button" class="button customize-theme"><?php _e( 'Customize' ); ?></button> 68 </h3> 69 107 <div class="filtered-by"> 108 <span><?php _e( 'Filtering by:' ); ?></span> 109 <div class="tags"></div> 110 <button type="button" class="button-link"><?php _e( 'Edit' ); ?></a> 111 </div> 112 </div> 113 <# } #> 70 114 <div class="theme-overlay" tabindex="0" role="dialog" aria-label="<?php esc_attr_e( 'Theme Details' ); ?>"></div> 71 72 <div id="customize-container"></div>73 <?php if ( count( $this->controls ) > 4 ) : ?>74 <p><label for="themes-filter">75 <span class="screen-reader-text"><?php _e( 'Search installed themes…' ); ?></span>76 <input type="text" id="themes-filter" placeholder="<?php esc_attr_e( 'Search installed themes…' ); ?>" />77 </label></p>78 <?php endif; ?>79 115 <div class="theme-browser rendered"> 80 <ul class="themes accordion-section-content"> 116 <div class="error unexpected-error" style="display: none; "><p><?php _e( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the <a href="https://wordpress.org/support/">support forums</a>.' ); ?></p></div> 117 <ul class="themes"> 81 118 </ul> 119 <p class="no-themes"><?php _e( 'No themes found. Try a different search.' ); ?></p> 120 <p class="spinner"></p> 82 121 </div> 83 122 </div> 84 123 </li>