Changeset 41626
- Timestamp:
- 09/27/2017 10:24:37 PM (7 years ago)
- Location:
- trunk
- Files:
-
- 1 added
- 12 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-admin/css/customize-controls.css
r41602 r41626 25 25 } 26 26 27 #customize- header-actions .button-primary{27 #customize-save-button-wrapper { 28 28 float: right; 29 29 margin-top: 9px; 30 } 31 32 #customize-save-button-wrapper .save { 33 float: left; 34 border-radius: 3px; 35 box-shadow: none; /* @todo Adjust box shadow based on the disable states of paired button. */ 36 display: none; /* Shown when ready. */ 37 margin-top: 0; 38 } 39 #customize-save-button-wrapper .save.has-next-sibling { 40 border-radius: 3px 0 0 3px; 41 } 42 43 #customize-outer-theme-controls-wrapper { 44 position: absolute; 45 top: 0; 46 bottom: 0; 47 left: -301px; 48 visibility: hidden; 49 overflow-x: hidden; 50 overflow-y: auto; 51 width: 300px; 52 margin: 0; 53 z-index: 4; 54 background: #eee; 55 transition: left .18s; 56 border-right: 1px solid #ddd; 57 } 58 59 .outer-section-open .wp-full-overlay.expanded { 60 margin-left: 300px; 61 } 62 63 #customize-theme-controls .control-section-outer { 64 display: none !important; 65 } 66 67 #customize-outer-theme-controls .accordion-section-content { 68 padding: 12px; 69 } 70 71 #customize-outer-theme-controls .accordion-section-content.open { 72 display: block; 73 } 74 75 .outer-section-open .wp-full-overlay.expanded #customize-outer-theme-controls-wrapper { 76 visibility: visible; 77 left: 0; 78 transition: left .18s; 79 } 80 81 .customize-outer-pane-parent { 82 margin: 0; 83 } 84 85 .outer-section-open .wp-full-overlay.expanded #customize-preview { 86 opacity: 0.4; 87 } 88 89 body.outer-section-open .wp-full-overlay.expanded .wp-full-overlay-main { 90 left: 300px; 91 } 92 93 #customize-outer-theme-controls li.notice { 94 padding-top: 8px; 95 padding-bottom: 8px; 96 margin-left: 0; 97 margin-bottom: 10px; 98 } 99 100 #publish-settings { 101 text-indent: 0; 102 border-radius: 0 3px 3px 0; 103 padding-left: 0; 104 padding-right: 0; 105 box-shadow: none; /* @todo Adjust box shadow based on the disable states of paired button. */ 106 font-size: 14px; 107 width: 30px; 108 float: left; 109 display: none; /* Shown when ready. */ 110 -webkit-transform: none; 111 transform: none; 112 margin-top: 0; 30 113 } 31 114 … … 54 137 } 55 138 139 #customize-control-changeset_status label, 140 #customize-control-changeset_preview_link input { 141 background-color: #ffffff; 142 border-bottom: 1px solid #ddd; 143 box-sizing: content-box; 144 width: 100%; 145 margin-left: -12px; 146 padding-left: 12px; 147 padding-right: 12px; 148 } 149 150 #customize-controls .date-input:invalid { 151 border-color: red; 152 } 153 154 .date-time-fields .month-field { 155 width: 79px; 156 } 157 158 .date-time-fields .day-field, 159 .date-time-fields .hour-field, 160 .date-time-fields .minute-field { 161 width: 46px; 162 } 163 164 .date-time-fields .year-field { 165 width: 60px; 166 } 167 168 .date-time-fields .am-pm-field { 169 width: 53px; 170 } 171 172 #customize-control-changeset_status label { 173 padding-top: 10px; 174 padding-bottom: 10px; 175 font-weight: 500; 176 } 177 178 #customize-control-changeset_status label:first-of-type { 179 border-top: 1px solid #ddd; 180 } 181 182 #customize-control-changeset_status .customize-control-title { 183 margin-bottom: 6px; 184 } 185 186 #customize-control-changeset_status input { 187 margin-left: 0; 188 } 189 190 #customize-control-changeset_preview_link { 191 position: relative; 192 display: block; 193 } 194 195 .customize-copy-preview-link { 196 position: absolute; 197 bottom: 9px; 198 right: 0; 199 } 200 201 .customize-copy-preview-link:before, 202 .customize-copy-preview-link:after { 203 content: ''; 204 height: 28px; 205 position: absolute; 206 background: #ffffff; 207 top: -1px; 208 } 209 210 .customize-copy-preview-link:before { 211 left: -10px; 212 width: 9px; 213 opacity: 0.75; 214 } 215 216 .customize-copy-preview-link:after { 217 left: -5px; 218 width: 4px; 219 opacity: 0.8; 220 } 221 222 #customize-control-changeset_preview_link input { 223 line-height: 2.5; 224 border-top: 1px solid #ddd; 225 border-left: none; 226 border-right: none; 227 text-indent: -999px; 228 color: white; 229 } 230 231 #customize-control-changeset_preview_link label { 232 position: relative; 233 display: block; 234 } 235 236 #customize-control-changeset_preview_link a.preview-control-element { 237 display: inline-block; 238 position: absolute; 239 white-space: nowrap; 240 overflow: hidden; 241 width: 217px; 242 bottom: 14px; 243 font-size: 14px; 244 text-decoration: none; 245 } 246 247 #customize-control-changeset_preview_link a.preview-control-element.disabled, 248 #customize-control-changeset_preview_link a.preview-control-element.disabled:active, 249 #customize-control-changeset_preview_link a.preview-control-element.disabled:focus, 250 #customize-control-changeset_preview_link a.preview-control-element.disabled:visited { 251 color: black; 252 opacity: 0.4; 253 cursor: default; 254 outline: none; 255 box-shadow: none; 256 } 257 258 #sub-accordion-section-publish_settings .customize-section-description-container { 259 display: none; 260 } 261 56 262 #customize-controls .customize-info.section-meta { 57 263 margin-bottom: 15px; 264 } 265 266 .date-time-fields { 267 padding-top: 10px; 268 padding-bottom:10px; 269 } 270 271 .date-time-fields label, 272 .date-time-fields .date-time-separator { 273 float: left; 274 margin-right:5px; 275 } 276 277 .date-time-fields .date-time-separator { 278 line-height: 2; 279 } 280 281 .date-time-fields .time-row { 282 padding-top: 12px; 283 } 284 285 .date-time-fields .date-timezone { 286 float: left; 287 line-height: 2.2; 288 text-decoration: none; 289 } 290 291 #customize-control-changeset_preview_link { 292 margin-top: 20px; 293 } 294 295 #customize-control-changeset_status { 296 margin-bottom: 0; 297 padding-bottom: 0; 298 } 299 300 #customize-control-changeset_scheduled_date { 301 box-sizing: content-box; 302 width: 100%; 303 margin-left: -12px; 304 padding: 12px 12px 18px; 305 background: #ffffff; 306 border-bottom: 1px solid #ddd; 307 margin-bottom: 0; 308 } 309 310 #customize-control-changeset_scheduled_date .customize-control-description { 311 font-style: normal; 58 312 } 59 313 … … 106 360 #customize-controls .customize-pane-child .customize-section-title h3, 107 361 #customize-controls .customize-pane-child h3.customize-section-title, 362 #customize-outer-theme-controls .customize-pane-child .customize-section-title h3, 363 #customize-outer-theme-controls .customize-pane-child h3.customize-section-title, 108 364 #customize-controls .customize-info .panel-title { 109 365 font-size: 20px; … … 151 407 #customize-controls .customize-info .customize-panel-description, 152 408 #customize-controls .customize-info .customize-section-description, 409 #customize-outer-theme-controls .customize-info .customize-section-description, 153 410 #customize-controls .no-widget-areas-rendered-notice { 154 411 color: #555d66; … … 172 429 } 173 430 174 #customize-controls .customize-info .customize-section-description { 431 #customize-controls .customize-info .customize-section-description, 432 #customize-outer-theme-controls .customize-section-description { 175 433 margin-bottom: 15px; 176 434 } … … 190 448 } 191 449 192 #customize-theme-controls .control-section { 450 #customize-theme-controls .control-section, 451 #customize-outer-theme-controls .control-section { 193 452 border: none; 194 453 } 195 454 196 #customize-theme-controls .accordion-section-title { 455 #customize-theme-controls .accordion-section-title, 456 #customize-outer-theme-controls .accordion-section-title { 197 457 color: #555d66; 198 458 background-color: #fff; … … 210 470 } 211 471 212 #customize-theme-controls .accordion-section-title:after { 472 #customize-theme-controls .accordion-section-title:after, 473 #customize-outer-theme-controls .accordion-section-title:after { 213 474 content: "\f345"; 214 475 color: #a0a5aa; 215 476 } 216 477 217 #customize-theme-controls .accordion-section-content { 478 #customize-theme-controls .accordion-section-content, 479 #customize-outer-theme-controls .accordion-section-content { 218 480 color: #555d66; 219 481 background: transparent; … … 223 485 #customize-controls .control-section .accordion-section-title:hover, 224 486 #customize-controls .control-section.open .accordion-section-title, 487 #customize-outer-theme-controls .control-section .accordion-section-title:hover, 488 #customize-outer-theme-controls .control-section.open .accordion-section-title, 489 #customize-outer-theme-controls .control-section .accordion-section-title:focus, 225 490 #customize-controls .control-section .accordion-section-title:focus { 226 491 color: #0073aa; … … 243 508 #customize-theme-controls .control-section .accordion-section-title:hover:after, 244 509 #customize-theme-controls .control-section.open .accordion-section-title:after, 245 #customize-theme-controls .control-section .accordion-section-title:focus:after { 510 #customize-theme-controls .control-section .accordion-section-title:focus:after, 511 #customize-outer-theme-controls .control-section:hover > .accordion-section-title:after, 512 #customize-outer-theme-controls .control-section .accordion-section-title:hover:after, 513 #customize-outer-theme-controls .control-section.open .accordion-section-title:after, 514 #customize-outer-theme-controls .control-section .accordion-section-title:focus:after { 246 515 color: #0073aa; 247 516 } … … 251 520 } 252 521 253 #customize-theme-controls .control-section.open .accordion-section-title { 522 #customize-theme-controls .control-section.open .accordion-section-title, 523 #customize-outer-theme-controls .control-section.open .accordion-section-title { 254 524 border-bottom-color: #eee !important; 255 525 } … … 829 1099 } 830 1100 1101 .wp-full-overlay.collapsed #customize-controls #customize-notifications-area { 1102 display: none !important; 1103 } 1104 831 1105 #customize-controls #customize-notifications-area, 832 1106 #customize-controls .customize-section-title > .customize-control-notifications-container, … … 1120 1394 } 1121 1395 1122 @-webkit-keyframes dice-color-change { 1123 0% { color: #d4b146; } 1124 50% { color: #ef54b0; } 1125 75% { color: #7190d3; } 1126 100% { color: #d4b146; } 1127 } 1128 1129 @keyframes dice-color-change { 1130 0% { color: #d4b146; } 1131 50% { color: #ef54b0; } 1132 75% { color: #7190d3; } 1133 100% { color: #d4b146; } 1396 .button-see-me { 1397 -webkit-animation: bounce .7s 1; 1398 animation: bounce .7s 1; 1399 -webkit-transform-origin: center bottom; 1400 transform-origin: center bottom; 1401 } 1402 1403 @-webkit-keyframes bounce { 1404 from, 20%, 53%, 80%, to { 1405 -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); 1406 -webkit-transform: translate3d(0,0,0); 1407 } 1408 1409 40%, 43% { 1410 -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); 1411 -webkit-transform: translate3d(0, -12px, 0); 1412 } 1413 1414 70% { 1415 -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); 1416 -webkit-transform: translate3d(0, -6px, 0); 1417 } 1418 1419 90% { 1420 -webkit-transform: translate3d(0,-1px,0); 1421 } 1422 } 1423 1424 @keyframes bounce { 1425 from, 20%, 53%, 80%, to { 1426 -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); 1427 animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); 1428 -webkit-transform: translate3d(0,0,0); 1429 transform: translate3d(0,0,0); 1430 } 1431 1432 40%, 43% { 1433 -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); 1434 animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); 1435 -webkit-transform: translate3d(0, -12px, 0); 1436 transform: translate3d(0, -12px, 0); 1437 } 1438 1439 70% { 1440 -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); 1441 animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); 1442 -webkit-transform: translate3d(0, -6px, 0); 1443 transform: translate3d(0, -6px, 0); 1444 } 1445 1446 90% { 1447 -webkit-transform: translate3d(0,-1px,0); 1448 transform: translate3d(0,-1px,0); 1449 } 1134 1450 } 1135 1451 … … 1311 1627 1312 1628 #customize-controls .control-section-themes .accordion-section-title span.customize-action, 1313 #customize-controls .customize-section-title span.customize-action { 1629 #customize-controls .customize-section-title span.customize-action, 1630 #customize-outer-theme-controls .customize-section-title span.customize-action { 1314 1631 font-size: 13px; 1315 1632 display: block; … … 1844 2161 } 1845 2162 2163 .customize-control .date-time-fields select { 2164 height: 39px; 2165 } 2166 2167 .date-time-fields .month-field { 2168 width: 79px; 2169 } 2170 2171 .date-time-fields .day-field, 2172 .date-time-fields .hour-field, 2173 .date-time-fields .minute-field { 2174 width: 55px; 2175 } 2176 2177 .date-time-fields .year-field { 2178 width: 80px; 2179 } 2180 2181 .date-time-fields .date-timezone { 2182 line-height: 3.2; 2183 } 1846 2184 .wp-core-ui.wp-customizer .button { 1847 2185 margin-top: 12px; … … 1854 2192 } 1855 2193 1856 .wp-full-overlay.expanded { 2194 .wp-full-overlay.expanded, 2195 .outer-section-open .wp-full-overlay.expanded { 1857 2196 margin-left: 0; 1858 2197 } … … 1932 2271 } 1933 2272 1934 #customize-header-actions .button-primary { 1935 margin-top: 6px; 2273 #publish-settings { 2274 height: 31px; 2275 } 2276 2277 #customize-control-changeset_status label { 2278 padding-top: 15px; 1936 2279 } 1937 2280 1938 2281 body.adding-widget div#available-widgets, 1939 body.adding-menu-items div#available-menu-items { 2282 body.adding-menu-items div#available-menu-items, 2283 body.outer-section-open div#customize-outer-theme-controls-wrapper { 1940 2284 top: 46px; 1941 2285 left: 0; -
trunk/src/wp-admin/customize.php
r41374 r41626 28 28 29 29 if ( $wp_customize->changeset_post_id() ) { 30 if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $wp_customize->changeset_post_id() ) ) { 30 $changeset_post = get_post( $wp_customize->changeset_post_id() ); 31 32 if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $changeset_post->ID ) ) { 31 33 wp_die( 32 34 '<h1>' . __( 'Cheatin’ uh?' ) . '</h1>' . … … 35 37 ); 36 38 } 37 if ( in_array( get_post_status( $wp_customize->changeset_post_id() ), array( 'publish', 'trash' ), true ) ) { 39 40 $missed_schedule = ( 41 'future' === $changeset_post->post_status && 42 get_post_time( 'G', true, $changeset_post ) < time() 43 ); 44 if ( $missed_schedule ) { 45 wp_publish_post( $changeset_post->ID ); 46 wp_die( 47 '<h1>' . __( 'Your scheduled changes just published' ) . '</h1>' . 48 '<p><a href="' . esc_url( remove_query_arg( 'changeset_uuid' ) ) . '">' . __( 'Customize New Changes' ) . '</a></p>', 49 200 50 ); 51 } 52 53 if ( in_array( get_post_status( $changeset_post->ID ), array( 'publish', 'trash' ), true ) ) { 38 54 wp_die( 39 55 '<h1>' . __( 'Cheatin’ uh?' ) . '</h1>' . … … 133 149 <form id="customize-controls" class="wrap wp-full-overlay-sidebar"> 134 150 <div id="customize-header-actions" class="wp-full-overlay-header"> 135 <?php 136 $save_text = $wp_customize->is_theme_active() ? __( 'Save & Publish' ) : __( 'Save & Activate' ); 137 $save_attrs = array(); 138 if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->publish_posts ) ) { 139 $save_attrs['style'] = 'display: none'; 140 } 141 submit_button( $save_text, 'primary save', 'save', false, $save_attrs ); 142 ?> 151 <?php $save_text = $wp_customize->is_theme_active() ? __( 'Publish' ) : __( 'Activate & Publish' ); ?> 152 <div id="customize-save-button-wrapper" class="customize-save-button-wrapper" > 153 <?php submit_button( $save_text, 'primary save', 'save', false ); ?> 154 <button id="publish-settings" class="publish-settings button-primary button dashicons dashicons-admin-generic" aria-label="<?php esc_attr_e( 'Publish Settings' ); ?>" aria-expanded="false" disabled></button> 155 </div> 143 156 <span class="spinner"></span> 144 157 <button type="button" class="customize-controls-preview-toggle"> … … 204 217 </form> 205 218 <div id="customize-preview" class="wp-full-overlay-main"></div> 219 <div id="customize-sidebar-outer-content"> 220 <div id="customize-outer-theme-controls-wrapper"> 221 <div id="customize-outer-theme-controls"> 222 <ul class="customize-outer-pane-parent"><?php // Outer panel and sections are not implemented, but its here as a placeholder to avoid any side-effect in api.Section. ?></ul> 223 </div> 224 </div> 225 </div> 206 226 <?php 207 227 -
trunk/src/wp-admin/js/customize-controls.js
r41603 r41626 360 360 * @param {object} [args] - Additional options for the save request. 361 361 * @param {boolean} [args.autosave=false] - Whether changes will be stored in autosave revision if the changeset has been promoted from an auto-draft. 362 * @param {boolean} [args.force=false] - Send request to update even when there are no changes to submit. This can be used to request the latest status of the changeset on the server. 362 363 * @param {string} [args.title] - Title to update in the changeset. Optional. 363 364 * @param {string} [args.date] - Date to update in the changeset. Optional. … … 371 372 title: null, 372 373 date: null, 373 autosave: false 374 autosave: false, 375 force: false 374 376 }, args ); 375 377 … … 389 391 } ); 390 392 393 // Allow plugins to attach additional params to the settings. 394 api.trigger( 'changeset-save', submittedChanges, submittedArgs ); 395 391 396 // Short-circuit when there are no pending changes. 392 if ( _.isEmpty( submittedChanges ) && null === submittedArgs.title && null === submittedArgs.date ) {397 if ( ! submittedArgs.force && _.isEmpty( submittedChanges ) && null === submittedArgs.title && null === submittedArgs.date ) { 393 398 deferred.resolve( {} ); 394 399 return deferred.promise(); 395 400 } 396 397 // Allow plugins to attach additional params to the settings.398 api.trigger( 'changeset-save', submittedChanges, submittedArgs );399 401 400 402 // A status would cause a revision to be made, and for this wp.customize.previewer.save() should be used. Status is also disallowed for revisions regardless. … … 441 443 442 444 api.state( 'changesetStatus' ).set( data.changeset_status ); 445 446 if ( data.changeset_date ) { 447 api.state( 'changesetDate' ).set( data.changeset_date ); 448 } 449 443 450 deferred.resolve( data ); 444 451 api.trigger( 'changeset-saved', data ); … … 583 590 ); 584 591 return equal; 592 }; 593 594 /** 595 * Get current timestamp adjusted for server clock time. 596 * 597 * Same functionality as the `current_time( 'mysql', false )` function in PHP. 598 * 599 * @since 4.9.0 600 * 601 * @returns {int} Current timestamp. 602 */ 603 api.utils.getCurrentTimestamp = function getCurrentTimestamp() { 604 var currentDate, currentClientTimestamp, timestampDifferential; 605 currentClientTimestamp = _.now(); 606 currentDate = new Date( api.settings.initialServerDate.replace( /-/g, '/' ) ); 607 timestampDifferential = currentClientTimestamp - api.settings.initialClientTimestamp; 608 timestampDifferential += api.settings.initialClientTimestamp - api.settings.initialServerTimestamp; 609 currentDate.setTime( currentDate.getTime() + timestampDifferential ); 610 return currentDate.getTime(); 611 }; 612 613 /** 614 * Get remaining time of when the date is set. 615 * 616 * @since 4.9.0 617 * 618 * @param {string|int|Date} datetime - Date time or timestamp of the future date. 619 * @return {int} remainingTime - Remaining time in milliseconds. 620 */ 621 api.utils.getRemainingTime = function getRemainingTime( datetime ) { 622 var millisecondsDivider = 1000, remainingTime, timestamp; 623 if ( datetime instanceof Date ) { 624 timestamp = datetime.getTime(); 625 } else if ( 'string' === typeof datetime ) { 626 timestamp = ( new Date( datetime.replace( /-/g, '/' ) ) ).getTime(); 627 } else { 628 timestamp = datetime; 629 } 630 631 remainingTime = timestamp - api.utils.getCurrentTimestamp(); 632 remainingTime = Math.ceil( remainingTime / millisecondsDivider ); 633 return remainingTime; 585 634 }; 586 635 … … 1081 1130 api.Section = Container.extend({ 1082 1131 containerType: 'section', 1132 containerParent: '#customize-theme-controls', 1133 containerPaneParent: '.customize-pane-parent', 1083 1134 defaults: { 1084 1135 title: '', … … 1133 1184 embed: function () { 1134 1185 var inject, 1135 section = this, 1136 container = $( '#customize-theme-controls' ); 1186 section = this; 1187 1188 section.containerParent = api.ensure( section.containerParent ); 1137 1189 1138 1190 // Watch for changes to the panel state … … 1149 1201 } 1150 1202 if ( ! section.contentContainer.parent().is( section.headContainer ) ) { 1151 container.append( section.contentContainer );1203 section.containerParent.append( section.contentContainer ); 1152 1204 } 1153 1205 section.deferred.embedded.resolve(); … … 1156 1208 } else { 1157 1209 // There is no panel, so embed the section in the root of the customizer 1158 parentContainer = $( '.customize-pane-parent' ); // @todo This should be defined elsewhere, and to be configurable1210 parentContainer = api.ensure( section.containerPaneParent ); 1159 1211 if ( ! section.headContainer.parent().is( parentContainer ) ) { 1160 1212 parentContainer.append( section.headContainer ); 1161 1213 } 1162 1214 if ( ! section.contentContainer.parent().is( section.headContainer ) ) { 1163 container.append( section.contentContainer );1215 section.containerParent.append( section.contentContainer ); 1164 1216 } 1165 1217 section.deferred.embedded.resolve(); … … 1298 1350 }); 1299 1351 } else { 1300 api.panel.each( function( panel ) { 1301 panel.collapse(); 1302 }); 1352 if ( ! args.allowMultiple ) { 1353 api.panel.each( function( panel ) { 1354 panel.collapse(); 1355 }); 1356 } 1303 1357 expand(); 1304 1358 } … … 1837 1891 } 1838 1892 }); 1893 } 1894 }); 1895 1896 /** 1897 * Class wp.customize.OuterSection. 1898 * 1899 * Creates section outside of the sidebar, there is no ui to trigger collapse/expand so 1900 * it would require custom handling. 1901 * 1902 * @since 4.9 1903 * 1904 * @constructor 1905 * @augments wp.customize.Section 1906 * @augments wp.customize.Container 1907 */ 1908 api.OuterSection = api.Section.extend({ 1909 1910 /** 1911 * Initialize. 1912 * 1913 * @since 4.9.0 1914 * 1915 * @returns {void} 1916 */ 1917 initialize: function() { 1918 var section = this; 1919 section.containerParent = '#customize-outer-theme-controls'; 1920 section.containerPaneParent = '.customize-outer-pane-parent'; 1921 return api.Section.prototype.initialize.apply( section, arguments ); 1922 }, 1923 1924 /** 1925 * Overrides api.Section.prototype.onChangeExpanded to prevent collapse/expand effect 1926 * on other sections and panels. 1927 * 1928 * @since 4.9.0 1929 * 1930 * @param {Boolean} expanded - The expanded state to transition to. 1931 * @param {Object} [args] - Args. 1932 * @param {boolean} [args.unchanged] - Whether the state is already known to not be changed, and so short-circuit with calling completeCallback early. 1933 * @param {Function} [args.completeCallback] - Function to call when the slideUp/slideDown has completed. 1934 * @param {Object} [args.duration] - The duration for the animation. 1935 */ 1936 onChangeExpanded: function( expanded, args ) { 1937 var section = this, 1938 container = section.headContainer.closest( '.wp-full-overlay-sidebar-content' ), 1939 content = section.contentContainer, 1940 backBtn = content.find( '.customize-section-back' ), 1941 sectionTitle = section.headContainer.find( '.accordion-section-title' ).first(), 1942 body = $( 'body' ), 1943 expand, panel; 1944 1945 body.toggleClass( 'outer-section-open', expanded ); 1946 section.container.toggleClass( 'open', expanded ); 1947 section.container.removeClass( 'busy' ); 1948 api.section.each( function( _section ) { 1949 if ( 'outer' === _section.params.type && _section.id !== section.id ) { 1950 _section.container.removeClass( 'open' ); 1951 } 1952 } ); 1953 1954 if ( expanded && ! content.hasClass( 'open' ) ) { 1955 1956 if ( args.unchanged ) { 1957 expand = args.completeCallback; 1958 } else { 1959 expand = $.proxy( function() { 1960 section._animateChangeExpanded( function() { 1961 sectionTitle.attr( 'tabindex', '-1' ); 1962 backBtn.attr( 'tabindex', '0' ); 1963 1964 backBtn.focus(); 1965 content.css( 'top', '' ); 1966 container.scrollTop( 0 ); 1967 1968 if ( args.completeCallback ) { 1969 args.completeCallback(); 1970 } 1971 } ); 1972 1973 content.addClass( 'open' ); 1974 }, this ); 1975 } 1976 1977 if ( section.panel() ) { 1978 api.panel( section.panel() ).expand({ 1979 duration: args.duration, 1980 completeCallback: expand 1981 }); 1982 } else { 1983 expand(); 1984 } 1985 1986 } else if ( ! expanded && content.hasClass( 'open' ) ) { 1987 if ( section.panel() ) { 1988 panel = api.panel( section.panel() ); 1989 if ( panel.contentContainer.hasClass( 'skip-transition' ) ) { 1990 panel.collapse(); 1991 } 1992 } 1993 section._animateChangeExpanded( function() { 1994 backBtn.attr( 'tabindex', '-1' ); 1995 sectionTitle.attr( 'tabindex', '0' ); 1996 1997 sectionTitle.focus(); 1998 content.css( 'top', '' ); 1999 2000 if ( args.completeCallback ) { 2001 args.completeCallback(); 2002 } 2003 } ); 2004 2005 content.removeClass( 'open' ); 2006 2007 } else { 2008 if ( args.completeCallback ) { 2009 args.completeCallback(); 2010 } 2011 } 1839 2012 } 1840 2013 }); … … 3961 4134 }); 3962 4135 4136 /** 4137 * Class wp.customize.DateTimeControl. 4138 * 4139 * @since 4.9.0 4140 * @constructor 4141 * @augments wp.customize.Control 4142 * @augments wp.customize.Class 4143 */ 4144 api.DateTimeControl = api.Control.extend({ 4145 4146 dateInputs: {}, 4147 inputElements: {}, 4148 invalidDate: false, 4149 4150 /** 4151 * Initialize behaviors. 4152 * 4153 * @since 4.9.0 4154 * @returns {void} 4155 */ 4156 ready: function ready() { 4157 var control = this; 4158 4159 _.bindAll( control, 'populateSetting', 'updateDaysForMonth', 'updateMinutesForHour' ); 4160 4161 control.dateInputs = control.container.find( '.date-input' ); 4162 4163 // @todo This needs https://core.trac.wordpress.org/ticket/37964 4164 if ( ! control.setting ) { 4165 control.setting = new api.Value(); 4166 } 4167 4168 if ( ! control.setting.get() && control.params.defaultValue ) { 4169 control.setting.set( control.params.defaultValue ); 4170 } 4171 4172 control.dateInputs.each( function() { 4173 var input = $( this ), component, element; 4174 component = input.data( 'component' ); 4175 element = new api.Element( input ); 4176 element.validate = function( value ) { 4177 return _.contains( [ 'am', 'pm' ], value ) ? value : parseInt( value, 10 ); 4178 }; 4179 control.inputElements[ component ] = element; 4180 control.elements.push( element ); 4181 } ); 4182 4183 control.dateInputs.on( 'input', control.populateSetting ); 4184 control.inputElements.month.bind( control.updateDaysForMonth ); 4185 control.inputElements.year.bind( control.updateDaysForMonth ); 4186 control.inputElements.hour.bind( control.updateMinutesForHour ); 4187 control.populateDateInputs(); 4188 }, 4189 4190 /** 4191 * Parse datetime string. 4192 * 4193 * @since 4.9.0 4194 * @param {string} datetime Date/Time string. Accepts Y-m-d H:i:s format. 4195 * @param {boolean} twelveHourFormat If twelve hour format array is required. 4196 * @returns {object|null} Returns object containing date components or null if parse error. 4197 */ 4198 parseDateTime: function parseDateTime( datetime, twelveHourFormat ) { 4199 var matches, date, midDayHour = 12; 4200 4201 if ( datetime ) { 4202 matches = datetime.match( /^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/ ); 4203 } 4204 4205 if ( ! matches ) { 4206 return null; 4207 } 4208 4209 matches.shift(); 4210 4211 date = { 4212 year: matches.shift(), 4213 month: matches.shift(), 4214 day: matches.shift(), 4215 hour: matches.shift(), 4216 minute: matches.shift(), 4217 second: matches.shift() 4218 }; 4219 4220 if ( twelveHourFormat ) { 4221 date.hour = parseInt( date.hour, 10 ); 4222 date.ampm = date.hour >= midDayHour ? 'pm' : 'am'; 4223 date.hour = date.hour % midDayHour ? String( date.hour % midDayHour ) : String( midDayHour ); 4224 delete date.second; 4225 } 4226 4227 return date; 4228 }, 4229 4230 /** 4231 * Validates if input components have valid date and time. 4232 * 4233 * @since 4.9.0 4234 * @return {boolean} If date input fields has error. 4235 */ 4236 validateInputs: function validateInputs() { 4237 var control = this, errorMessage; 4238 4239 control.invalidDate = false; 4240 4241 _.each( [ 'day', 'hour', 'year', 'minute' ], function( component ) { 4242 var element, el, max, min, maxLength, value; 4243 4244 if ( ! control.invalidDate ) { 4245 element = control.inputElements[ component ]; 4246 el = element.element.get( 0 ); 4247 max = parseInt( element.element.attr( 'max' ), 10 ); 4248 min = parseInt( element.element.attr( 'min' ), 10 ); 4249 maxLength = parseInt( element.element.attr( 'maxlength' ), 10 ); 4250 value = element(); 4251 control.invalidDate = value > max || value < min || String( value ).length > maxLength; 4252 errorMessage = control.invalidDate ? api.l10n.invalid + ' ' + component : ''; 4253 4254 el.setCustomValidity( errorMessage ); 4255 _.result( el, 'reportValidity' ); 4256 } 4257 } ); 4258 4259 return control.invalidDate; 4260 }, 4261 4262 /** 4263 * Updates number of days according to the month and year selected. 4264 * 4265 * @since 4.9.0 4266 * @return {void} 4267 */ 4268 updateDaysForMonth: function updateDaysForMonth() { 4269 var control = this, daysInMonth, year, month, day; 4270 4271 month = control.inputElements.month(); 4272 year = control.inputElements.year(); 4273 day = control.inputElements.day(); 4274 4275 if ( month && year ) { 4276 daysInMonth = new Date( year, month, 0 ).getDate(); 4277 control.inputElements.day.element.attr( 'max', daysInMonth ); 4278 4279 if ( day > daysInMonth ) { 4280 control.inputElements.day( daysInMonth ); 4281 } 4282 } 4283 }, 4284 4285 /** 4286 * Updates number of minutes according to the hour selected. 4287 * 4288 * @since 4.9.0 4289 * @return {void} 4290 */ 4291 updateMinutesForHour: function updateMinutesForHour() { 4292 var control = this, maxHours = 24, minuteEl; 4293 4294 if ( control.inputElements.ampm ) { 4295 return; 4296 } 4297 4298 minuteEl = control.inputElements.minute.element; 4299 4300 if ( maxHours === control.inputElements.hour() ) { 4301 control.inputElements.minute( 0 ); 4302 minuteEl.data( 'default-max', minuteEl.attr( 'max' ) ); 4303 minuteEl.data( 'default-maxlength', minuteEl.attr( 'maxlength' ) ); 4304 minuteEl.attr( 'max', '0' ); 4305 } else if ( minuteEl.data( 'default-max' ) ) { 4306 minuteEl.attr( 'max', minuteEl.data( 'default-max' ) ); 4307 minuteEl.attr( 'maxlength', minuteEl.data( 'maxlength' ) ); 4308 } 4309 }, 4310 4311 /** 4312 * Populate setting value from the inputs. 4313 * 4314 * @since 4.9.0 4315 * @returns {boolean} If setting updated. 4316 */ 4317 populateSetting: function populateSetting() { 4318 var control = this, date; 4319 4320 if ( control.validateInputs() || ! control.params.allowPastDate && ! control.isFutureDate() ) { 4321 return false; 4322 } 4323 4324 date = control.convertInputDateToString(); 4325 control.setting.set( date ); 4326 return true; 4327 }, 4328 4329 /** 4330 * Converts input values to string in Y-m-d H:i:s format. 4331 * 4332 * @since 4.9.0 4333 * @return {string} Date string. 4334 */ 4335 convertInputDateToString: function convertInputDateToString() { 4336 var control = this, date = '', dateFormat, hourInTwentyFourHourFormat, 4337 getElementValue, pad; 4338 4339 pad = function( number, padding ) { 4340 var zeros; 4341 if ( String( number ).length < padding ) { 4342 zeros = padding - String( number ).length; 4343 number = Math.pow( 10, zeros ).toString().substr( 1 ) + String( number ); 4344 } 4345 return number; 4346 }; 4347 4348 getElementValue = function( component ) { 4349 var value = control.inputElements[ component ].get(); 4350 4351 if ( _.contains( [ 'month', 'day', 'hour', 'minute' ], component ) ) { 4352 value = pad( value, 2 ); 4353 } else if ( 'year' === component ) { 4354 value = pad( value, 4 ); 4355 } 4356 return value; 4357 }; 4358 4359 hourInTwentyFourHourFormat = control.inputElements.ampm ? control.convertHourToTwentyFourHourFormat( control.inputElements.hour(), control.inputElements.ampm() ) : control.inputElements.hour(); 4360 dateFormat = [ 'year', '-', 'month', '-', 'day', ' ', pad( hourInTwentyFourHourFormat, 2 ), ':', 'minute', ':', '00' ]; 4361 4362 _.each( dateFormat, function( component ) { 4363 date += control.inputElements[ component ] ? getElementValue( component ) : component; 4364 } ); 4365 4366 return date; 4367 }, 4368 4369 /** 4370 * Check if the date is in the future. 4371 * 4372 * @since 4.9.0 4373 * @returns {boolean} True if future date. 4374 */ 4375 isFutureDate: function isFutureDate() { 4376 var control = this; 4377 return 0 < api.utils.getRemainingTime( control.convertInputDateToString() ); 4378 }, 4379 4380 /** 4381 * Convert hour in twelve hour format to twenty four hour format. 4382 * 4383 * @since 4.9.0 4384 * @param {string} hourInTwelveHourFormat Hour in twelve hour format. 4385 * @param {string} ampm am/pm 4386 * @return {string} Hour in twenty four hour format. 4387 */ 4388 convertHourToTwentyFourHourFormat: function convertHour( hourInTwelveHourFormat, ampm ) { 4389 var hourInTwentyFourHourFormat, hour, midDayHour = 12; 4390 4391 hour = parseInt( hourInTwelveHourFormat, 10 ); 4392 4393 if ( 'pm' === ampm && hour < midDayHour ) { 4394 hourInTwentyFourHourFormat = hour + midDayHour; 4395 } else if ( 'am' === ampm && midDayHour === hour ) { 4396 hourInTwentyFourHourFormat = hour - midDayHour; 4397 } else { 4398 hourInTwentyFourHourFormat = hour; 4399 } 4400 4401 return String( hourInTwentyFourHourFormat ); 4402 }, 4403 4404 /** 4405 * Populates date inputs in date fields. 4406 * 4407 * @since 4.9.0 4408 * @returns {boolean} Whether the inputs were populated. 4409 */ 4410 populateDateInputs: function populateDateInputs() { 4411 var control = this, parsed; 4412 4413 parsed = control.parseDateTime( control.setting.get(), control.params.twelveHourFormat ); 4414 4415 if ( ! parsed ) { 4416 return false; 4417 } 4418 4419 _.each( control.inputElements, function( element, component ) { 4420 element.set( parsed[ component ] ); 4421 } ); 4422 4423 return true; 4424 }, 4425 4426 /** 4427 * Toggle future date notification for date control. 4428 * 4429 * @since 4.9.0 4430 * @param {boolean} notify Add or remove the notification. 4431 * @return {wp.customize.DateTimeControl} 4432 */ 4433 toggleFutureDateNotification: function toggleFutureDateNotification( notify ) { 4434 var control = this, notificationCode, notification; 4435 4436 notificationCode = 'not_future_date'; 4437 4438 if ( notify ) { 4439 notification = new api.Notification( notificationCode, { 4440 type: 'error', 4441 message: api.l10n.futureDateError 4442 } ); 4443 control.notifications.add( notificationCode, notification ); 4444 } else { 4445 control.notifications.remove( notificationCode ); 4446 } 4447 4448 return control; 4449 } 4450 }); 4451 4452 /** 4453 * Class PreviewLinkControl. 4454 * 4455 * @since 4.9.0 4456 * @constructor 4457 * @augments wp.customize.Control 4458 * @augments wp.customize.Class 4459 */ 4460 api.PreviewLinkControl = api.Control.extend({ 4461 4462 previewElements: {}, 4463 4464 /** 4465 * Override the templateSelector before embedding the control into the page. 4466 * 4467 * @since 4.9.0 4468 * @return {void} 4469 */ 4470 embed: function() { 4471 var control = this; 4472 control.templateSelector = 'customize-preview-link-control'; 4473 return api.Control.prototype.embed.apply( control, arguments ); 4474 }, 4475 4476 /** 4477 * Initialize behaviors. 4478 * 4479 * @since 4.9.0 4480 * @returns {void} 4481 */ 4482 ready: function ready() { 4483 var control = this, element, component, node, link, input, button; 4484 4485 _.bindAll( control, 'updatePreviewLink' ); 4486 4487 if ( ! control.setting ) { 4488 control.setting = new api.Value(); 4489 } 4490 4491 control.container.find( '.preview-control-element' ).each( function() { 4492 node = $( this ); 4493 component = node.data( 'component' ); 4494 element = new api.Element( node ); 4495 control.previewElements[ component ] = element; 4496 control.elements.push( element ); 4497 } ); 4498 4499 link = control.previewElements.link; 4500 input = control.previewElements.input; 4501 button = control.previewElements.button; 4502 4503 input.link( control.setting ); 4504 link.link( control.setting ); 4505 4506 link.bind( function( value ) { 4507 link.element.attr( 'href', value ); 4508 link.element.attr( 'target', api.settings.changeset.uuid ); 4509 } ); 4510 4511 api.bind( 'ready', control.updatePreviewLink ); 4512 api.bind( 'change', control.updatePreviewLink ); 4513 api.state( 'saved' ).bind( control.updatePreviewLink ); 4514 4515 button.element.on( 'click', function( event ) { 4516 event.preventDefault(); 4517 if ( control.setting() ) { 4518 input.element.select(); 4519 document.execCommand( 'copy' ); 4520 button( button.element.data( 'copied-text' ) ); 4521 } 4522 } ); 4523 4524 link.element.on( 'click', function( event ) { 4525 if ( link.element.hasClass( 'disabled' ) ) { 4526 event.preventDefault(); 4527 } 4528 } ); 4529 4530 button.element.on( 'mouseenter', function() { 4531 if ( control.setting() ) { 4532 button( button.element.data( 'copy-text' ) ); 4533 } 4534 } ); 4535 }, 4536 4537 /** 4538 * Updates Preview Link 4539 * 4540 * @since 4.9.0 4541 * @return {void} 4542 */ 4543 updatePreviewLink: function updatePreviewLink() { 4544 var control = this, unsavedDirtyValues; 4545 4546 unsavedDirtyValues = ! _.isEmpty( api.dirtyValues( { 4547 unsaved: true 4548 } ) ); 4549 4550 control.toggleSaveNotification( unsavedDirtyValues ); 4551 control.previewElements.link.element.toggleClass( 'disabled', unsavedDirtyValues ); 4552 control.previewElements.button.element.prop( 'disabled', unsavedDirtyValues ); 4553 control.setting.set( api.previewer.getFrontendPreviewUrl() ); 4554 }, 4555 4556 /** 4557 * Toggles save notification. 4558 * 4559 * @since 4.9.0 4560 * @param {boolean} notify Add or remove notification. 4561 * @return {void} 4562 */ 4563 toggleSaveNotification: function toggleSaveNotification( notify ) { 4564 var control = this, notificationCode, notification; 4565 4566 notificationCode = 'changes_not_saved'; 4567 4568 if ( notify ) { 4569 notification = new api.Notification( notificationCode, { 4570 type: 'info', 4571 message: api.l10n.saveBeforeShare 4572 } ); 4573 control.notifications.add( notificationCode, notification ); 4574 } else { 4575 control.notifications.remove( notificationCode ); 4576 } 4577 } 4578 }); 4579 3963 4580 // Change objects contained within the main customize object to Settings. 3964 4581 api.defaultConstructor = api.Setting; … … 4060 4677 } 4061 4678 ); 4062 if ( ! api.state( 'saved' ).get() ) {4679 if ( api.settings.changeset.autosaved || ! api.state( 'saved' ).get() ) { 4063 4680 params.customize_autosaved = 'on'; 4064 4681 } … … 4661 5278 background_position: api.BackgroundPositionControl, 4662 5279 theme: api.ThemeControl, 5280 date_time: api.DateTimeControl, 4663 5281 code_editor: api.CodeEditorControl 4664 5282 }; 4665 5283 api.panelConstructor = {}; 4666 5284 api.sectionConstructor = { 4667 themes: api.ThemesSection 5285 themes: api.ThemesSection, 5286 outer: api.OuterSection 4668 5287 }; 4669 5288 … … 4837 5456 }, api ); 4838 5457 5458 // Define state values. 5459 api.state = new api.Values(); 5460 _.each( [ 5461 'saved', 5462 'autosaved', 5463 'saving', 5464 'activated', 5465 'processing', 5466 'paneVisible', 5467 'expandedPanel', 5468 'expandedSection', 5469 'changesetDate', 5470 'selectedChangesetDate', 5471 'changesetStatus', 5472 'selectedChangesetStatus', 5473 'remainingTimeToPublish', 5474 'previewerAlive', 5475 'editShortcutVisibility' 5476 ], function( name ) { 5477 api.state.create( name ); 5478 }); 5479 4839 5480 $( function() { 4840 5481 api.settings = window._wpCustomizeSettings; … … 4864 5505 closeBtn = $( '.customize-controls-close' ), 4865 5506 saveBtn = $( '#save' ), 5507 btnWrapper = $( '#customize-save-button-wrapper' ), 5508 publishSettingsBtn = $( '#publish-settings' ), 4866 5509 footerActions = $( '#customize-footer-actions' ); 5510 5511 saveBtn.show(); 5512 5513 api.section( 'publish_settings', function( section ) { 5514 var updateButtonsState, previewLinkControl, previewLinkControlId = 'changeset_preview_link'; 5515 5516 previewLinkControl = new api.PreviewLinkControl( previewLinkControlId, { 5517 params: { 5518 section: section.id, 5519 active: true, 5520 priority: 100, 5521 content: '<li id="customize-control-' + previewLinkControlId + '" class="customize-control"></li>' 5522 } 5523 } ); 5524 5525 api.control.add( previewLinkControlId, previewLinkControl ); 5526 5527 // Make sure publish settings are not available until the theme has been activated. 5528 if ( ! api.settings.theme.active ) { 5529 section.active.set( false ); 5530 section.active.link( api.state( 'activated' ) ); 5531 } 5532 5533 // Bind visibility of the publish settings button to whether the section is active. 5534 updateButtonsState = function() { 5535 publishSettingsBtn.toggle( section.active.get() ); 5536 saveBtn.toggleClass( 'has-next-sibling', section.active.get() ); 5537 }; 5538 updateButtonsState(); 5539 section.active.bind( updateButtonsState ); 5540 5541 section.contentContainer.find( '.customize-action' ).text( api.l10n.updating ); 5542 section.contentContainer.find( '.customize-section-back' ).removeAttr( 'tabindex' ); 5543 publishSettingsBtn.prop( 'disabled', false ); 5544 5545 publishSettingsBtn.on( 'click', function( event ) { 5546 event.preventDefault(); 5547 section.expanded.set( ! section.expanded.get() ); 5548 } ); 5549 5550 section.expanded.bind( function( isExpanded ) { 5551 publishSettingsBtn.attr( 'aria-expanded', String( isExpanded ) ); 5552 publishSettingsBtn.toggleClass( 'active', isExpanded ); 5553 } ); 5554 5555 api.state( 'changesetStatus' ).bind( function( status ) { 5556 if ( 'publish' === status ) { 5557 section.collapse(); 5558 } 5559 } ); 5560 } ); 4867 5561 4868 5562 // Prevent the form from saving when enter is pressed on an input or select element. … … 4924 5618 customize_changeset_uuid: api.settings.changeset.uuid 4925 5619 }; 4926 if ( ! api.state( 'saved' ).get() ) {5620 if ( api.settings.changeset.autosaved || ! api.state( 'saved' ).get() ) { 4927 5621 queryVars.customize_autosaved = 'on'; 4928 5622 } … … 4960 5654 var previewer = this, 4961 5655 deferred = $.Deferred(), 4962 changesetStatus = 'publish', 5656 changesetStatus = api.state( 'selectedChangesetStatus' ).get(), 5657 selectedChangesetDate = api.state( 'selectedChangesetDate' ).get(), 4963 5658 processing = api.state( 'processing' ), 4964 5659 submitWhenDoneProcessing, … … 4966 5661 modifiedWhileSaving = {}, 4967 5662 invalidSettings = [], 4968 invalidControls; 5663 invalidControls = [], 5664 invalidSettingLessControls = []; 4969 5665 4970 5666 if ( args && args.status ) { … … 5005 5701 } ); 5006 5702 } ); 5007 invalidControls = api.findControlsForSettings( invalidSettings ); 5703 5704 /** 5705 * Find all invalid setting less controls with notification type error. 5706 */ 5707 api.control.each( function( control ) { 5708 if ( ! control.setting || ! control.setting.id && control.active.get() ) { 5709 control.notifications.each( function( notification ) { 5710 if ( 'error' === notification.type ) { 5711 invalidSettingLessControls.push( [ control ] ); 5712 } 5713 } ); 5714 } 5715 } ); 5716 5717 invalidControls = _.union( invalidSettingLessControls, _.values( api.findControlsForSettings( invalidSettings ) ) ); 5008 5718 if ( ! _.isEmpty( invalidControls ) ) { 5009 _.values( invalidControls )[0][0].focus(); 5719 5720 invalidControls[0][0].focus(); 5010 5721 api.unbind( 'change', captureSettingModifiedDuringSave ); 5011 5722 5012 api.notifications.add( errorCode, new api.Notification( errorCode, { 5013 message: ( 1 === invalidSettings.length ? api.l10n.saveBlockedError.singular : api.l10n.saveBlockedError.plural ).replace( /%s/g, String( invalidSettings.length ) ), 5014 type: 'error', 5015 dismissible: true, 5016 saveFailure: true 5017 } ) ); 5723 if ( invalidSettings.length ) { 5724 api.notifications.add( errorCode, new api.Notification( errorCode, { 5725 message: ( 1 === invalidSettings.length ? api.l10n.saveBlockedError.singular : api.l10n.saveBlockedError.plural ).replace( /%s/g, String( invalidSettings.length ) ), 5726 type: 'error', 5727 dismissible: true, 5728 saveFailure: true 5729 } ) ); 5730 } 5018 5731 5019 5732 deferred.rejectWith( previewer, [ … … 5032 5745 customize_changeset_status: changesetStatus 5033 5746 } ); 5747 5034 5748 if ( args && args.date ) { 5035 5749 query.customize_changeset_date = args.date; 5750 } else if ( 'future' === changesetStatus && selectedChangesetDate ) { 5751 query.customize_changeset_date = selectedChangesetDate; 5036 5752 } 5753 5037 5754 if ( args && args.title ) { 5038 5755 query.customize_changeset_title = args.title; … … 5071 5788 5072 5789 request.fail( function ( response ) { 5790 var notification, notificationArgs; 5791 notificationArgs = { 5792 type: 'error', 5793 dismissible: true, 5794 fromServer: true, 5795 saveFailure: true 5796 }; 5073 5797 5074 5798 if ( '0' === response ) { … … 5088 5812 } ); 5089 5813 } else if ( response.code ) { 5090 api.notifications.add( response.code, new api.Notification( response.code, { 5091 message: response.message, 5092 type: 'error', 5093 dismissible: true, 5094 fromServer: true, 5095 saveFailure: true 5814 if ( 'not_future_date' === response.code && api.section.has( 'publish_settings' ) && api.section( 'publish_settings' ).active.get() && api.control.has( 'changeset_scheduled_date' ) ) { 5815 api.control( 'changeset_scheduled_date' ).toggleFutureDateNotification( true ).focus(); 5816 } else { 5817 notification = new api.Notification( response.code, _.extend( notificationArgs, { 5818 message: response.message 5819 } ) ); 5820 } 5821 } else { 5822 notification = new api.Notification( 'unknown_error', _.extend( notificationArgs, { 5823 message: api.l10n.serverSaveError 5096 5824 } ) ); 5097 } else { 5098 api.notifications.add( 'unknown_error', new api.Notification( 'unknown_error', { 5099 message: api.l10n.serverSaveError, 5100 type: 'error', 5101 dismissible: true, 5102 fromServer: true, 5103 saveFailure: true 5104 } ) ); 5825 } 5826 5827 if ( notification ) { 5828 api.notifications.add( notification.code, notification ); 5105 5829 } 5106 5830 … … 5114 5838 deferred.rejectWith( previewer, [ response ] ); 5115 5839 api.trigger( 'error', response ); 5840 5841 // Start a new changeset if the underlying changeset was published. 5842 if ( 'changeset_already_published' === response.code && response.next_changeset_uuid ) { 5843 api.settings.changeset.uuid = response.next_changeset_uuid; 5844 api.state( 'changesetStatus' ).set( '' ); 5845 parent.send( 'changeset-uuid', api.settings.changeset.uuid ); 5846 api.previewer.send( 'changeset-uuid', api.settings.changeset.uuid ); 5847 } 5116 5848 } ); 5117 5849 … … 5121 5853 5122 5854 api.state( 'changesetStatus' ).set( response.changeset_status ); 5855 api.state( 'changesetDate' ).set( response.changeset_date ); 5856 5123 5857 if ( 'publish' === response.changeset_status ) { 5124 5858 … … 5174 5908 5175 5909 return deferred.promise(); 5910 }, 5911 5912 /** 5913 * Builds the front preview url with the current state of customizer. 5914 * 5915 * @since 4.9 5916 * 5917 * @return {string} Preview url. 5918 */ 5919 getFrontendPreviewUrl: function() { 5920 var previewer = this, 5921 a = document.createElement( 'a' ), 5922 params = {}; 5923 5924 if ( api.state( 'changesetStatus' ).get() && 'publish' !== api.state( 'changesetStatus' ).get() ) { 5925 params.customize_changeset_uuid = api.settings.changeset.uuid; 5926 } 5927 5928 a.href = previewer.previewUrl(); 5929 a.search = $.param( params ); 5930 5931 return a.href; 5176 5932 } 5177 5933 }); … … 5300 6056 5301 6057 // Save and activated states 5302 (function() { 5303 var state = new api.Values(), 5304 saved = state.create( 'saved' ), 5305 saving = state.create( 'saving' ), 5306 activated = state.create( 'activated' ), 5307 processing = state.create( 'processing' ), 5308 paneVisible = state.create( 'paneVisible' ), 5309 expandedPanel = state.create( 'expandedPanel' ), 5310 expandedSection = state.create( 'expandedSection' ), 5311 changesetStatus = state.create( 'changesetStatus' ), 5312 previewerAlive = state.create( 'previewerAlive' ), 5313 editShortcutVisibility = state.create( 'editShortcutVisibility' ), 6058 (function( state ) { 6059 var saved = state.instance( 'saved' ), 6060 saving = state.instance( 'saving' ), 6061 activated = state.instance( 'activated' ), 6062 processing = state.instance( 'processing' ), 6063 paneVisible = state.instance( 'paneVisible' ), 6064 expandedPanel = state.instance( 'expandedPanel' ), 6065 expandedSection = state.instance( 'expandedSection' ), 6066 changesetStatus = state.instance( 'changesetStatus' ), 6067 selectedChangesetStatus = state.instance( 'selectedChangesetStatus' ), 6068 changesetDate = state.instance( 'changesetDate' ), 6069 selectedChangesetDate = state.instance( 'selectedChangesetDate' ), 6070 previewerAlive = state.instance( 'previewerAlive' ), 6071 editShortcutVisibility = state.instance( 'editShortcutVisibility' ), 5314 6072 populateChangesetUuidParam; 5315 6073 5316 6074 state.bind( 'change', function() { 5317 6075 var canSave; 6076 6077 btnWrapper.removeClass( 'button-see-me' ); 5318 6078 5319 6079 if ( ! activated() ) { 5320 6080 saveBtn.val( api.l10n.activate ); 5321 6081 closeBtn.find( '.screen-reader-text' ).text( api.l10n.cancel ); 6082 publishSettingsBtn.prop( 'disabled', false ); 5322 6083 5323 6084 } else if ( '' === changesetStatus.get() && saved() ) { 5324 saveBtn.val( api.l10n.saved ); 6085 if ( api.settings.changeset.currentUserCanPublish ) { 6086 saveBtn.val( api.l10n.published ); 6087 } else { 6088 saveBtn.val( api.l10n.saved ); 6089 } 6090 publishSettingsBtn.prop( 'disabled', true ); 5325 6091 closeBtn.find( '.screen-reader-text' ).text( api.l10n.close ); 5326 6092 5327 6093 } else { 5328 saveBtn.val( api.l10n.save ); 6094 if ( 'draft' === selectedChangesetStatus() ) { 6095 if ( saved() && selectedChangesetStatus() === changesetStatus() ) { 6096 saveBtn.val( api.l10n.draftSaved ); 6097 } else { 6098 saveBtn.val( api.l10n.saveDraft ); 6099 } 6100 } else if ( 'future' === selectedChangesetStatus() ) { 6101 if ( saved() && selectedChangesetStatus() === changesetStatus() ) { 6102 if ( changesetDate.get() !== selectedChangesetDate.get() ) { 6103 saveBtn.val( api.l10n.schedule ); 6104 btnWrapper.addClass( 'button-see-me' ); 6105 } else { 6106 saveBtn.val( api.l10n.scheduled ); 6107 } 6108 } else { 6109 btnWrapper.addClass( 'button-see-me' ); 6110 saveBtn.val( api.l10n.schedule ); 6111 } 6112 } else if ( ! api.settings.changeset.currentUserCanPublish ) { 6113 selectedChangesetStatus( 'draft' ); 6114 } else { 6115 saveBtn.val( api.l10n.publish ); 6116 } 5329 6117 closeBtn.find( '.screen-reader-text' ).text( api.l10n.cancel ); 6118 publishSettingsBtn.prop( 'disabled', false ); 5330 6119 } 5331 6120 … … 5334 6123 * and if the theme is not active or the changeset exists but is not published. 5335 6124 */ 5336 canSave = ! saving() && ( ! activated() || ! saved() || ( '' !== changesetStatus() && 'publish' !== changesetStatus() ) );6125 canSave = ! saving() && ( ! activated() || ! saved() || ( changesetStatus() !== selectedChangesetStatus() && '' !== changesetStatus() ) || ( 'future' === selectedChangesetStatus() && changesetDate.get() !== selectedChangesetDate.get() ) ); 5337 6126 5338 6127 saveBtn.prop( 'disabled', ! canSave ); 5339 6128 }); 6129 6130 selectedChangesetStatus.validate = function( status ) { 6131 if ( '' === status || 'auto-draft' === status ) { 6132 return null; 6133 } 6134 return status; 6135 }; 5340 6136 5341 6137 // Set default states. 5342 6138 changesetStatus( api.settings.changeset.status ); 6139 changesetDate( api.settings.changeset.publishDate ); 6140 selectedChangesetStatus( '' === api.settings.changeset.status || 'auto-draft' === api.settings.changeset.status ? 'publish' : api.settings.changeset.status ); 6141 selectedChangesetStatus.link( changesetStatus ); // Ensure that direct updates to status on server via wp.customizer.previewer.save() will update selection. 5343 6142 saved( true ); 5344 6143 if ( '' === changesetStatus() ) { // Handle case for loading starter content. … … 5425 6224 }; 5426 6225 6226 /** 6227 * Deactivate themes section if changeset status is not auto-draft 6228 */ 6229 api.section( 'themes', function( section ) { 6230 var canActivate; 6231 6232 canActivate = function() { 6233 return ! changesetStatus() || 'auto-draft' === changesetStatus(); 6234 }; 6235 6236 section.active.validate = canActivate; 6237 section.active.set( canActivate() ); 6238 changesetStatus.bind( function() { 6239 section.active.set( canActivate() ); 6240 } ); 6241 } ); 6242 5427 6243 // Show changeset UUID in URL when in branching mode and there is a saved changeset. 5428 6244 if ( api.settings.changeset.branching ) { … … 5431 6247 } ); 5432 6248 } 5433 5434 // Expose states to the API. 5435 api.state = state; 5436 }()); 5437 5438 // Set up autosave prompt. 6249 }( api.state ) ); 6250 6251 // Set up initial notifications. 5439 6252 (function() { 5440 6253 … … 5523 6336 onStateChange = function() { 5524 6337 api.notifications.remove( code ); 5525 api.state( 'saved' ).unbind( onStateChange ); 5526 api.state( 'saving' ).unbind( onStateChange ); 6338 api.unbind( 'change', onStateChange ); 5527 6339 api.state( 'changesetStatus' ).unbind( onStateChange ); 5528 6340 }; 5529 api.state( 'saved' ).bind( onStateChange ); 5530 api.state( 'saving' ).bind( onStateChange ); 6341 api.bind( 'change', onStateChange ); 5531 6342 api.state( 'changesetStatus' ).bind( onStateChange ); 5532 6343 } … … 5554 6365 event.preventDefault(); 5555 6366 }).keydown( function( event ) { 5556 if ( 9 === event.which ) // tab6367 if ( 9 === event.which ) { // Tab. 5557 6368 return; 5558 if ( 13 === event.which ) // enter 6369 } 6370 if ( 13 === event.which ) { // Enter. 5559 6371 api.previewer.save(); 6372 } 5560 6373 event.preventDefault(); 5561 6374 }); 5562 6375 5563 6376 closeBtn.keydown( function( event ) { 5564 if ( 9 === event.which ) // tab6377 if ( 9 === event.which ) { // Tab. 5565 6378 return; 5566 if ( 13 === event.which ) // enter 6379 } 6380 if ( 13 === event.which ) { // Enter. 5567 6381 this.click(); 6382 } 5568 6383 event.preventDefault(); 5569 6384 }); … … 5940 6755 * when customize-loader.js is used. 5941 6756 */ 5942 if ( isInsideIframe &&isCleanState() ) {6757 if ( isInsideIframe || isCleanState() ) { 5943 6758 clearedToClose.resolve(); 5944 6759 } else if ( confirm( api.l10n.saveAlert ) ) { … … 6221 7036 }); 6222 7037 })(); 7038 7039 /** 7040 * Publish settings section and controls. 7041 */ 7042 api.control( 'changeset_status', 'changeset_scheduled_date', function( statusControl, dateControl ) { 7043 $.when( statusControl.deferred.embedded, dateControl.deferred.embedded ).done( function() { 7044 var radioNodes, statusElement, toggleDateControl, publishWhenTime, pollInterval, updateTimeArrivedPoller, timeArrivedPollingInterval = 1000; 7045 7046 radioNodes = statusControl.container.find( 'input[type=radio][name]' ); 7047 statusElement = new api.Element( radioNodes ); 7048 statusControl.elements.push( statusElement ); 7049 7050 statusElement.sync( api.state( 'selectedChangesetStatus' ) ); 7051 statusElement.set( api.state( 'selectedChangesetStatus' ).get() ); 7052 7053 dateControl.notifications.alt = true; 7054 dateControl.deferred.embedded.done( function() { 7055 api.state( 'selectedChangesetDate' ).sync( dateControl.setting ); 7056 api.state( 'selectedChangesetDate' ).set( dateControl.setting() ); 7057 } ); 7058 7059 publishWhenTime = function() { 7060 var publishSettingsSection; 7061 7062 api.state( 'selectedChangesetStatus' ).set( 'publish' ); 7063 publishSettingsSection = api.section( 'publish_settings' ); 7064 if ( publishSettingsSection ) { 7065 publishSettingsSection.collapse(); 7066 } 7067 api.previewer.save(); 7068 }; 7069 7070 // Start countdown for when the dateTime arrives, or clear interval when it is . 7071 updateTimeArrivedPoller = function() { 7072 var shouldPoll = ( 7073 'future' === api.state( 'changesetStatus' ).get() && 7074 'future' === api.state( 'selectedChangesetStatus' ).get() && 7075 api.state( 'changesetDate' ).get() && 7076 api.state( 'selectedChangesetDate' ).get() === api.state( 'changesetDate' ).get() && 7077 api.utils.getRemainingTime( api.state( 'changesetDate' ).get() ) >= 0 7078 ); 7079 7080 if ( shouldPoll && ! pollInterval ) { 7081 pollInterval = setInterval( function() { 7082 var remainingTime = api.utils.getRemainingTime( api.state( 'changesetDate' ).get() ); 7083 api.state( 'remainingTimeToPublish' ).set( remainingTime ); 7084 if ( remainingTime <= 0 ) { 7085 clearInterval( pollInterval ); 7086 pollInterval = 0; 7087 publishWhenTime(); 7088 } 7089 }, timeArrivedPollingInterval ); 7090 } else if ( ! shouldPoll && pollInterval ) { 7091 clearInterval( pollInterval ); 7092 pollInterval = 0; 7093 } 7094 }; 7095 7096 api.state( 'changesetDate' ).bind( updateTimeArrivedPoller ); 7097 api.state( 'selectedChangesetDate' ).bind( updateTimeArrivedPoller ); 7098 api.state( 'changesetStatus' ).bind( updateTimeArrivedPoller ); 7099 api.state( 'selectedChangesetStatus' ).bind( updateTimeArrivedPoller ); 7100 updateTimeArrivedPoller(); 7101 7102 // Ensure dateControl only appears when selected status is future. 7103 dateControl.active.validate = function() { 7104 return 'future' === statusElement.get(); 7105 }; 7106 toggleDateControl = function( value ) { 7107 dateControl.active.set( 'future' === value ); 7108 }; 7109 toggleDateControl( statusElement.get() ); 7110 statusElement.bind( toggleDateControl ); 7111 7112 // Show notification on date control when status is future but it isn't a future date. 7113 api.state( 'saving' ).bind( function( isSaving ) { 7114 if ( isSaving && 'future' === api.state( 'selectedChangesetStatus' ).get() ) { 7115 dateControl.toggleFutureDateNotification( ! dateControl.isFutureDate() ); 7116 } 7117 } ); 7118 } ); 7119 } ); 6223 7120 6224 7121 // Toggle visibility of Header Video notice when active state change. -
trunk/src/wp-includes/class-wp-customize-control.php
r41162 r41626 748 748 */ 749 749 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-control.php' ); 750 751 /** 752 * WP_Customize_Date_Time_Control class. 753 */ 754 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-date-time-control.php' ); -
trunk/src/wp-includes/class-wp-customize-manager.php
r41603 r41626 597 597 */ 598 598 public function establish_loaded_changeset() { 599 if ( empty( $this->_changeset_uuid ) ) { 600 $changeset_uuid = null; 601 602 if ( ! $this->branching() ) { 603 $unpublished_changeset_posts = $this->get_changeset_posts( array( 604 'post_status' => array_diff( get_post_stati(), array( 'auto-draft', 'publish', 'trash', 'inherit', 'private' ) ), 605 'exclude_restore_dismissed' => false, 606 'posts_per_page' => 1, 607 'order' => 'DESC', 608 'orderby' => 'date', 609 ) ); 610 $unpublished_changeset_post = array_shift( $unpublished_changeset_posts ); 611 if ( ! empty( $unpublished_changeset_post ) && wp_is_uuid( $unpublished_changeset_post->post_name ) ) { 612 $changeset_uuid = $unpublished_changeset_post->post_name; 613 } 614 } 615 616 // If no changeset UUID has been set yet, then generate a new one. 617 if ( empty( $changeset_uuid ) ) { 618 $changeset_uuid = wp_generate_uuid4(); 619 } 620 621 $this->_changeset_uuid = $changeset_uuid; 622 } 623 } 624 625 /** 626 * Callback to validate a theme once it is loaded 627 * 628 * @since 3.4.0 629 */ 630 public function after_setup_theme() { 631 $doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_POST['customized'] ) ); 632 if ( ! $doing_ajax_or_is_customized && ! validate_current_theme() ) { 633 wp_redirect( 'themes.php?broken=true' ); 634 exit; 635 } 636 } 637 638 /** 639 * If the theme to be previewed isn't the active theme, add filter callbacks 640 * to swap it out at runtime. 641 * 642 * @since 3.4.0 643 */ 644 public function start_previewing_theme() { 645 // Bail if we're already previewing. 646 if ( $this->is_preview() ) { 647 return; 648 } 649 650 $this->previewing = true; 651 652 if ( ! $this->is_theme_active() ) { 653 add_filter( 'template', array( $this, 'get_template' ) ); 654 add_filter( 'stylesheet', array( $this, 'get_stylesheet' ) ); 655 add_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) ); 656 657 // @link: https://core.trac.wordpress.org/ticket/20027 658 add_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) ); 659 add_filter( 'pre_option_template', array( $this, 'get_template' ) ); 660 661 // Handle custom theme roots. 662 add_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) ); 663 add_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) ); 664 } 665 666 /** 667 * Fires once the Customizer theme preview has started. 668 * 669 * @since 3.4.0 670 * 671 * @param WP_Customize_Manager $this WP_Customize_Manager instance. 672 */ 673 do_action( 'start_previewing_theme', $this ); 674 } 675 676 /** 677 * Stop previewing the selected theme. 678 * 679 * Removes filters to change the current theme. 680 * 681 * @since 3.4.0 682 */ 683 public function stop_previewing_theme() { 684 if ( ! $this->is_preview() ) { 685 return; 686 } 687 688 $this->previewing = false; 689 690 if ( ! $this->is_theme_active() ) { 691 remove_filter( 'template', array( $this, 'get_template' ) ); 692 remove_filter( 'stylesheet', array( $this, 'get_stylesheet' ) ); 693 remove_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) ); 694 695 // @link: https://core.trac.wordpress.org/ticket/20027 696 remove_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) ); 697 remove_filter( 'pre_option_template', array( $this, 'get_template' ) ); 698 699 // Handle custom theme roots. 700 remove_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) ); 701 remove_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) ); 702 } 703 704 /** 705 * Fires once the Customizer theme preview has stopped. 706 * 707 * @since 3.4.0 708 * 709 * @param WP_Customize_Manager $this WP_Customize_Manager instance. 710 */ 711 do_action( 'stop_previewing_theme', $this ); 712 } 713 714 /** 715 * Gets whether settings are or will be previewed. 716 * 717 * @since 4.9.0 718 * @see WP_Customize_Setting::preview() 719 * 720 * @return bool 721 */ 722 public function settings_previewed() { 723 return $this->settings_previewed; 724 } 725 726 /** 727 * Gets whether data from a changeset's autosaved revision should be loaded if it exists. 728 * 729 * @since 4.9.0 730 * @see WP_Customize_Manager::changeset_data() 731 * 732 * @return bool Is using autosaved changeset revision. 733 */ 734 public function autosaved() { 735 return $this->autosaved; 736 } 737 738 /** 739 * Whether the changeset branching is allowed. 740 * 741 * @since 4.9.0 742 * @see WP_Customize_Manager::establish_loaded_changeset() 743 * 744 * @return bool Is changeset branching. 745 */ 746 public function branching() { 599 747 600 748 /** … … 625 773 $this->branching = apply_filters( 'customize_changeset_branching', $this->branching, $this ); 626 774 627 if ( empty( $this->_changeset_uuid ) ) { 628 $changeset_uuid = null; 629 630 if ( ! $this->branching ) { 631 $unpublished_changeset_posts = $this->get_changeset_posts( array( 632 'post_status' => array_diff( get_post_stati(), array( 'auto-draft', 'publish', 'trash', 'inherit', 'private' ) ), 633 'exclude_restore_dismissed' => false, 634 'posts_per_page' => 1, 635 'order' => 'DESC', 636 'orderby' => 'date', 637 ) ); 638 $unpublished_changeset_post = array_shift( $unpublished_changeset_posts ); 639 if ( ! empty( $unpublished_changeset_post ) && wp_is_uuid( $unpublished_changeset_post->post_name ) ) { 640 $changeset_uuid = $unpublished_changeset_post->post_name; 641 } 642 } 643 644 // If no changeset UUID has been set yet, then generate a new one. 645 if ( empty( $changeset_uuid ) ) { 646 $changeset_uuid = wp_generate_uuid4(); 647 } 648 649 $this->_changeset_uuid = $changeset_uuid; 650 } 651 } 652 653 /** 654 * Callback to validate a theme once it is loaded 655 * 656 * @since 3.4.0 657 */ 658 public function after_setup_theme() { 659 $doing_ajax_or_is_customized = ( $this->doing_ajax() || isset( $_POST['customized'] ) ); 660 if ( ! $doing_ajax_or_is_customized && ! validate_current_theme() ) { 661 wp_redirect( 'themes.php?broken=true' ); 662 exit; 663 } 664 } 665 666 /** 667 * If the theme to be previewed isn't the active theme, add filter callbacks 668 * to swap it out at runtime. 669 * 670 * @since 3.4.0 671 */ 672 public function start_previewing_theme() { 673 // Bail if we're already previewing. 674 if ( $this->is_preview() ) { 675 return; 676 } 677 678 $this->previewing = true; 679 680 if ( ! $this->is_theme_active() ) { 681 add_filter( 'template', array( $this, 'get_template' ) ); 682 add_filter( 'stylesheet', array( $this, 'get_stylesheet' ) ); 683 add_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) ); 684 685 // @link: https://core.trac.wordpress.org/ticket/20027 686 add_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) ); 687 add_filter( 'pre_option_template', array( $this, 'get_template' ) ); 688 689 // Handle custom theme roots. 690 add_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) ); 691 add_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) ); 692 } 693 694 /** 695 * Fires once the Customizer theme preview has started. 696 * 697 * @since 3.4.0 698 * 699 * @param WP_Customize_Manager $this WP_Customize_Manager instance. 700 */ 701 do_action( 'start_previewing_theme', $this ); 702 } 703 704 /** 705 * Stop previewing the selected theme. 706 * 707 * Removes filters to change the current theme. 708 * 709 * @since 3.4.0 710 */ 711 public function stop_previewing_theme() { 712 if ( ! $this->is_preview() ) { 713 return; 714 } 715 716 $this->previewing = false; 717 718 if ( ! $this->is_theme_active() ) { 719 remove_filter( 'template', array( $this, 'get_template' ) ); 720 remove_filter( 'stylesheet', array( $this, 'get_stylesheet' ) ); 721 remove_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) ); 722 723 // @link: https://core.trac.wordpress.org/ticket/20027 724 remove_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) ); 725 remove_filter( 'pre_option_template', array( $this, 'get_template' ) ); 726 727 // Handle custom theme roots. 728 remove_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) ); 729 remove_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) ); 730 } 731 732 /** 733 * Fires once the Customizer theme preview has stopped. 734 * 735 * @since 3.4.0 736 * 737 * @param WP_Customize_Manager $this WP_Customize_Manager instance. 738 */ 739 do_action( 'stop_previewing_theme', $this ); 740 } 741 742 /** 743 * Gets whether settings are or will be previewed. 744 * 745 * @since 4.9.0 746 * @see WP_Customize_Setting::preview() 747 * 748 * @return bool 749 */ 750 public function settings_previewed() { 751 return $this->settings_previewed; 775 return $this->branching; 752 776 } 753 777 … … 764 788 public function changeset_uuid() { 765 789 if ( empty( $this->_changeset_uuid ) ) { 766 throw new Exception( 'Changeset UUID has not been set.' ); // @todo Replace this with a call to `WP_Customize_Manager::establish_loaded_changeset()` during 4.9-beta2.790 $this->establish_loaded_changeset(); 767 791 } 768 792 return $this->_changeset_uuid; … … 982 1006 983 1007 /** 1008 * Dismiss all of the current user's auto-drafts (other than the present one). 1009 * 1010 * @since 4.9.0 1011 * @return int The number of auto-drafts that were dismissed. 1012 */ 1013 protected function dismiss_user_auto_draft_changesets() { 1014 $changeset_autodraft_posts = $this->get_changeset_posts( array( 1015 'post_status' => 'auto-draft', 1016 'exclude_restore_dismissed' => true, 1017 'posts_per_page' => -1, 1018 ) ); 1019 $dismissed = 0; 1020 foreach ( $changeset_autodraft_posts as $autosave_autodraft_post ) { 1021 if ( $autosave_autodraft_post->ID === $this->changeset_post_id() ) { 1022 continue; 1023 } 1024 if ( update_post_meta( $autosave_autodraft_post->ID, '_customize_restore_dismissed', true ) ) { 1025 $dismissed++; 1026 } 1027 } 1028 return $dismissed; 1029 } 1030 1031 /** 984 1032 * Get the changeset post id for the loaded changeset. 985 1033 * … … 1051 1099 $this->_changeset_data = array(); 1052 1100 } else { 1053 if ( $this->autosaved ) {1101 if ( $this->autosaved() ) { 1054 1102 $autosave_post = wp_get_post_autosave( $changeset_post_id ); 1055 1103 if ( $autosave_post ) { … … 1973 2021 'changeset' => array( 1974 2022 'uuid' => $this->changeset_uuid(), 1975 'autosaved' => $this->autosaved ,2023 'autosaved' => $this->autosaved(), 1976 2024 ), 1977 2025 'timeouts' => array( … … 2346 2394 } else { 2347 2395 $response = $r; 2396 $changeset_post = get_post( $this->changeset_post_id() ); 2348 2397 2349 2398 // Dismiss all other auto-draft changeset posts for this user (they serve like autosave revisions), as there should only be one. 2350 2399 if ( $is_new_changeset ) { 2351 $changeset_autodraft_posts = $this->get_changeset_posts( array( 2352 'post_status' => 'auto-draft', 2353 'exclude_restore_dismissed' => true, 2354 'posts_per_page' => -1, 2355 ) ); 2356 foreach ( $changeset_autodraft_posts as $autosave_autodraft_post ) { 2357 if ( $autosave_autodraft_post->ID !== $this->changeset_post_id() ) { 2358 update_post_meta( $autosave_autodraft_post->ID, '_customize_restore_dismissed', true ); 2359 } 2360 } 2400 $this->dismiss_user_auto_draft_changesets(); 2361 2401 } 2362 2402 2363 2403 // Note that if the changeset status was publish, then it will get set to trash if revisions are not supported. 2364 $response['changeset_status'] = get_post_status( $this->changeset_post_id() );2404 $response['changeset_status'] = $changeset_post->post_status; 2365 2405 if ( $is_publish && 'trash' === $response['changeset_status'] ) { 2366 2406 $response['changeset_status'] = 'publish'; 2367 2407 } 2368 2408 2369 if ( 'publish' === $response['changeset_status'] ) { 2409 if ( 'future' === $response['changeset_status'] ) { 2410 $response['changeset_date'] = $changeset_post->post_date; 2411 } 2412 2413 if ( 'publish' === $response['changeset_status'] || 'trash' === $response['changeset_status'] ) { 2370 2414 $response['next_changeset_uuid'] = wp_generate_uuid4(); 2371 2415 } … … 2435 2479 $existing_status = get_post_status( $changeset_post_id ); 2436 2480 if ( 'publish' === $existing_status || 'trash' === $existing_status ) { 2437 return new WP_Error( 'changeset_already_published' ); 2481 return new WP_Error( 2482 'changeset_already_published', 2483 __( 'The previous set of changes already been published. Please try saving your current set of changes again.' ), 2484 array( 2485 'next_changeset_uuid' => wp_generate_uuid4(), 2486 ) 2487 ); 2438 2488 } 2439 2489 … … 2454 2504 $is_future_dated = ( mysql2date( 'U', $args['date_gmt'], false ) > mysql2date( 'U', $now, false ) ); 2455 2505 if ( ! $is_future_dated ) { 2456 return new WP_Error( 'not_future_date' ); // Only future dates are allowed.2506 return new WP_Error( 'not_future_date', __( 'You must supply a future date to schedule.' ) ); // Only future dates are allowed. 2457 2507 } 2458 2508 … … 2469 2519 $changeset_post = get_post( $changeset_post_id ); 2470 2520 if ( mysql2date( 'U', $changeset_post->post_date_gmt, false ) <= mysql2date( 'U', $now, false ) ) { 2471 return new WP_Error( 'not_future_date' );2521 return new WP_Error( 'not_future_date', __( 'You must supply a future date to schedule.' ) ); 2472 2522 } 2473 2523 } … … 3057 3107 3058 3108 if ( empty( $changeset_post_id ) || 'auto-draft' === get_post_status( $changeset_post_id ) ) { 3059 $changeset_autodraft_posts = $this->get_changeset_posts( array( 3060 'post_status' => 'auto-draft', 3061 'exclude_restore_dismissed' => true, 3062 'posts_per_page' => -1, 3063 ) ); 3064 $dismissed = 0; 3065 foreach ( $changeset_autodraft_posts as $autosave_autodraft_post ) { 3066 if ( $autosave_autodraft_post->ID === $changeset_post_id ) { 3067 continue; 3068 } 3069 if ( update_post_meta( $autosave_autodraft_post->ID, '_customize_restore_dismissed', true ) ) { 3070 $dismissed++; 3071 } 3072 } 3109 $dismissed = $this->dismiss_user_auto_draft_changesets(); 3073 3110 if ( $dismissed > 0 ) { 3074 3111 wp_send_json_success( 'auto_draft_dismissed' ); 3075 3112 } else { 3076 wp_send_json_error( 'no_auto save_to_delete', 404 );3113 wp_send_json_error( 'no_auto_draft_to_delete', 404 ); 3077 3114 } 3078 3115 } else { … … 3090 3127 } 3091 3128 } else { 3092 wp_send_json_error( 'no_autosave_ to_delete', 404 );3129 wp_send_json_error( 'no_autosave_revision_to_delete', 404 ); 3093 3130 } 3094 3131 } … … 3517 3554 </ul> 3518 3555 </script> 3556 <script type="text/html" id="tmpl-customize-preview-link-control" > 3557 <span class="customize-control-title"> 3558 <label><?php esc_html_e( 'Share Preview Link' ); ?></label> 3559 </span> 3560 <span class="description customize-control-description"><?php esc_html_e( 'See how changes would look live on your website, and share the preview with people who can\'t access the Customizer.' ); ?></span> 3561 <div class="customize-control-notifications-container"></div> 3562 <div class="preview-link-wrapper"> 3563 <label> 3564 <span class="screen-reader-text"><?php esc_html_e( 'Preview Link' ); ?></span> 3565 <a class="preview-control-element" data-component="link" href="" target=""></a> 3566 <input readonly class="preview-control-element" data-component="input" value="test" > 3567 <button class="customize-copy-preview-link preview-control-element button button-secondary" data-component="button" data-copy-text="<?php esc_attr_e( 'Copy' ); ?>" data-copied-text="<?php esc_attr_e( 'Copied' ); ?>" ><?php esc_html_e( 'Copy' ); ?></button> 3568 </label> 3569 </div> 3570 </script> 3519 3571 <?php 3520 3572 } … … 3878 3930 $autosave_autodraft_post = null; 3879 3931 $changeset_post_id = $this->changeset_post_id(); 3880 if ( ! $this->saved_starter_content_changeset && ! $this->autosaved ) {3932 if ( ! $this->saved_starter_content_changeset && ! $this->autosaved() ) { 3881 3933 if ( $changeset_post_id ) { 3882 3934 $autosave_revision_post = wp_get_post_autosave( $changeset_post_id ); … … 3894 3946 3895 3947 // Prepare Customizer settings to pass to JavaScript. 3948 $changeset_post = null; 3949 if ( $changeset_post_id ) { 3950 $changeset_post = get_post( $changeset_post_id ); 3951 } 3952 3896 3953 $settings = array( 3897 3954 'changeset' => array( 3898 3955 'uuid' => $this->changeset_uuid(), 3899 'branching' => $this->branching ,3900 'autosaved' => $this->autosaved ,3956 'branching' => $this->branching(), 3957 'autosaved' => $this->autosaved(), 3901 3958 'hasAutosaveRevision' => ! empty( $autosave_revision_post ), 3902 3959 'latestAutoDraftUuid' => $autosave_autodraft_post ? $autosave_autodraft_post->post_name : null, 3903 'status' => $changeset_post_id ? get_post_status( $changeset_post_id ) : '', 3960 'status' => $changeset_post ? $changeset_post->post_status : '', 3961 'currentUserCanPublish' => current_user_can( get_post_type_object( 'customize_changeset' )->cap->publish_posts ), 3962 'publishDate' => $changeset_post ? $changeset_post->post_date : '', // @todo Only if future status? Rename to just date? 3904 3963 ), 3964 'initialServerDate' => current_time( 'mysql', false ), 3965 'initialServerTimestamp' => floor( microtime( true ) * 1000 ), 3966 'initialClientTimestamp' => -1, // To be set with JS below. 3905 3967 'timeouts' => array( 3906 3968 'windowRefresh' => 250, … … 3958 4020 <script type="text/javascript"> 3959 4021 var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>; 4022 _wpCustomizeSettings.initialClientTimestamp = _.now(); 3960 4023 _wpCustomizeSettings.controls = {}; 3961 4024 _wpCustomizeSettings.settings = {}; … … 4048 4111 $this->register_control_type( 'WP_Customize_Theme_Control' ); 4049 4112 $this->register_control_type( 'WP_Customize_Code_Editor_Control' ); 4113 $this->register_control_type( 'WP_Customize_Date_Time_Control' ); 4114 4115 /* Publish Settings */ 4116 4117 $this->add_section( 'publish_settings', array( 4118 'title' => __( 'Publish Settings' ), 4119 'priority' => 0, 4120 'capability' => 'customize', 4121 'type' => 'outer', 4122 'active_callback' => array( $this, 'is_theme_active' ), 4123 ) ); 4124 4125 /* Publish Settings Controls */ 4126 $status_choices = array( 4127 'publish' => __( 'Publish' ), 4128 'draft' => __( 'Save Draft' ), 4129 'future' => __( 'Schedule' ), 4130 ); 4131 4132 if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->publish_posts ) ) { 4133 unset( $status_choices['publish'] ); 4134 } 4135 4136 $this->add_control( 'changeset_status', array( 4137 'section' => 'publish_settings', 4138 'settings' => array(), 4139 'type' => 'radio', 4140 'label' => __( 'Action' ), 4141 'choices' => $status_choices, 4142 'capability' => 'customize', 4143 ) ); 4144 4145 if ( $this->changeset_post_id() && 'future' === get_post_status( $this->changeset_post_id() ) ) { 4146 $initial_date = get_the_time( 'Y-m-d H:i:s', $this->changeset_post_id() ); 4147 } else { 4148 $initial_date = current_time( 'mysql', false ); 4149 } 4150 $this->add_control( new WP_Customize_Date_Time_Control( $this, 'changeset_scheduled_date', array( 4151 'section' => 'publish_settings', 4152 'settings' => array(), 4153 'type' => 'date_time', 4154 'min_year' => date( 'Y' ), 4155 'allow_past_date' => false, 4156 'twelve_hour_format' => false !== stripos( get_option( 'time_format' ), 'a' ), 4157 'description' => __( 'Schedule your customization changes to publish ("go live") at a future date.' ), 4158 'capability' => 'customize', 4159 'default_value' => $initial_date, 4160 ) ) ); 4050 4161 4051 4162 /* Themes */ -
trunk/src/wp-includes/js/customize-preview.js
r41597 r41626 673 673 674 674 $( function() { 675 var bg, setValue ;675 var bg, setValue, handleUpdatedChangesetUuid; 676 676 677 677 api.settings = window._wpCustomizeSettings; … … 766 766 }); 767 767 768 /** 769 * Handle update to changeset UUID. 770 * 771 * @param {string} uuid - UUID. 772 * @returns {void} 773 */ 774 handleUpdatedChangesetUuid = function( uuid ) { 775 api.settings.changeset.uuid = uuid; 776 777 // Update UUIDs in links and forms. 778 $( document.body ).find( 'a[href], area' ).each( function() { 779 api.prepareLinkPreview( this ); 780 } ); 781 $( document.body ).find( 'form' ).each( function() { 782 api.prepareFormPreview( this ); 783 } ); 784 785 /* 786 * Replace the UUID in the URL. Note that the wrapped history.replaceState() 787 * will handle injecting the current api.settings.changeset.uuid into the URL, 788 * so this is merely to trigger that logic. 789 */ 790 if ( history.replaceState ) { 791 history.replaceState( currentHistoryState, '', location.href ); 792 } 793 }; 794 795 api.preview.bind( 'changeset-uuid', handleUpdatedChangesetUuid ); 796 768 797 api.preview.bind( 'saved', function( response ) { 769 798 if ( response.next_changeset_uuid ) { 770 api.settings.changeset.uuid = response.next_changeset_uuid; 771 772 // Update UUIDs in links and forms. 773 $( document.body ).find( 'a[href], area' ).each( function() { 774 api.prepareLinkPreview( this ); 775 } ); 776 $( document.body ).find( 'form' ).each( function() { 777 api.prepareFormPreview( this ); 778 } ); 779 780 /* 781 * Replace the UUID in the URL. Note that the wrapped history.replaceState() 782 * will handle injecting the current api.settings.changeset.uuid into the URL, 783 * so this is merely to trigger that logic. 784 */ 785 if ( history.replaceState ) { 786 history.replaceState( currentHistoryState, '', location.href ); 787 } 788 } 789 799 handleUpdatedChangesetUuid( response.next_changeset_uuid ); 800 } 790 801 api.trigger( 'saved', response ); 791 802 } ); -
trunk/src/wp-includes/script-loader.php
r41608 r41626 548 548 $scripts->add( 'customize-controls', "/wp-admin/js/customize-controls$suffix.js", array( 'customize-base', 'wp-a11y', 'wp-util' ), false, 1 ); 549 549 did_action( 'init' ) && $scripts->localize( 'customize-controls', '_wpCustomizeControlsL10n', array( 550 'activate' => __( 'Save & Activate' ), 551 'save' => __( 'Save & Publish' ), 550 'activate' => __( 'Activate & Publish' ), 551 'save' => __( 'Save & Publish' ), // @todo Remove as not required. 552 'publish' => __( 'Publish' ), 553 'published' => __( 'Published' ), 554 'saveDraft' => __( 'Save Draft' ), 555 'draftSaved' => __( 'Draft Saved' ), 556 'updating' => __( 'Updating' ), 557 'schedule' => __( 'Schedule' ), 558 'scheduled' => __( 'Scheduled' ), 559 'invalid' => __( 'Invalid' ), 560 'saveBeforeShare' => __( 'Please save your changes in order to share the preview.' ), 561 'futureDateError' => __( 'You must supply a future date to schedule.' ), 552 562 'saveAlert' => __( 'The changes you made will be lost if you navigate away from this page.' ), 553 563 'saved' => __( 'Saved' ), … … 564 574 /* translators: placeholder is URL to the Customizer to load the autosaved version */ 565 575 'autosaveNotice' => __( 'There is a more recent autosave of your changes than the one you are previewing. <a href="%s">Restore the autosave</a>' ), 566 'videoHeaderNotice' 576 'videoHeaderNotice' => __( 'This theme doesn\'t support video headers on this page. Navigate to the front page or another page that supports video headers.' ), 567 577 // Used for overriding the file types allowed in plupload. 568 578 'allowedFiles' => __( 'Allowed Files' ), -
trunk/tests/phpunit/tests/ajax/CustomizeManager.php
r39409 r41626 166 166 $this->assertFalse( $this->_last_response_parsed['success'] ); 167 167 $this->assertEquals( 'changeset_already_published', $this->_last_response_parsed['data']['code'] ); 168 wp_update_post( array( 'ID' => $wp_customize->changeset_post_id(), 'post_status' => 'auto-draft' ) ); 168 wp_update_post( array( 169 'ID' => $wp_customize->changeset_post_id(), 170 'post_status' => 'auto-draft', 171 ) ); 169 172 170 173 // User cannot edit. … … 223 226 $this->assertTrue( $this->_last_response_parsed['success'] ); 224 227 $this->assertEquals( 'future', get_post_status( $wp_customize->changeset_post_id() ) ); 225 wp_update_post( array( 'ID' => $wp_customize->changeset_post_id(), 'post_status' => 'auto-draft' ) ); 226 228 wp_update_post( array( 229 'ID' => $wp_customize->changeset_post_id(), 230 'post_status' => 'auto-draft', 231 ) ); 227 232 } 228 233 … … 255 260 $wp_customize = $this->set_up_valid_state(); 256 261 257 // Successful future.258 262 $_POST['customize_changeset_status'] = 'publish'; 259 263 $_POST['customize_changeset_title'] = 'Success Changeset'; … … 269 273 $this->assertEquals( 'publish', $this->_last_response_parsed['data']['changeset_status'] ); 270 274 $this->assertArrayHasKey( 'next_changeset_uuid', $this->_last_response_parsed['data'] ); 275 $this->assertTrue( wp_is_uuid( $this->_last_response_parsed['data']['next_changeset_uuid'], 4 ) ); 271 276 $this->assertEquals( 'Success Changeset', get_post( $wp_customize->changeset_post_id() )->post_title ); 272 277 $this->assertEquals( 'Successful Site Title', get_option( 'blogname' ) ); 273 274 278 } 275 279 … … 296 300 $wp_customize = $this->set_up_valid_state( $uuid ); 297 301 298 // Successful future.299 302 $_POST['customize_changeset_status'] = 'publish'; 300 303 $_POST['customize_changeset_title'] = 'Published'; … … 305 308 $this->assertEquals( 'publish', $this->_last_response_parsed['data']['changeset_status'] ); 306 309 $this->assertArrayHasKey( 'next_changeset_uuid', $this->_last_response_parsed['data'] ); 310 $this->assertTrue( wp_is_uuid( $this->_last_response_parsed['data']['next_changeset_uuid'], 4 ) ); 307 311 $this->assertEquals( 'New Site Title', get_option( 'blogname' ) ); 308 312 $this->assertEquals( 'Published', get_post( $post_id )->post_title ); … … 337 341 $this->make_ajax_call( 'customize_save' ); 338 342 $this->assertTrue( $this->_last_response_parsed['success'] ); 343 $this->assertArrayHasKey( 'changeset_date', $this->_last_response_parsed['data'] ); 339 344 $changeset_post_schedule = get_post( $post_id ); 340 345 $this->assertEquals( $future_date, $changeset_post_schedule->post_date ); … … 345 350 $this->make_ajax_call( 'customize_save' ); 346 351 $this->assertTrue( $this->_last_response_parsed['success'] ); 352 $this->assertArrayNotHasKey( 'changeset_date', $this->_last_response_parsed['data'] ); 347 353 $changeset_post_draft = get_post( $post_id ); 348 354 $this->assertEquals( $future_date, $changeset_post_draft->post_date ); … … 352 358 $this->make_ajax_call( 'customize_save' ); 353 359 $this->assertTrue( $this->_last_response_parsed['success'] ); 360 $this->assertArrayHasKey( 'changeset_date', $this->_last_response_parsed['data'] ); 354 361 $changeset_post_schedule = get_post( $post_id ); 355 362 $this->assertEquals( $future_date, $changeset_post_schedule->post_date ); … … 381 388 $this->make_ajax_call( 'customize_save' ); 382 389 $this->assertTrue( $this->_last_response_parsed['success'] ); 390 $this->assertArrayHasKey( 'next_changeset_uuid', $this->_last_response_parsed['data'] ); 391 $this->assertTrue( wp_is_uuid( $this->_last_response_parsed['data']['next_changeset_uuid'], 4 ) ); 383 392 $changeset_post_publish = get_post( $post_id ); 384 393 $this->assertNotEquals( $future_date, $changeset_post_publish->post_date ); 394 395 // Check response when trying to update an already-published post. 396 $this->assertEquals( 'trash', get_post_status( $post_id ) ); 397 $_POST['customize_changeset_status'] = 'publish'; 398 $this->make_ajax_call( 'customize_save' ); 399 $this->assertFalse( $this->_last_response_parsed['success'] ); 400 $this->assertEquals( 'changeset_already_published', $this->_last_response_parsed['data']['code'] ); 401 $this->assertArrayHasKey( 'next_changeset_uuid', $this->_last_response_parsed['data'] ); 402 $this->assertTrue( wp_is_uuid( $this->_last_response_parsed['data']['next_changeset_uuid'], 4 ) ); 403 } 404 405 /** 406 * Test WP_Customize_Manager::save(). 407 * 408 * @ticket 39896 409 * @covers WP_Customize_Manager::save() 410 */ 411 public function test_save_autosave() { 412 $uuid = wp_generate_uuid4(); 413 414 $post_id = $this->factory()->post->create( array( 415 'post_name' => $uuid, 416 'post_type' => 'customize_changeset', 417 'post_status' => 'draft', 418 'post_content' => wp_json_encode( array( 419 'blogname' => array( 420 'value' => 'New Site Title', 421 ), 422 ) ), 423 ) ); 424 $this->set_up_valid_state( $uuid ); 425 426 $this->assertFalse( wp_get_post_autosave( $post_id ) ); 427 428 $_POST['customize_changeset_data'] = wp_json_encode( array( 429 'blogname' => array( 430 'value' => 'Autosaved Site Title', 431 ), 432 ) ); 433 434 $_POST['customize_changeset_autosave'] = 'on'; 435 $this->make_ajax_call( 'customize_save' ); 436 $this->assertTrue( $this->_last_response_parsed['success'] ); 437 $this->assertEquals( 'draft', $this->_last_response_parsed['data']['changeset_status'] ); 438 $autosave_revision = wp_get_post_autosave( $post_id ); 439 $this->assertInstanceOf( 'WP_Post', $autosave_revision ); 440 441 $this->assertContains( 'New Site Title', get_post( $post_id )->post_content ); 442 $this->assertContains( 'Autosaved Site Title', $autosave_revision->post_content ); 443 } 444 445 /** 446 * Test request for dismissing autosave changesets. 447 * 448 * @ticket 39896 449 * @covers WP_Customize_Manager::handle_dismiss_changeset_autosave_request() 450 * @covers WP_Customize_Manager::dismiss_user_auto_draft_changesets() 451 */ 452 public function test_handle_dismiss_changeset_autosave_request() { 453 $uuid = wp_generate_uuid4(); 454 $wp_customize = $this->set_up_valid_state( $uuid ); 455 456 $this->make_ajax_call( 'dismiss_customize_changeset_autosave' ); 457 $this->assertFalse( $this->_last_response_parsed['success'] ); 458 $this->assertEquals( 'invalid_nonce', $this->_last_response_parsed['data'] ); 459 460 $nonce = wp_create_nonce( 'dismiss_customize_changeset_autosave' ); 461 $_POST['nonce'] = $_GET['nonce'] = $_REQUEST['nonce'] = $nonce; 462 $this->make_ajax_call( 'dismiss_customize_changeset_autosave' ); 463 $this->assertFalse( $this->_last_response_parsed['success'] ); 464 $this->assertEquals( 'no_auto_draft_to_delete', $this->_last_response_parsed['data'] ); 465 466 $other_user_id = $this->factory()->user->create(); 467 468 // Create auto-drafts. 469 $user_auto_draft_ids = array(); 470 for ( $i = 0; $i < 3; $i++ ) { 471 $user_auto_draft_ids[] = $this->factory()->post->create( array( 472 'post_name' => wp_generate_uuid4(), 473 'post_type' => 'customize_changeset', 474 'post_status' => 'auto-draft', 475 'post_author' => self::$admin_user_id, 476 'post_content' => wp_json_encode( array() ), 477 ) ); 478 } 479 $other_user_auto_draft_ids = array(); 480 for ( $i = 0; $i < 3; $i++ ) { 481 $other_user_auto_draft_ids[] = $this->factory()->post->create( array( 482 'post_name' => wp_generate_uuid4(), 483 'post_type' => 'customize_changeset', 484 'post_status' => 'auto-draft', 485 'post_author' => $other_user_id, 486 'post_content' => wp_json_encode( array() ), 487 ) ); 488 } 489 foreach ( array_merge( $user_auto_draft_ids, $other_user_auto_draft_ids ) as $post_id ) { 490 $this->assertFalse( (bool) get_post_meta( $post_id, '_customize_restore_dismissed', true ) ); 491 } 492 $this->make_ajax_call( 'dismiss_customize_changeset_autosave' ); 493 $this->assertTrue( $this->_last_response_parsed['success'] ); 494 $this->assertEquals( 'auto_draft_dismissed', $this->_last_response_parsed['data'] ); 495 foreach ( $user_auto_draft_ids as $post_id ) { 496 $this->assertEquals( 'auto-draft', get_post_status( $post_id ) ); 497 $this->assertTrue( (bool) get_post_meta( $post_id, '_customize_restore_dismissed', true ) ); 498 } 499 foreach ( $other_user_auto_draft_ids as $post_id ) { 500 $this->assertEquals( 'auto-draft', get_post_status( $post_id ) ); 501 $this->assertFalse( (bool) get_post_meta( $post_id, '_customize_restore_dismissed', true ) ); 502 } 503 504 // Subsequent test results in none dismissed. 505 $this->make_ajax_call( 'dismiss_customize_changeset_autosave' ); 506 $this->assertFalse( $this->_last_response_parsed['success'] ); 507 $this->assertEquals( 'no_auto_draft_to_delete', $this->_last_response_parsed['data'] ); 508 509 // Save a changeset as a draft. 510 $r = $wp_customize->save_changeset_post( array( 511 'data' => array( 512 'blogname' => array( 513 'value' => 'Foo', 514 ), 515 ), 516 'status' => 'draft', 517 ) ); 518 $this->assertNotInstanceOf( 'WP_Error', $r ); 519 $this->assertFalse( wp_get_post_autosave( $wp_customize->changeset_post_id() ) ); 520 $this->assertContains( 'Foo', get_post( $wp_customize->changeset_post_id() )->post_content ); 521 522 // Since no autosave yet, confirm no action. 523 $this->make_ajax_call( 'dismiss_customize_changeset_autosave' ); 524 $this->assertFalse( $this->_last_response_parsed['success'] ); 525 $this->assertEquals( 'no_autosave_revision_to_delete', $this->_last_response_parsed['data'] ); 526 527 // Add the autosave revision. 528 $r = $wp_customize->save_changeset_post( array( 529 'data' => array( 530 'blogname' => array( 531 'value' => 'Bar', 532 ), 533 ), 534 'autosave' => true, 535 ) ); 536 $this->assertNotInstanceOf( 'WP_Error', $r ); 537 $autosave_revision = wp_get_post_autosave( $wp_customize->changeset_post_id() ); 538 $this->assertInstanceOf( 'WP_Post', $autosave_revision ); 539 $this->assertContains( 'Foo', get_post( $wp_customize->changeset_post_id() )->post_content ); 540 $this->assertContains( 'Bar', $autosave_revision->post_content ); 541 542 // Confirm autosave gets deleted. 543 $this->make_ajax_call( 'dismiss_customize_changeset_autosave' ); 544 $this->assertTrue( $this->_last_response_parsed['success'] ); 545 $this->assertEquals( 'autosave_revision_deleted', $this->_last_response_parsed['data'] ); 546 $this->assertFalse( wp_get_post_autosave( $wp_customize->changeset_post_id() ) ); 547 548 // Since no autosave yet, confirm no action. 549 $this->make_ajax_call( 'dismiss_customize_changeset_autosave' ); 550 $this->assertFalse( $this->_last_response_parsed['success'] ); 551 $this->assertEquals( 'no_autosave_revision_to_delete', $this->_last_response_parsed['data'] ); 385 552 } 386 553 } -
trunk/tests/phpunit/tests/customize/manager.php
r41558 r41626 121 121 $this->assertEquals( $theme, $wp_customize->get_stylesheet() ); 122 122 $this->assertEquals( $messenger_channel, $wp_customize->get_messenger_channel() ); 123 $this->assertFalse( $wp_customize->autosaved() ); 124 $this->assertTrue( $wp_customize->branching() ); 125 126 $wp_customize = new WP_Customize_Manager( array( 127 'changeset_uuid' => null, 128 ) ); 129 $this->assertTrue( wp_is_uuid( $wp_customize->changeset_uuid(), 4 ) ); 123 130 124 131 $theme = 'twentyfourteen'; … … 134 141 $wp_customize = new WP_Customize_Manager(); 135 142 $this->assertEquals( $theme, $wp_customize->get_stylesheet() ); 136 $this->assertNotEmpty( $wp_customize->changeset_uuid() ); 143 $this->assertTrue( wp_is_uuid( $wp_customize->changeset_uuid(), 4 ) ); 144 } 145 146 /** 147 * Test constructor when deferring UUID. 148 * 149 * @ticket 39896 150 * @covers WP_Customize_Manager::establish_loaded_changeset() 151 * @covers WP_Customize_Manager::__construct() 152 */ 153 public function test_constructor_deferred_changeset_uuid() { 154 $data = array( 155 'blogname' => array( 156 'value' => 'Test', 157 ), 158 ); 159 $uuid = wp_generate_uuid4(); 160 $post_id = $this->factory()->post->create( array( 161 'post_type' => 'customize_changeset', 162 'post_name' => $uuid, 163 'post_status' => 'draft', 164 'post_content' => wp_json_encode( $data ), 165 ) ); 166 $wp_customize = new WP_Customize_Manager( array( 167 'changeset_uuid' => false, // Cause UUID to be deferred. 168 'branching' => false, // To cause drafted changeset to be autoloaded. 169 ) ); 170 $this->assertEquals( $uuid, $wp_customize->changeset_uuid() ); 171 $this->assertEquals( $post_id, $wp_customize->changeset_post_id() ); 137 172 } 138 173 … … 255 290 256 291 /** 292 * Test WP_Customize_Manager::autosaved(). 293 * 294 * @ticket 39896 295 * @covers WP_Customize_Manager::autosaved() 296 */ 297 public function test_autosaved() { 298 $wp_customize = new WP_Customize_Manager(); 299 $this->assertFalse( $wp_customize->autosaved() ); 300 301 $wp_customize = new WP_Customize_Manager( array( 'autosaved' => false ) ); 302 $this->assertFalse( $wp_customize->autosaved() ); 303 304 $wp_customize = new WP_Customize_Manager( array( 'autosaved' => true ) ); 305 $this->assertTrue( $wp_customize->autosaved() ); 306 } 307 308 /** 309 * Test WP_Customize_Manager::branching(). 310 * 311 * @ticket 39896 312 * @covers WP_Customize_Manager::branching() 313 */ 314 public function test_branching() { 315 $wp_customize = new WP_Customize_Manager(); 316 $this->assertTrue( $wp_customize->branching(), 'Branching should default to true since it is original behavior in 4.7.' ); 317 318 $wp_customize = new WP_Customize_Manager( array( 'branching' => false ) ); 319 $this->assertFalse( $wp_customize->branching() ); 320 add_filter( 'customize_changeset_branching', '__return_true' ); 321 $this->assertTrue( $wp_customize->branching() ); 322 remove_filter( 'customize_changeset_branching', '__return_true' ); 323 324 $wp_customize = new WP_Customize_Manager( array( 'branching' => true ) ); 325 $this->assertTrue( $wp_customize->branching() ); 326 add_filter( 'customize_changeset_branching', '__return_false' ); 327 $this->assertFalse( $wp_customize->branching() ); 328 } 329 330 /** 257 331 * Test WP_Customize_Manager::changeset_uuid(). 258 332 * … … 338 412 */ 339 413 function test_changeset_data() { 414 wp_set_current_user( self::$admin_user_id ); 340 415 $uuid = wp_generate_uuid4(); 341 416 $wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) ); … … 343 418 344 419 $uuid = wp_generate_uuid4(); 345 $data = array( 'blogname' => array( 'value' => 'Hello World' ) ); 420 $data = array( 421 'blogname' => array( 'value' => 'Hello World' ), 422 'blogdescription' => array( 'value' => 'Greet the world' ), 423 ); 346 424 $this->factory()->post->create( array( 347 425 'post_name' => $uuid, 348 426 'post_type' => 'customize_changeset', 349 'post_status' => ' auto-draft',427 'post_status' => 'draft', 350 428 'post_content' => wp_json_encode( $data ), 351 429 ) ); 352 430 $wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) ); 353 431 $this->assertEquals( $data, $wp_customize->changeset_data() ); 432 433 // Autosave. 434 $wp_customize->set_post_value( 'blogname', 'Hola Mundo' ); 435 $wp_customize->register_controls(); // That is, settings, so blogname setting is registered. 436 $r = $wp_customize->save_changeset_post( array( 437 'autosave' => true, 438 ) ); 439 $this->assertNotInstanceOf( 'WP_Error', $r ); 440 441 // No change to data if not requesting autosave. 442 $wp_customize = new WP_Customize_Manager( array( 443 'changeset_uuid' => $uuid, 444 'autosaved' => false, 445 ) ); 446 $wp_customize->register_controls(); // That is, settings. 447 $this->assertFalse( $wp_customize->autosaved() ); 448 $this->assertEquals( $data, $wp_customize->changeset_data() ); 449 450 // No change to data if not requesting autosave. 451 $wp_customize = new WP_Customize_Manager( array( 452 'changeset_uuid' => $uuid, 453 'autosaved' => true, 454 ) ); 455 $this->assertTrue( $wp_customize->autosaved() ); 456 $this->assertNotEquals( $data, $wp_customize->changeset_data() ); 457 $this->assertEquals( 458 array_merge( 459 wp_list_pluck( $data, 'value' ), 460 array( 'blogname' => 'Hola Mundo' ) 461 ), 462 wp_list_pluck( $wp_customize->changeset_data(), 'value' ) 463 ); 354 464 } 355 465 … … 1274 1384 1275 1385 /** 1386 * Test writing changesets when user supplies unchanged values. 1387 * 1388 * @ticket 39896 1389 * @covers WP_Customize_Manager::save_changeset_post() 1390 * @covers WP_Customize_Manager::grant_edit_post_capability_for_changeset() 1391 */ 1392 public function test_save_changeset_post_with_autosave() { 1393 wp_set_current_user( self::$admin_user_id ); 1394 $uuid = wp_generate_uuid4(); 1395 $changeset_post_id = wp_insert_post( array( 1396 'post_type' => 'customize_changeset', 1397 'post_content' => wp_json_encode( array( 1398 'blogname' => array( 1399 'value' => 'Auto-draft Title', 1400 ), 1401 ) ), 1402 'post_author' => self::$admin_user_id, 1403 'post_name' => $uuid, 1404 'post_status' => 'auto-draft', 1405 ) ); 1406 1407 $wp_customize = new WP_Customize_Manager( array( 1408 'changeset_uuid' => $uuid, 1409 ) ); 1410 $wp_customize->register_controls(); // And settings too. 1411 1412 // Autosave of an auto-draft overwrites original. 1413 $wp_customize->save_changeset_post( array( 1414 'data' => array( 1415 'blogname' => array( 1416 'value' => 'Autosaved Auto-draft Title', 1417 ), 1418 ), 1419 'autosave' => true, 1420 ) ); 1421 $this->assertFalse( wp_get_post_autosave( $changeset_post_id ) ); 1422 $this->assertContains( 'Autosaved Auto-draft Title', get_post( $changeset_post_id )->post_content ); 1423 1424 // Update status to draft for subsequent tests. 1425 $wp_customize->save_changeset_post( array( 1426 'data' => array( 1427 'blogname' => array( 1428 'value' => 'Draft Title', 1429 ), 1430 ), 1431 'status' => 'draft', 1432 'autosave' => false, 1433 ) ); 1434 $this->assertContains( 'Draft Title', get_post( $changeset_post_id )->post_content ); 1435 1436 // Fail: illegal_autosave_with_date_gmt. 1437 $r = $wp_customize->save_changeset_post( array( 1438 'autosave' => true, 1439 'date_gmt' => ( gmdate( 'Y' ) + 1 ) . '-12-01 00:00:00', 1440 ) ); 1441 $this->assertInstanceOf( 'WP_Error', $r ); 1442 $this->assertEquals( 'illegal_autosave_with_date_gmt', $r->get_error_code() ); 1443 1444 // Fail: illegal_autosave_with_status. 1445 $r = $wp_customize->save_changeset_post( array( 1446 'autosave' => true, 1447 'status' => 'pending', 1448 ) ); 1449 $this->assertEquals( 'illegal_autosave_with_status', $r->get_error_code() ); 1450 1451 // Fail: illegal_autosave_with_non_current_user. 1452 $r = $wp_customize->save_changeset_post( array( 1453 'autosave' => true, 1454 'user_id' => $this->factory()->user->create( array( 'role' => 'administrator' ) ), 1455 ) ); 1456 $this->assertEquals( 'illegal_autosave_with_non_current_user', $r->get_error_code() ); 1457 1458 // Try autosave. 1459 $this->assertFalse( wp_get_post_autosave( $changeset_post_id ) ); 1460 $r = $wp_customize->save_changeset_post( array( 1461 'data' => array( 1462 'blogname' => array( 1463 'value' => 'Autosave Title', 1464 ), 1465 ), 1466 'autosave' => true, 1467 ) ); 1468 $this->assertInternalType( 'array', $r ); 1469 1470 // Verify that autosave happened. 1471 $autosave_revision = wp_get_post_autosave( $changeset_post_id ); 1472 $this->assertInstanceOf( 'WP_Post', $autosave_revision ); 1473 $this->assertContains( 'Draft Title', get_post( $changeset_post_id )->post_content ); 1474 $this->assertContains( 'Autosave Title', $autosave_revision->post_content ); 1475 } 1476 1477 /** 1276 1478 * Test passing `null` for a setting ID to remove it from the changeset. 1277 1479 * … … 2352 2554 $this->assertNotEmpty( $data ); 2353 2555 2354 $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'changeset', 'timeouts' ), array_keys( $data ) );2556 $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'changeset', 'timeouts', 'initialClientTimestamp', 'initialServerDate', 'initialServerTimestamp' ), array_keys( $data ) ); 2355 2557 $this->assertEquals( $autofocus, $data['autofocus'] ); 2356 2558 $this->assertArrayHasKey( 'save', $data['nonce'] ); 2357 2559 $this->assertArrayHasKey( 'preview', $data['nonce'] ); 2560 2561 $this->assertEqualSets( 2562 array( 2563 'branching', 2564 'autosaved', 2565 'hasAutosaveRevision', 2566 'latestAutoDraftUuid', 2567 'status', 2568 'uuid', 2569 'currentUserCanPublish', 2570 'publishDate', 2571 ), 2572 array_keys( $data['changeset'] ) 2573 ); 2358 2574 } 2359 2575 -
trunk/tests/qunit/fixtures/customize-settings.js
r38810 r41626 107 107 'type': 'reusing-default-template' 108 108 }, 109 'publish_settings': { 110 'active': true, 111 'description': '', 112 'instanceNumber': 6, 113 'priority': 20, 114 'title': 'Fixture section of custom type re-using default template', 115 'type': 'outer' 116 }, 109 117 'fixture-section-without-params': {} 110 118 }, … … 149 157 } 150 158 }, 159 initialClientTimestamp: 1506510531595, 160 initialServerDate: '2017-09-27 16:38:49', 161 initialServerTimestamp: 1506510529913, 151 162 changeset: { 152 163 status: '', 153 uuid: '0c674ff4-c159-4e7a-beb4-cb830ae73979' 164 uuid: '0c674ff4-c159-4e7a-beb4-cb830ae73979', 165 autosaved: false, 166 branching: false, 167 currentUserCanPublish: false, 168 hasAutosaveRevision: false, 169 latestAutoDraftUuid: '341b06f6-3c1f-454f-96df-3cf197f3e347', 170 publishDate: '' 154 171 }, 155 172 timeouts: { -
trunk/tests/qunit/index.html
r41590 r41626 873 873 <# } #> 874 874 </script> 875 875 <script type="text/html" id="tmpl-customize-control-date_time-content"> 876 877 <# _.defaults( data, {"settings":[],"type":"date_time","priority":10,"active":true,"section":"","content":"<li id=\"customize-control-temp\" class=\"customize-control customize-control-date_time\">\n\t\t\t\t\t<\/li>","label":"","description":"","instanceNumber":69,"maxYear":9999,"minYear":1000,"allowPastDate":true,"twelveHourFormat":true,"defaultValue":null,"month_choices":{"1":{"text":"1-Jan","value":1},"2":{"text":"2-Feb","value":2},"3":{"text":"3-Mar","value":3},"4":{"text":"4-Apr","value":4},"5":{"text":"5-May","value":5},"6":{"text":"6-Jun","value":6},"7":{"text":"7-Jul","value":7},"8":{"text":"8-Aug","value":8},"9":{"text":"9-Sep","value":9},"10":{"text":"10-Oct","value":10},"11":{"text":"11-Nov","value":11},"12":{"text":"12-Dec","value":12}}} ); #> 878 879 <span class="customize-control-title"> 880 <label>{{ data.label }}</label> 881 </span> 882 <div class="customize-control-notifications-container"></div> 883 <span class="description customize-control-description">{{ data.description }}</span> 884 <div class="date-time-fields"> 885 <div class="day-row"> 886 <span class="title-day">Day</span> 887 <div class="day-fields clear"> 888 <label class="month-field"> 889 <span class="screen-reader-text">Month</span> 890 <select class="date-input month" data-component="month"> 891 <# _.each( data.month_choices, function( choice ) { 892 if ( _.isObject( choice ) && ! _.isUndefined( choice.text ) && ! _.isUndefined( choice.value ) ) { 893 text = choice.text; 894 value = choice.value; 895 } 896 #> 897 <option value="{{ value }}" > 898 {{ text }} 899 </option> 900 <# } ); #> 901 </select> 902 </label> 903 <label class="day-field"> 904 <span class="screen-reader-text">Day</span> 905 <input type="number" size="2" maxlength="2" autocomplete="off" class="date-input day" data-component="day" min="1" max="31"" /> 906 </label> 907 <span class="time-special-char date-time-separator">,</span> 908 <label class="year-field"> 909 <span class="screen-reader-text">Year</span> 910 <# var maxYearLength = String( data.maxYear ).length; #> 911 <input type="number" size="4" maxlength="{{ maxYearLength }}" autocomplete="off" class="date-input year" data-component="year" min="{{ data.minYear }}" max="{{ data.maxYear }}" /> 912 </label> 913 </div> 914 </div> 915 <div class="time-row clear"> 916 <span class="title-time">Time</span> 917 <div class="time-fields clear"> 918 <label class="hour-field"> 919 <span class="screen-reader-text">Hour</span> 920 <# var maxHour = data.twelveHourFormat ? 12 : 24; #> 921 <input type="number" size="2" maxlength="2" autocomplete="off" class="date-input hour" data-component="hour" min="1" max="{{ maxHour }}"" /> 922 </label> 923 <span class="time-special-char date-time-separator">:</span> 924 <label class="minute-field"> 925 <span class="screen-reader-text">Minute</span> 926 <input type="number" size="2" maxlength="2" autocomplete="off" class="date-input minute" data-component="minute" min="0" max="59" /> 927 </label> 928 <# if ( data.twelveHourFormat ) { #> 929 <label class="am-pm-field"> 930 <span class="screen-reader-text">AM / PM</span> 931 <select class="date-input" data-component="ampm"> 932 <option value="am">AM</option> 933 <option value="pm">PM</option> 934 </select> 935 </label> 936 <# } #> 937 <abbr class="date-timezone" aria-label="Timezone" title="Timezone is Asia/Kolkata (IST), currently UTC+5:30.">IST</abbr> 938 </div> 939 </div> 940 </div> 941 </script> 942 <script type="text/html" id="tmpl-customize-preview-link-control" > 943 <span class="customize-control-title"> 944 <label>Share Preview Link</label> 945 </span> 946 <span class="description customize-control-description">See how changes would look live on your website, and share the preview with people who can't access the Customizer.</span> 947 <div class="customize-control-notifications-container"></div> 948 <div class="preview-link-wrapper"> 949 <label> 950 <span class="screen-reader-text">Preview Link</span> 951 <a class="preview-control-element" data-component="link" href="" target=""></a> 952 <input readonly class="preview-control-element" data-component="input" value="test" > 953 <button class="customize-copy-preview-link preview-control-element button button-secondary" data-component="button" data-copy-text="Copy" data-copied-text="Copied" >Copy</button> 954 </label> 955 </div> 956 </script> 876 957 <script type="text/html" id="tmpl-media-modal"> 877 958 <div class="media-modal wp-core-ui"> -
trunk/tests/qunit/wp-admin/js/customize-controls.js
r41374 r41626 676 676 } ); 677 677 } ); 678 679 module( 'Customize Utils: wp.customize.utils.getRemainingTime()' ); 680 test( 'utils.getRemainingTime calculates time correctly', function( assert ) { 681 var datetime = '2599-08-06 12:12:13', timeRemaining, timeRemainingWithDateInstance, timeRemaingingWithTimestamp; 682 683 timeRemaining = wp.customize.utils.getRemainingTime( datetime ); 684 timeRemainingWithDateInstance = wp.customize.utils.getRemainingTime( new Date( datetime.replace( /-/g, '/' ) ) ); 685 timeRemaingingWithTimestamp = wp.customize.utils.getRemainingTime( ( new Date( datetime.replace( /-/g, '/' ) ) ).getTime() ); 686 687 assert.equal( typeof timeRemaining, 'number', timeRemaining ); 688 assert.equal( typeof timeRemainingWithDateInstance, 'number', timeRemaining ); 689 assert.equal( typeof timeRemaingingWithTimestamp, 'number', timeRemaining ); 690 assert.deepEqual( timeRemaining, timeRemainingWithDateInstance ); 691 assert.deepEqual( timeRemaining, timeRemaingingWithTimestamp ); 692 }); 693 694 module( 'Customize Utils: wp.customize.utils.getCurrentTimestamp()' ); 695 test( 'utils.getCurrentTimestamp returns timestamp', function( assert ) { 696 var currentTimeStamp; 697 currentTimeStamp = wp.customize.utils.getCurrentTimestamp(); 698 assert.equal( typeof currentTimeStamp, 'number' ); 699 }); 700 701 module( 'Customize Controls: wp.customize.DateTimeControl' ); 702 test( 'Test DateTimeControl creation and its methods', function( assert ) { 703 var control, controlId = 'date_time', section, sectionId = 'fixture-section', 704 datetime = '2599-08-06 18:12:13', dateTimeArray, dateTimeArrayInampm, timeString, 705 day, year, month, minute, ampm, hour; 706 707 section = wp.customize.section( sectionId ); 708 709 control = new wp.customize.DateTimeControl( controlId, { 710 params: { 711 section: section.id, 712 type: 'date_time', 713 content: '<li id="customize-control-' + controlId + '" class="customize-control"></li>', 714 defaultValue: datetime 715 } 716 } ); 717 718 wp.customize.control.add( controlId, control ); 719 720 // Test control creations. 721 assert.ok( control.templateSelector, '#customize-control-date_time-content' ); 722 assert.ok( control.section(), sectionId ); 723 assert.equal( _.size( control.inputElements ), control.elements.length ); 724 assert.ok( control.setting(), datetime ); 725 726 day = control.inputElements.day; 727 month = control.inputElements.month; 728 year = control.inputElements.year; 729 minute = control.inputElements.minute; 730 hour = control.inputElements.hour; 731 ampm = control.inputElements.ampm; 732 733 year( '23' ); 734 assert.equal( typeof year(), 'number', 'Should always return integer' ); 735 736 month( 'test' ); 737 assert.notOk( month(), 'Should not accept text' ); 738 739 // Test control.parseDateTime(); 740 dateTimeArray = control.parseDateTime( datetime ); 741 assert.deepEqual( dateTimeArray, { 742 year: '2599', 743 month: '08', 744 hour: '18', 745 minute: '12', 746 second: '13', 747 day: '06' 748 } ); 749 750 dateTimeArrayInampm = control.parseDateTime( datetime, true ); 751 assert.deepEqual( dateTimeArrayInampm, { 752 year: '2599', 753 month: '08', 754 hour: '6', 755 minute: '12', 756 ampm: 'pm', 757 day: '06' 758 } ); 759 760 year( '2010' ); 761 month( '12' ); 762 day( '18' ); 763 hour( '3' ); 764 minute( '44' ); 765 ampm( 'am' ); 766 767 // Test control.convertInputDateToString(). 768 timeString = control.convertInputDateToString(); 769 assert.equal( timeString, '2010-12-18 03:44:00' ); 770 771 ampm( 'pm' ); 772 timeString = control.convertInputDateToString(); 773 assert.equal( timeString, '2010-12-18 15:44:00' ); 774 775 // Test control.updateDaysForMonth();. 776 year( 2017 ); 777 month( 2 ); 778 day( 31 ); 779 control.updateDaysForMonth(); 780 assert.deepEqual( day(), 28, 'Should update to the correct days' ); 781 782 day( 20 ); 783 assert.deepEqual( day(), 20, 'Should not update if its less the correct number of days' ); 784 785 // Test control.convertHourToTwentyFourHourFormat(). 786 assert.equal( control.convertHourToTwentyFourHourFormat( 11, 'pm' ), 23 ); 787 assert.equal( control.convertHourToTwentyFourHourFormat( 12, 'pm' ), 12 ); 788 assert.equal( control.convertHourToTwentyFourHourFormat( 12, 'am' ), 0 ); 789 assert.equal( control.convertHourToTwentyFourHourFormat( 11, 'am' ), 11 ); 790 791 // Test control.toggleFutureDateNotification(). 792 assert.deepEqual( control.toggleFutureDateNotification(), control ); 793 control.toggleFutureDateNotification( true ); 794 assert.ok( control.notifications.has( 'not_future_date' ) ); 795 control.toggleFutureDateNotification( false ); 796 assert.notOk( control.notifications.has( 'not_future_date' ) ); 797 798 // Test control.populateDateInputs(); 799 control.populateDateInputs(); 800 control.dateInputs.each( function() { 801 var node = jQuery( this ); 802 assert.equal( node.val(), control.inputElements[ node.data( 'component' ) ].get() ); 803 } ); 804 805 // Test control.validateInputs(); 806 hour( 33 ); 807 assert.ok( control.validateInputs() ); 808 hour( 10 ); 809 assert.notOk( control.validateInputs() ); 810 minute( 123 ); 811 assert.ok( control.validateInputs() ); 812 minute( 20 ); 813 assert.notOk( control.validateInputs() ); 814 815 // Test control.populateSetting(); 816 day( 2 ); 817 month( 11 ); 818 year( 2018 ); 819 hour( 4 ); 820 minute( 20 ); 821 ampm( 'pm' ); 822 control.populateSetting(); 823 assert.equal( control.setting(), '2018-11-02 16:20:00' ); 824 825 hour( 123 ); 826 control.populateSetting(); 827 assert.equal( control.setting(), '2018-11-02 16:20:00' ); // Should not update if invalid hour. 828 829 hour( 5 ); 830 control.populateSetting(); 831 assert.equal( control.setting(), '2018-11-02 17:20:00' ); 832 833 // Test control.isFutureDate(); 834 day( 2 ); 835 month( 11 ); 836 year( 2318 ); 837 hour( 4 ); 838 minute( 20 ); 839 ampm( 'pm' ); 840 assert.ok( control.isFutureDate() ); 841 842 year( 2016 ); 843 assert.notOk( control.isFutureDate() ); 844 845 /** 846 * Test control.updateMinutesForHour(). 847 * Run this at the end or else the above tests may fail. 848 */ 849 hour( 24 ); 850 minute( 32 ); 851 control.inputElements.ampm = false; // Because it works only when the time is twenty four hour format. 852 control.updateMinutesForHour(); 853 assert.deepEqual( minute(), 0 ); 854 855 // Tear Down. 856 wp.customize.control.remove( controlId ); 857 }); 858 859 module( 'Customize Sections: wp.customize.OuterSection' ); 860 test( 'Test OuterSection', function( assert ) { 861 var section, sectionId = 'test_outer_section', body = jQuery( 'body' ), 862 defaultSection, defaultSectionId = 'fixture-section'; 863 864 defaultSection = wp.customize.section( defaultSectionId ); 865 866 section = new wp.customize.OuterSection( sectionId, { 867 params: { 868 content: defaultSection.params.content, 869 type: 'outer' 870 } 871 } ); 872 873 wp.customize.section.add( sectionId, section ); 874 wp.customize.section.add( defaultSectionId, section ); 875 876 assert.equal( section.containerPaneParent, '.customize-outer-pane-parent' ); 877 assert.equal( section.containerParent.selector, '#customize-outer-theme-controls' ); 878 879 defaultSection.expand(); 880 section.expand(); 881 assert.ok( body.hasClass( 'outer-section-open' ) ); 882 assert.ok( section.container.hasClass( 'open' ) ); 883 assert.ok( defaultSection.expanded() ); // Ensure it does not affect other sections state. 884 885 section.collapse(); 886 assert.notOk( body.hasClass( 'outer-section-open' ) ); 887 assert.notOk( section.container.hasClass( 'open' ) ); // Ensure it does not affect other sections state. 888 assert.ok( defaultSection.expanded() ); 889 890 // Tear down 891 wp.customize.section.remove( sectionId ); 892 }); 893 894 module( 'Customize Controls: PreviewLinkControl' ); 895 test( 'Test PreviewLinkControl creation and its methods', function( assert ) { 896 var section, sectionId = 'publish_settings', newLink; 897 898 section = wp.customize.section( sectionId ); 899 section.deferred.embedded.resolve(); 900 901 assert.expect( 9 ); 902 section.deferred.embedded.done( function() { 903 _.each( section.controls(), function( control ) { 904 if ( 'changeset_preview_link' === control.id ) { 905 assert.equal( control.templateSelector, 'customize-preview-link-control' ); 906 assert.equal( _.size( control.previewElements ), control.elements.length ); 907 908 // Test control.ready(). 909 newLink = 'http://example.org?' + wp.customize.settings.changeset.uuid; 910 control.setting.set( newLink ); 911 912 assert.equal( control.previewElements.input(), newLink ); 913 assert.equal( control.previewElements.link(), newLink ); 914 assert.equal( control.previewElements.link.element.attr( 'href' ), newLink ); 915 assert.equal( control.previewElements.link.element.attr( 'target' ), wp.customize.settings.changeset.uuid ); 916 917 // Test control.toggleSaveNotification(). 918 control.toggleSaveNotification( true ); 919 assert.ok( control.notifications.has( 'changes_not_saved' ) ); 920 control.toggleSaveNotification( false ); 921 assert.notOk( control.notifications.has( 'changes_not_saved' ) ); 922 923 // Test control.updatePreviewLink(). 924 control.updatePreviewLink(); 925 assert.equal( control.setting.get(), wp.customize.previewer.getFrontendPreviewUrl() ); 926 } 927 } ); 928 } ); 929 }); 678 930 });
Note: See TracChangeset
for help on using the changeset viewer.