Customize: Introduce drafting and scheduling for Customizer changesets.

* Incorporates code from the Customize Snapshots and Customize Posts feature plugins.
* Adds a new Publish Settings section for managing the changeset status, scheduled date, and frontend preview link.
* Updates Publish button to reflect the status selected in the Publish Settings (including Save Draft and Schedule).
* Deactivates the Themes section when a non-publish status selected, and deactivates the Publish Settings section when previewing a theme switch.
* Introduces an `outer` section type (`wp.customize.OuterSection` in JS) for the Publish Settings section to use and for available widgets and available nav menu panels to use in the future. These sections can be expanded while other sections are expanded.
* Introduces `WP_Customize_Date_Time_Control` in PHP and `wp.customize.DateTimeControl` in JS for managing a date/time value.
* Keeps track of scheduled time and proactively publish from the client when the time arrives, as opposed to waiting for WP Cron.
* Auto-publishes a scheduled changeset when attempting to access one that missed its schedule.
* Starts a new changeset if attempting to save a changeset that was previously publish.
* Adds `force` arg to `requestChangesetUpdate()` to force an update request even when there are no pending changes.
* Adds utils methods for `getCurrentTimestamp` and `getRemainingTime`.
* Adds new state values for `selectedChangesetStatus`, `changesetDate`, `selectedChangesetDate`.
* Fixes logic for when to short-circuit check to close Customizer when there are unsaved changes.
* Adds getter methods for `autosaved` and `branching` parameters, with the latter applying the `customize_changeset_branching` filter.
* Call to `establish_loaded_changeset` on the fly when `changeset_uuid()` is called if no changeset UUID was specififed.
* De-duplicates logic for dismissing auto-draft changesets.
* Includes unit tests.

Builds on [41597].
Props sayedwp, westonruter, melchoyce, JoshuaWold, folletto, stubgo, karmatosed, dlh, paaljoachim, afercia, johnregan3, utkarshpatel, valendesigns.
See #30937.
Fixes #39896, #28721, #39275.


git-svn-id: https://develop.svn.wordpress.org/trunk@41626 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Weston Ruter 2017-09-27 22:24:37 +00:00
parent 621a95a23f
commit a92bb89f4f
13 changed files with 2590 additions and 202 deletions

View File

@ -24,11 +24,94 @@ body {
color: #555d66;
}
#customize-header-actions .button-primary {
#customize-save-button-wrapper {
float: right;
margin-top: 9px;
}
#customize-save-button-wrapper .save {
float: left;
border-radius: 3px;
box-shadow: none; /* @todo Adjust box shadow based on the disable states of paired button. */
display: none; /* Shown when ready. */
margin-top: 0;
}
#customize-save-button-wrapper .save.has-next-sibling {
border-radius: 3px 0 0 3px;
}
#customize-outer-theme-controls-wrapper {
position: absolute;
top: 0;
bottom: 0;
left: -301px;
visibility: hidden;
overflow-x: hidden;
overflow-y: auto;
width: 300px;
margin: 0;
z-index: 4;
background: #eee;
transition: left .18s;
border-right: 1px solid #ddd;
}
.outer-section-open .wp-full-overlay.expanded {
margin-left: 300px;
}
#customize-theme-controls .control-section-outer {
display: none !important;
}
#customize-outer-theme-controls .accordion-section-content {
padding: 12px;
}
#customize-outer-theme-controls .accordion-section-content.open {
display: block;
}
.outer-section-open .wp-full-overlay.expanded #customize-outer-theme-controls-wrapper {
visibility: visible;
left: 0;
transition: left .18s;
}
.customize-outer-pane-parent {
margin: 0;
}
.outer-section-open .wp-full-overlay.expanded #customize-preview {
opacity: 0.4;
}
body.outer-section-open .wp-full-overlay.expanded .wp-full-overlay-main {
left: 300px;
}
#customize-outer-theme-controls li.notice {
padding-top: 8px;
padding-bottom: 8px;
margin-left: 0;
margin-bottom: 10px;
}
#publish-settings {
text-indent: 0;
border-radius: 0 3px 3px 0;
padding-left: 0;
padding-right: 0;
box-shadow: none; /* @todo Adjust box shadow based on the disable states of paired button. */
font-size: 14px;
width: 30px;
float: left;
display: none; /* Shown when ready. */
-webkit-transform: none;
transform: none;
margin-top: 0;
}
#customize-header-actions .spinner {
margin-top: 13px;
margin-right: 4px;
@ -53,10 +136,181 @@ body {
margin-bottom: 15px;
}
#customize-control-changeset_status label,
#customize-control-changeset_preview_link input {
background-color: #ffffff;
border-bottom: 1px solid #ddd;
box-sizing: content-box;
width: 100%;
margin-left: -12px;
padding-left: 12px;
padding-right: 12px;
}
#customize-controls .date-input:invalid {
border-color: red;
}
.date-time-fields .month-field {
width: 79px;
}
.date-time-fields .day-field,
.date-time-fields .hour-field,
.date-time-fields .minute-field {
width: 46px;
}
.date-time-fields .year-field {
width: 60px;
}
.date-time-fields .am-pm-field {
width: 53px;
}
#customize-control-changeset_status label {
padding-top: 10px;
padding-bottom: 10px;
font-weight: 500;
}
#customize-control-changeset_status label:first-of-type {
border-top: 1px solid #ddd;
}
#customize-control-changeset_status .customize-control-title {
margin-bottom: 6px;
}
#customize-control-changeset_status input {
margin-left: 0;
}
#customize-control-changeset_preview_link {
position: relative;
display: block;
}
.customize-copy-preview-link {
position: absolute;
bottom: 9px;
right: 0;
}
.customize-copy-preview-link:before,
.customize-copy-preview-link:after {
content: '';
height: 28px;
position: absolute;
background: #ffffff;
top: -1px;
}
.customize-copy-preview-link:before {
left: -10px;
width: 9px;
opacity: 0.75;
}
.customize-copy-preview-link:after {
left: -5px;
width: 4px;
opacity: 0.8;
}
#customize-control-changeset_preview_link input {
line-height: 2.5;
border-top: 1px solid #ddd;
border-left: none;
border-right: none;
text-indent: -999px;
color: white;
}
#customize-control-changeset_preview_link label {
position: relative;
display: block;
}
#customize-control-changeset_preview_link a.preview-control-element {
display: inline-block;
position: absolute;
white-space: nowrap;
overflow: hidden;
width: 217px;
bottom: 14px;
font-size: 14px;
text-decoration: none;
}
#customize-control-changeset_preview_link a.preview-control-element.disabled,
#customize-control-changeset_preview_link a.preview-control-element.disabled:active,
#customize-control-changeset_preview_link a.preview-control-element.disabled:focus,
#customize-control-changeset_preview_link a.preview-control-element.disabled:visited {
color: black;
opacity: 0.4;
cursor: default;
outline: none;
box-shadow: none;
}
#sub-accordion-section-publish_settings .customize-section-description-container {
display: none;
}
#customize-controls .customize-info.section-meta {
margin-bottom: 15px;
}
.date-time-fields {
padding-top: 10px;
padding-bottom:10px;
}
.date-time-fields label,
.date-time-fields .date-time-separator {
float: left;
margin-right:5px;
}
.date-time-fields .date-time-separator {
line-height: 2;
}
.date-time-fields .time-row {
padding-top: 12px;
}
.date-time-fields .date-timezone {
float: left;
line-height: 2.2;
text-decoration: none;
}
#customize-control-changeset_preview_link {
margin-top: 20px;
}
#customize-control-changeset_status {
margin-bottom: 0;
padding-bottom: 0;
}
#customize-control-changeset_scheduled_date {
box-sizing: content-box;
width: 100%;
margin-left: -12px;
padding: 12px 12px 18px;
background: #ffffff;
border-bottom: 1px solid #ddd;
margin-bottom: 0;
}
#customize-control-changeset_scheduled_date .customize-control-description {
font-style: normal;
}
#customize-controls .customize-info.is-in-view,
#customize-controls .customize-section-title.is-in-view {
position: absolute;
@ -105,6 +359,8 @@ body {
#customize-controls .customize-pane-child .customize-section-title h3,
#customize-controls .customize-pane-child h3.customize-section-title,
#customize-outer-theme-controls .customize-pane-child .customize-section-title h3,
#customize-outer-theme-controls .customize-pane-child h3.customize-section-title,
#customize-controls .customize-info .panel-title {
font-size: 20px;
font-weight: 200;
@ -150,6 +406,7 @@ body {
#customize-controls .customize-info .customize-panel-description,
#customize-controls .customize-info .customize-section-description,
#customize-outer-theme-controls .customize-info .customize-section-description,
#customize-controls .no-widget-areas-rendered-notice {
color: #555d66;
display: none;
@ -171,7 +428,8 @@ body {
margin-bottom: 0;
}
#customize-controls .customize-info .customize-section-description {
#customize-controls .customize-info .customize-section-description,
#customize-outer-theme-controls .customize-section-description {
margin-bottom: 15px;
}
@ -189,11 +447,13 @@ body {
padding-right: 30px;
}
#customize-theme-controls .control-section {
#customize-theme-controls .control-section,
#customize-outer-theme-controls .control-section {
border: none;
}
#customize-theme-controls .accordion-section-title {
#customize-theme-controls .accordion-section-title,
#customize-outer-theme-controls .accordion-section-title {
color: #555d66;
background-color: #fff;
border-bottom: 1px solid #ddd;
@ -209,12 +469,14 @@ body {
border-left: 4px solid #fff;
}
#customize-theme-controls .accordion-section-title:after {
#customize-theme-controls .accordion-section-title:after,
#customize-outer-theme-controls .accordion-section-title:after {
content: "\f345";
color: #a0a5aa;
}
#customize-theme-controls .accordion-section-content {
#customize-theme-controls .accordion-section-content,
#customize-outer-theme-controls .accordion-section-content {
color: #555d66;
background: transparent;
}
@ -222,6 +484,9 @@ body {
#customize-controls .control-section:hover > .accordion-section-title,
#customize-controls .control-section .accordion-section-title:hover,
#customize-controls .control-section.open .accordion-section-title,
#customize-outer-theme-controls .control-section .accordion-section-title:hover,
#customize-outer-theme-controls .control-section.open .accordion-section-title,
#customize-outer-theme-controls .control-section .accordion-section-title:focus,
#customize-controls .control-section .accordion-section-title:focus {
color: #0073aa;
background: #f3f3f5;
@ -242,7 +507,11 @@ body {
#customize-theme-controls .control-section:hover > .accordion-section-title:after,
#customize-theme-controls .control-section .accordion-section-title:hover:after,
#customize-theme-controls .control-section.open .accordion-section-title:after,
#customize-theme-controls .control-section .accordion-section-title:focus:after {
#customize-theme-controls .control-section .accordion-section-title:focus:after,
#customize-outer-theme-controls .control-section:hover > .accordion-section-title:after,
#customize-outer-theme-controls .control-section .accordion-section-title:hover:after,
#customize-outer-theme-controls .control-section.open .accordion-section-title:after,
#customize-outer-theme-controls .control-section .accordion-section-title:focus:after {
color: #0073aa;
}
@ -250,7 +519,8 @@ body {
border-bottom: 1px solid #eee;
}
#customize-theme-controls .control-section.open .accordion-section-title {
#customize-theme-controls .control-section.open .accordion-section-title,
#customize-outer-theme-controls .control-section.open .accordion-section-title {
border-bottom-color: #eee !important;
}
@ -828,6 +1098,10 @@ p.customize-section-description {
margin: 0;
}
.wp-full-overlay.collapsed #customize-controls #customize-notifications-area {
display: none !important;
}
#customize-controls #customize-notifications-area,
#customize-controls .customize-section-title > .customize-control-notifications-container,
#customize-controls .panel-meta > .customize-control-notifications-container {
@ -1119,18 +1393,60 @@ p.customize-section-description {
animation: dice-color-change 3s infinite;
}
@-webkit-keyframes dice-color-change {
0% { color: #d4b146; }
50% { color: #ef54b0; }
75% { color: #7190d3; }
100% { color: #d4b146; }
.button-see-me {
-webkit-animation: bounce .7s 1;
animation: bounce .7s 1;
-webkit-transform-origin: center bottom;
transform-origin: center bottom;
}
@keyframes dice-color-change {
0% { color: #d4b146; }
50% { color: #ef54b0; }
75% { color: #7190d3; }
100% { color: #d4b146; }
@-webkit-keyframes bounce {
from, 20%, 53%, 80%, to {
-webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
-webkit-transform: translate3d(0,0,0);
}
40%, 43% {
-webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -12px, 0);
}
70% {
-webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -6px, 0);
}
90% {
-webkit-transform: translate3d(0,-1px,0);
}
}
@keyframes bounce {
from, 20%, 53%, 80%, to {
-webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
40%, 43% {
-webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -12px, 0);
transform: translate3d(0, -12px, 0);
}
70% {
-webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -6px, 0);
transform: translate3d(0, -6px, 0);
}
90% {
-webkit-transform: translate3d(0,-1px,0);
transform: translate3d(0,-1px,0);
}
}
.customize-control-header .choice {
@ -1310,7 +1626,8 @@ p.customize-section-description {
}
#customize-controls .control-section-themes .accordion-section-title span.customize-action,
#customize-controls .customize-section-title span.customize-action {
#customize-controls .customize-section-title span.customize-action,
#customize-outer-theme-controls .customize-section-title span.customize-action {
font-size: 13px;
display: block;
font-weight: 400;
@ -1843,6 +2160,27 @@ body.adding-widget .add-new-widget:before,
line-height: 32px;
}
.customize-control .date-time-fields select {
height: 39px;
}
.date-time-fields .month-field {
width: 79px;
}
.date-time-fields .day-field,
.date-time-fields .hour-field,
.date-time-fields .minute-field {
width: 55px;
}
.date-time-fields .year-field {
width: 80px;
}
.date-time-fields .date-timezone {
line-height: 3.2;
}
.wp-core-ui.wp-customizer .button {
margin-top: 12px;
}
@ -1853,7 +2191,8 @@ body.adding-widget .add-new-widget:before,
width: 100%;
}
.wp-full-overlay.expanded {
.wp-full-overlay.expanded,
.outer-section-open .wp-full-overlay.expanded {
margin-left: 0;
}
@ -1931,12 +2270,17 @@ body.adding-widget .add-new-widget:before,
margin-top: 12px;
}
#customize-header-actions .button-primary {
margin-top: 6px;
#publish-settings {
height: 31px;
}
#customize-control-changeset_status label {
padding-top: 15px;
}
body.adding-widget div#available-widgets,
body.adding-menu-items div#available-menu-items {
body.adding-menu-items div#available-menu-items,
body.outer-section-open div#customize-outer-theme-controls-wrapper {
top: 46px;
left: 0;
z-index: 10;

View File

@ -27,14 +27,30 @@ if ( ! current_user_can( 'customize' ) ) {
global $wp_scripts, $wp_customize;
if ( $wp_customize->changeset_post_id() ) {
if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $wp_customize->changeset_post_id() ) ) {
$changeset_post = get_post( $wp_customize->changeset_post_id() );
if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $changeset_post->ID ) ) {
wp_die(
'<h1>' . __( 'Cheatin&#8217; uh?' ) . '</h1>' .
'<p>' . __( 'Sorry, you are not allowed to edit this changeset.' ) . '</p>',
403
);
}
if ( in_array( get_post_status( $wp_customize->changeset_post_id() ), array( 'publish', 'trash' ), true ) ) {
$missed_schedule = (
'future' === $changeset_post->post_status &&
get_post_time( 'G', true, $changeset_post ) < time()
);
if ( $missed_schedule ) {
wp_publish_post( $changeset_post->ID );
wp_die(
'<h1>' . __( 'Your scheduled changes just published' ) . '</h1>' .
'<p><a href="' . esc_url( remove_query_arg( 'changeset_uuid' ) ) . '">' . __( 'Customize New Changes' ) . '</a></p>',
200
);
}
if ( in_array( get_post_status( $changeset_post->ID ), array( 'publish', 'trash' ), true ) ) {
wp_die(
'<h1>' . __( 'Cheatin&#8217; uh?' ) . '</h1>' .
'<p>' . __( 'This changeset has already been published and cannot be further modified.' ) . '</p>' .
@ -132,14 +148,11 @@ do_action( 'customize_controls_print_scripts' );
<div class="wp-full-overlay expanded">
<form id="customize-controls" class="wrap wp-full-overlay-sidebar">
<div id="customize-header-actions" class="wp-full-overlay-header">
<?php
$save_text = $wp_customize->is_theme_active() ? __( 'Save &amp; Publish' ) : __( 'Save &amp; Activate' );
$save_attrs = array();
if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->publish_posts ) ) {
$save_attrs['style'] = 'display: none';
}
submit_button( $save_text, 'primary save', 'save', false, $save_attrs );
?>
<?php $save_text = $wp_customize->is_theme_active() ? __( 'Publish' ) : __( 'Activate &amp; Publish' ); ?>
<div id="customize-save-button-wrapper" class="customize-save-button-wrapper" >
<?php submit_button( $save_text, 'primary save', 'save', false ); ?>
<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>
</div>
<span class="spinner"></span>
<button type="button" class="customize-controls-preview-toggle">
<span class="controls"><?php _e( 'Customize' ); ?></span>
@ -203,6 +216,13 @@ do_action( 'customize_controls_print_scripts' );
</div>
</form>
<div id="customize-preview" class="wp-full-overlay-main"></div>
<div id="customize-sidebar-outer-content">
<div id="customize-outer-theme-controls-wrapper">
<div id="customize-outer-theme-controls">
<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>
</div>
</div>
</div>
<?php
/**

File diff suppressed because it is too large Load Diff

View File

@ -747,3 +747,8 @@ require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-auto-add
* WP_Customize_New_Menu_Control class.
*/
require_once( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-control.php' );
/**
* WP_Customize_Date_Time_Control class.
*/
require_once( ABSPATH . WPINC . '/customize/class-wp-customize-date-time-control.php' );

View File

@ -596,38 +596,10 @@ final class WP_Customize_Manager {
* @since 4.9.0
*/
public function establish_loaded_changeset() {
/**
* Filters whether or not changeset branching is allowed.
*
* By default in core, when changeset branching is not allowed, changesets will operate
* linearly in that only one saved changeset will exist at a time (with a 'draft' or
* 'future' status). This makes the Customizer operate in a way that is similar to going to
* "edit" to one existing post: all users will be making changes to the same post, and autosave
* revisions will be made for that post.
*
* By contrast, when changeset branching is allowed, then the model is like users going
* to "add new" for a page and each user makes changes independently of each other since
* they are all operating on their own separate pages, each getting their own separate
* initial auto-drafts and then once initially saved, autosave revisions on top of that
* user's specific post.
*
* Since linear changesets are deemed to be more suitable for the majority of WordPress users,
* they are the default. For WordPress sites that have heavy site management in the Customizer
* by multiple users then branching changesets should be enabled by means of this filter.
*
* @since 4.9.0
*
* @param bool $allow_branching Whether branching is allowed. If `false`, the default,
* then only one saved changeset exists at a time.
* @param WP_Customize_Manager $wp_customize Manager instance.
*/
$this->branching = apply_filters( 'customize_changeset_branching', $this->branching, $this );
if ( empty( $this->_changeset_uuid ) ) {
$changeset_uuid = null;
if ( ! $this->branching ) {
if ( ! $this->branching() ) {
$unpublished_changeset_posts = $this->get_changeset_posts( array(
'post_status' => array_diff( get_post_stati(), array( 'auto-draft', 'publish', 'trash', 'inherit', 'private' ) ),
'exclude_restore_dismissed' => false,
@ -751,6 +723,58 @@ final class WP_Customize_Manager {
return $this->settings_previewed;
}
/**
* Gets whether data from a changeset's autosaved revision should be loaded if it exists.
*
* @since 4.9.0
* @see WP_Customize_Manager::changeset_data()
*
* @return bool Is using autosaved changeset revision.
*/
public function autosaved() {
return $this->autosaved;
}
/**
* Whether the changeset branching is allowed.
*
* @since 4.9.0
* @see WP_Customize_Manager::establish_loaded_changeset()
*
* @return bool Is changeset branching.
*/
public function branching() {
/**
* Filters whether or not changeset branching is allowed.
*
* By default in core, when changeset branching is not allowed, changesets will operate
* linearly in that only one saved changeset will exist at a time (with a 'draft' or
* 'future' status). This makes the Customizer operate in a way that is similar to going to
* "edit" to one existing post: all users will be making changes to the same post, and autosave
* revisions will be made for that post.
*
* By contrast, when changeset branching is allowed, then the model is like users going
* to "add new" for a page and each user makes changes independently of each other since
* they are all operating on their own separate pages, each getting their own separate
* initial auto-drafts and then once initially saved, autosave revisions on top of that
* user's specific post.
*
* Since linear changesets are deemed to be more suitable for the majority of WordPress users,
* they are the default. For WordPress sites that have heavy site management in the Customizer
* by multiple users then branching changesets should be enabled by means of this filter.
*
* @since 4.9.0
*
* @param bool $allow_branching Whether branching is allowed. If `false`, the default,
* then only one saved changeset exists at a time.
* @param WP_Customize_Manager $wp_customize Manager instance.
*/
$this->branching = apply_filters( 'customize_changeset_branching', $this->branching, $this );
return $this->branching;
}
/**
* Get the changeset UUID.
*
@ -763,7 +787,7 @@ final class WP_Customize_Manager {
*/
public function changeset_uuid() {
if ( empty( $this->_changeset_uuid ) ) {
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.
$this->establish_loaded_changeset();
}
return $this->_changeset_uuid;
}
@ -980,6 +1004,30 @@ final class WP_Customize_Manager {
return get_posts( $args );
}
/**
* Dismiss all of the current user's auto-drafts (other than the present one).
*
* @since 4.9.0
* @return int The number of auto-drafts that were dismissed.
*/
protected function dismiss_user_auto_draft_changesets() {
$changeset_autodraft_posts = $this->get_changeset_posts( array(
'post_status' => 'auto-draft',
'exclude_restore_dismissed' => true,
'posts_per_page' => -1,
) );
$dismissed = 0;
foreach ( $changeset_autodraft_posts as $autosave_autodraft_post ) {
if ( $autosave_autodraft_post->ID === $this->changeset_post_id() ) {
continue;
}
if ( update_post_meta( $autosave_autodraft_post->ID, '_customize_restore_dismissed', true ) ) {
$dismissed++;
}
}
return $dismissed;
}
/**
* Get the changeset post id for the loaded changeset.
*
@ -1050,7 +1098,7 @@ final class WP_Customize_Manager {
if ( ! $changeset_post_id ) {
$this->_changeset_data = array();
} else {
if ( $this->autosaved ) {
if ( $this->autosaved() ) {
$autosave_post = wp_get_post_autosave( $changeset_post_id );
if ( $autosave_post ) {
$data = $this->get_changeset_post_data( $autosave_post->ID );
@ -1972,7 +2020,7 @@ final class WP_Customize_Manager {
$settings = array(
'changeset' => array(
'uuid' => $this->changeset_uuid(),
'autosaved' => $this->autosaved,
'autosaved' => $this->autosaved(),
),
'timeouts' => array(
'selectiveRefresh' => 250,
@ -2345,28 +2393,24 @@ final class WP_Customize_Manager {
}
} else {
$response = $r;
$changeset_post = get_post( $this->changeset_post_id() );
// Dismiss all other auto-draft changeset posts for this user (they serve like autosave revisions), as there should only be one.
if ( $is_new_changeset ) {
$changeset_autodraft_posts = $this->get_changeset_posts( array(
'post_status' => 'auto-draft',
'exclude_restore_dismissed' => true,
'posts_per_page' => -1,
) );
foreach ( $changeset_autodraft_posts as $autosave_autodraft_post ) {
if ( $autosave_autodraft_post->ID !== $this->changeset_post_id() ) {
update_post_meta( $autosave_autodraft_post->ID, '_customize_restore_dismissed', true );
}
}
$this->dismiss_user_auto_draft_changesets();
}
// Note that if the changeset status was publish, then it will get set to trash if revisions are not supported.
$response['changeset_status'] = get_post_status( $this->changeset_post_id() );
$response['changeset_status'] = $changeset_post->post_status;
if ( $is_publish && 'trash' === $response['changeset_status'] ) {
$response['changeset_status'] = 'publish';
}
if ( 'publish' === $response['changeset_status'] ) {
if ( 'future' === $response['changeset_status'] ) {
$response['changeset_date'] = $changeset_post->post_date;
}
if ( 'publish' === $response['changeset_status'] || 'trash' === $response['changeset_status'] ) {
$response['next_changeset_uuid'] = wp_generate_uuid4();
}
}
@ -2434,7 +2478,13 @@ final class WP_Customize_Manager {
if ( $changeset_post_id ) {
$existing_status = get_post_status( $changeset_post_id );
if ( 'publish' === $existing_status || 'trash' === $existing_status ) {
return new WP_Error( 'changeset_already_published' );
return new WP_Error(
'changeset_already_published',
__( 'The previous set of changes already been published. Please try saving your current set of changes again.' ),
array(
'next_changeset_uuid' => wp_generate_uuid4(),
)
);
}
$existing_changeset_data = $this->get_changeset_post_data( $changeset_post_id );
@ -2453,7 +2503,7 @@ final class WP_Customize_Manager {
if ( $args['date_gmt'] ) {
$is_future_dated = ( mysql2date( 'U', $args['date_gmt'], false ) > mysql2date( 'U', $now, false ) );
if ( ! $is_future_dated ) {
return new WP_Error( 'not_future_date' ); // Only future dates are allowed.
return new WP_Error( 'not_future_date', __( 'You must supply a future date to schedule.' ) ); // Only future dates are allowed.
}
if ( ! $this->is_theme_active() && ( 'future' === $args['status'] || $is_future_dated ) ) {
@ -2468,7 +2518,7 @@ final class WP_Customize_Manager {
// Fail if the new status is future but the existing post's date is not in the future.
$changeset_post = get_post( $changeset_post_id );
if ( mysql2date( 'U', $changeset_post->post_date_gmt, false ) <= mysql2date( 'U', $now, false ) ) {
return new WP_Error( 'not_future_date' );
return new WP_Error( 'not_future_date', __( 'You must supply a future date to schedule.' ) );
}
}
@ -3056,24 +3106,11 @@ final class WP_Customize_Manager {
$changeset_post_id = $this->changeset_post_id();
if ( empty( $changeset_post_id ) || 'auto-draft' === get_post_status( $changeset_post_id ) ) {
$changeset_autodraft_posts = $this->get_changeset_posts( array(
'post_status' => 'auto-draft',
'exclude_restore_dismissed' => true,
'posts_per_page' => -1,
) );
$dismissed = 0;
foreach ( $changeset_autodraft_posts as $autosave_autodraft_post ) {
if ( $autosave_autodraft_post->ID === $changeset_post_id ) {
continue;
}
if ( update_post_meta( $autosave_autodraft_post->ID, '_customize_restore_dismissed', true ) ) {
$dismissed++;
}
}
$dismissed = $this->dismiss_user_auto_draft_changesets();
if ( $dismissed > 0 ) {
wp_send_json_success( 'auto_draft_dismissed' );
} else {
wp_send_json_error( 'no_autosave_to_delete', 404 );
wp_send_json_error( 'no_auto_draft_to_delete', 404 );
}
} else {
$revision = wp_get_post_autosave( $changeset_post_id );
@ -3089,7 +3126,7 @@ final class WP_Customize_Manager {
wp_send_json_success( 'autosave_revision_deleted' );
}
} else {
wp_send_json_error( 'no_autosave_to_delete', 404 );
wp_send_json_error( 'no_autosave_revision_to_delete', 404 );
}
}
wp_send_json_error( 'unknown_error', 500 );
@ -3516,6 +3553,21 @@ final class WP_Customize_Manager {
<# } ); #>
</ul>
</script>
<script type="text/html" id="tmpl-customize-preview-link-control" >
<span class="customize-control-title">
<label><?php esc_html_e( 'Share Preview Link' ); ?></label>
</span>
<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>
<div class="customize-control-notifications-container"></div>
<div class="preview-link-wrapper">
<label>
<span class="screen-reader-text"><?php esc_html_e( 'Preview Link' ); ?></span>
<a class="preview-control-element" data-component="link" href="" target=""></a>
<input readonly class="preview-control-element" data-component="input" value="test" >
<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>
</label>
</div>
</script>
<?php
}
@ -3877,7 +3929,7 @@ final class WP_Customize_Manager {
$autosave_revision_post = null;
$autosave_autodraft_post = null;
$changeset_post_id = $this->changeset_post_id();
if ( ! $this->saved_starter_content_changeset && ! $this->autosaved ) {
if ( ! $this->saved_starter_content_changeset && ! $this->autosaved() ) {
if ( $changeset_post_id ) {
$autosave_revision_post = wp_get_post_autosave( $changeset_post_id );
} else {
@ -3893,15 +3945,25 @@ final class WP_Customize_Manager {
}
// Prepare Customizer settings to pass to JavaScript.
$changeset_post = null;
if ( $changeset_post_id ) {
$changeset_post = get_post( $changeset_post_id );
}
$settings = array(
'changeset' => array(
'uuid' => $this->changeset_uuid(),
'branching' => $this->branching,
'autosaved' => $this->autosaved,
'branching' => $this->branching(),
'autosaved' => $this->autosaved(),
'hasAutosaveRevision' => ! empty( $autosave_revision_post ),
'latestAutoDraftUuid' => $autosave_autodraft_post ? $autosave_autodraft_post->post_name : null,
'status' => $changeset_post_id ? get_post_status( $changeset_post_id ) : '',
'status' => $changeset_post ? $changeset_post->post_status : '',
'currentUserCanPublish' => current_user_can( get_post_type_object( 'customize_changeset' )->cap->publish_posts ),
'publishDate' => $changeset_post ? $changeset_post->post_date : '', // @todo Only if future status? Rename to just date?
),
'initialServerDate' => current_time( 'mysql', false ),
'initialServerTimestamp' => floor( microtime( true ) * 1000 ),
'initialClientTimestamp' => -1, // To be set with JS below.
'timeouts' => array(
'windowRefresh' => 250,
'changesetAutoSave' => AUTOSAVE_INTERVAL * 1000,
@ -3957,6 +4019,7 @@ final class WP_Customize_Manager {
?>
<script type="text/javascript">
var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
_wpCustomizeSettings.initialClientTimestamp = _.now();
_wpCustomizeSettings.controls = {};
_wpCustomizeSettings.settings = {};
<?php
@ -4047,6 +4110,54 @@ final class WP_Customize_Manager {
$this->register_control_type( 'WP_Customize_Site_Icon_Control' );
$this->register_control_type( 'WP_Customize_Theme_Control' );
$this->register_control_type( 'WP_Customize_Code_Editor_Control' );
$this->register_control_type( 'WP_Customize_Date_Time_Control' );
/* Publish Settings */
$this->add_section( 'publish_settings', array(
'title' => __( 'Publish Settings' ),
'priority' => 0,
'capability' => 'customize',
'type' => 'outer',
'active_callback' => array( $this, 'is_theme_active' ),
) );
/* Publish Settings Controls */
$status_choices = array(
'publish' => __( 'Publish' ),
'draft' => __( 'Save Draft' ),
'future' => __( 'Schedule' ),
);
if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->publish_posts ) ) {
unset( $status_choices['publish'] );
}
$this->add_control( 'changeset_status', array(
'section' => 'publish_settings',
'settings' => array(),
'type' => 'radio',
'label' => __( 'Action' ),
'choices' => $status_choices,
'capability' => 'customize',
) );
if ( $this->changeset_post_id() && 'future' === get_post_status( $this->changeset_post_id() ) ) {
$initial_date = get_the_time( 'Y-m-d H:i:s', $this->changeset_post_id() );
} else {
$initial_date = current_time( 'mysql', false );
}
$this->add_control( new WP_Customize_Date_Time_Control( $this, 'changeset_scheduled_date', array(
'section' => 'publish_settings',
'settings' => array(),
'type' => 'date_time',
'min_year' => date( 'Y' ),
'allow_past_date' => false,
'twelve_hour_format' => false !== stripos( get_option( 'time_format' ), 'a' ),
'description' => __( 'Schedule your customization changes to publish ("go live") at a future date.' ),
'capability' => 'customize',
'default_value' => $initial_date,
) ) );
/* Themes */

View File

@ -0,0 +1,257 @@
<?php
/**
* Customize API: WP_Customize_Date_Time_Control class
*
* @package WordPress
* @subpackage Customize
* @since 4.9.0
*/
/**
* Customize Date Time Control class.
*
* @since 4.9.0
*
* @see WP_Customize_Control
*/
class WP_Customize_Date_Time_Control extends WP_Customize_Control {
/**
* Customize control type.
*
* @since 4.9.0
* @var string
*/
public $type = 'date_time';
/**
* Minimum Year.
*
* @since 4.9.0
* @var integer
*/
public $min_year = 1000;
/**
* Maximum Year.
*
* @since 4.9.0
* @var integer
*/
public $max_year = 9999;
/**
* Allow past date, if set to false user can only select future date.
*
* @since 4.9.0
* @var boolean
*/
public $allow_past_date = true;
/**
* If set to false the control will appear in 24 hour format,
* the value will still be saved in Y-m-d H:i:s format.
*
* @since 4.9.0
* @var boolean
*/
public $twelve_hour_format = true;
/**
* Default date/time to be displayed in the control.
*
* @since 4.9.0
* @var string
*/
public $default_value;
/**
* Don't render the control's content - it's rendered with a JS template.
*
* @since 4.9.0
*/
public function render_content() {}
/**
* Export data to JS.
*
* @since 4.9.0
* @return array
*/
public function json() {
$data = parent::json();
$data['maxYear'] = intval( $this->max_year );
$data['minYear'] = intval( $this->min_year );
$data['allowPastDate'] = $this->allow_past_date ? true : false;
$data['twelveHourFormat'] = $this->twelve_hour_format ? true : false;
$data['defaultValue'] = $this->default_value;
return $data;
}
/**
* Renders a JS template for the content of date time control.
*
* @since 4.9.0
*/
public function content_template() {
$data = array_merge( $this->json(), $this->get_month_choices() );
$timezone_info = $this->get_timezone_info();
?>
<# _.defaults( data, <?php echo wp_json_encode( $data ); ?> ); #>
<span class="customize-control-title">
<label>{{ data.label }}</label>
</span>
<div class="customize-control-notifications-container"></div>
<span class="description customize-control-description">{{ data.description }}</span>
<div class="date-time-fields">
<div class="day-row">
<span class="title-day"><?php esc_html_e( 'Day' ); ?></span>
<div class="day-fields clear">
<label class="month-field">
<span class="screen-reader-text"><?php esc_html_e( 'Month' ); ?></span>
<select class="date-input month" data-component="month">
<# _.each( data.month_choices, function( choice ) {
if ( _.isObject( choice ) && ! _.isUndefined( choice.text ) && ! _.isUndefined( choice.value ) ) {
text = choice.text;
value = choice.value;
}
#>
<option value="{{ value }}" >
{{ text }}
</option>
<# } ); #>
</select>
</label>
<label class="day-field">
<span class="screen-reader-text"><?php esc_html_e( 'Day' ); ?></span>
<input type="number" size="2" maxlength="2" autocomplete="off" class="date-input day" data-component="day" min="1" max="31"" />
</label>
<span class="time-special-char date-time-separator">,</span>
<label class="year-field">
<span class="screen-reader-text"><?php esc_html_e( 'Year' ); ?></span>
<# var maxYearLength = String( data.maxYear ).length; #>
<input type="number" size="4" maxlength="{{ maxYearLength }}" autocomplete="off" class="date-input year" data-component="year" min="{{ data.minYear }}" max="{{ data.maxYear }}" />
</label>
</div>
</div>
<div class="time-row clear">
<span class="title-time"><?php esc_html_e( 'Time' ); ?></span>
<div class="time-fields clear">
<label class="hour-field">
<span class="screen-reader-text"><?php esc_html_e( 'Hour' ); ?></span>
<# var maxHour = data.twelveHourFormat ? 12 : 24; #>
<input type="number" size="2" maxlength="2" autocomplete="off" class="date-input hour" data-component="hour" min="1" max="{{ maxHour }}"" />
</label>
<span class="time-special-char date-time-separator">:</span>
<label class="minute-field">
<span class="screen-reader-text"><?php esc_html_e( 'Minute' ); ?></span>
<input type="number" size="2" maxlength="2" autocomplete="off" class="date-input minute" data-component="minute" min="0" max="59" />
</label>
<# if ( data.twelveHourFormat ) { #>
<label class="am-pm-field">
<span class="screen-reader-text"><?php esc_html_e( 'AM / PM' ); ?></span>
<select class="date-input" data-component="ampm">
<option value="am"><?php esc_html_e( 'AM' ); ?></option>
<option value="pm"><?php esc_html_e( 'PM' ); ?></option>
</select>
</label>
<# } #>
<abbr class="date-timezone" aria-label="<?php esc_attr_e( 'Timezone' ); ?>" title="<?php echo esc_attr( $timezone_info['description'] ); ?>"><?php echo esc_html( $timezone_info['abbr'] ); ?></abbr>
</div>
</div>
</div>
<?php
}
/**
* Generate options for the month Select.
*
* Based on touch_time().
*
* @since 4.9.0
* @see touch_time()
*
* @return array
*/
public function get_month_choices() {
global $wp_locale;
$months = array();
for ( $i = 1; $i < 13; $i++ ) {
$month_text = $wp_locale->get_month_abbrev( $wp_locale->get_month( $i ) );
/* translators: 1: month number (01, 02, etc.), 2: month abbreviation */
$months[ $i ]['text'] = sprintf( __( '%1$s-%2$s' ), $i, $month_text );
$months[ $i ]['value'] = $i;
}
return array(
'month_choices' => $months,
);
}
/**
* Get timezone info.
*
* @since 4.9.0
*
* @return array abbr and description.
*/
public function get_timezone_info() {
$tz_string = get_option( 'timezone_string' );
$timezone_info = array();
if ( $tz_string ) {
try {
$tz = new DateTimezone( $tz_string );
} catch ( Exception $e ) {
$tz = '';
}
if ( $tz ) {
$now = new DateTime( 'now', $tz );
$formatted_gmt_offset = sprintf( 'UTC%s', $this->format_gmt_offset( $tz->getOffset( $now ) / 3600 ) );
$tz_name = str_replace( '_', ' ', $tz->getName() );
$timezone_info['abbr'] = $now->format( 'T' );
/* translators: 1: timezone name, 2: timezone abbreviation, 3: gmt offset */
$timezone_info['description'] = sprintf( __( 'Timezone is %1$s (%2$s), currently %3$s.' ), $tz_name, $timezone_info['abbr'], $formatted_gmt_offset );
} else {
$timezone_info['description'] = '';
}
} else {
$formatted_gmt_offset = $this->format_gmt_offset( intval( get_option( 'gmt_offset', 0 ) ) );
$timezone_info['abbr'] = sprintf( 'UTC%s', $formatted_gmt_offset );
/* translators: %s: UTC offset */
$timezone_info['description'] = sprintf( __( 'Timezone is %s.' ), $timezone_info['abbr'] );
}
return $timezone_info;
}
/**
* Format GMT Offset.
*
* @since 4.9.0
* @see wp_timezone_choice()
*
* @param float $offset Offset in hours.
* @return string Formatted offset.
*/
public function format_gmt_offset( $offset ) {
if ( 0 <= $offset ) {
$formatted_offset = '+' . (string) $offset;
} else {
$formatted_offset = (string) $offset;
}
$formatted_offset = str_replace(
array( '.25', '.5', '.75' ),
array( ':15', ':30', ':45' ),
$formatted_offset
);
return $formatted_offset;
}
}

View File

@ -672,7 +672,7 @@
};
$( function() {
var bg, setValue;
var bg, setValue, handleUpdatedChangesetUuid;
api.settings = window._wpCustomizeSettings;
if ( ! api.settings ) {
@ -765,28 +765,39 @@
api.preview.send( 'scroll', $( window ).scrollTop() );
});
/**
* Handle update to changeset UUID.
*
* @param {string} uuid - UUID.
* @returns {void}
*/
handleUpdatedChangesetUuid = function( uuid ) {
api.settings.changeset.uuid = uuid;
// Update UUIDs in links and forms.
$( document.body ).find( 'a[href], area' ).each( function() {
api.prepareLinkPreview( this );
} );
$( document.body ).find( 'form' ).each( function() {
api.prepareFormPreview( this );
} );
/*
* Replace the UUID in the URL. Note that the wrapped history.replaceState()
* will handle injecting the current api.settings.changeset.uuid into the URL,
* so this is merely to trigger that logic.
*/
if ( history.replaceState ) {
history.replaceState( currentHistoryState, '', location.href );
}
};
api.preview.bind( 'changeset-uuid', handleUpdatedChangesetUuid );
api.preview.bind( 'saved', function( response ) {
if ( response.next_changeset_uuid ) {
api.settings.changeset.uuid = response.next_changeset_uuid;
// Update UUIDs in links and forms.
$( document.body ).find( 'a[href], area' ).each( function() {
api.prepareLinkPreview( this );
} );
$( document.body ).find( 'form' ).each( function() {
api.prepareFormPreview( this );
} );
/*
* Replace the UUID in the URL. Note that the wrapped history.replaceState()
* will handle injecting the current api.settings.changeset.uuid into the URL,
* so this is merely to trigger that logic.
*/
if ( history.replaceState ) {
history.replaceState( currentHistoryState, '', location.href );
}
handleUpdatedChangesetUuid( response.next_changeset_uuid );
}
api.trigger( 'saved', response );
} );

View File

@ -547,8 +547,18 @@ function wp_default_scripts( &$scripts ) {
$scripts->add( 'customize-views', "/wp-includes/js/customize-views.js", array( 'jquery', 'underscore', 'imgareaselect', 'customize-models', 'media-editor', 'media-views' ), false, 1 );
$scripts->add( 'customize-controls', "/wp-admin/js/customize-controls$suffix.js", array( 'customize-base', 'wp-a11y', 'wp-util' ), false, 1 );
did_action( 'init' ) && $scripts->localize( 'customize-controls', '_wpCustomizeControlsL10n', array(
'activate' => __( 'Save &amp; Activate' ),
'save' => __( 'Save &amp; Publish' ),
'activate' => __( 'Activate &amp; Publish' ),
'save' => __( 'Save &amp; Publish' ), // @todo Remove as not required.
'publish' => __( 'Publish' ),
'published' => __( 'Published' ),
'saveDraft' => __( 'Save Draft' ),
'draftSaved' => __( 'Draft Saved' ),
'updating' => __( 'Updating' ),
'schedule' => __( 'Schedule' ),
'scheduled' => __( 'Scheduled' ),
'invalid' => __( 'Invalid' ),
'saveBeforeShare' => __( 'Please save your changes in order to share the preview.' ),
'futureDateError' => __( 'You must supply a future date to schedule.' ),
'saveAlert' => __( 'The changes you made will be lost if you navigate away from this page.' ),
'saved' => __( 'Saved' ),
'cancel' => __( 'Cancel' ),
@ -563,7 +573,7 @@ function wp_default_scripts( &$scripts ) {
'serverSaveError' => __( 'Failed connecting to the server. Please try saving again.' ),
/* translators: placeholder is URL to the Customizer to load the autosaved version */
'autosaveNotice' => __( 'There is a more recent autosave of your changes than the one you are previewing. <a href="%s">Restore the autosave</a>' ),
'videoHeaderNotice' => __( 'This theme doesn\'t support video headers on this page. Navigate to the front page or another page that supports video headers.' ),
'videoHeaderNotice' => __( 'This theme doesn\'t support video headers on this page. Navigate to the front page or another page that supports video headers.' ),
// Used for overriding the file types allowed in plupload.
'allowedFiles' => __( 'Allowed Files' ),
'customCssError' => wp_array_slice_assoc(

View File

@ -165,7 +165,10 @@ class Tests_Ajax_CustomizeManager extends WP_Ajax_UnitTestCase {
$this->make_ajax_call( 'customize_save' );
$this->assertFalse( $this->_last_response_parsed['success'] );
$this->assertEquals( 'changeset_already_published', $this->_last_response_parsed['data']['code'] );
wp_update_post( array( 'ID' => $wp_customize->changeset_post_id(), 'post_status' => 'auto-draft' ) );
wp_update_post( array(
'ID' => $wp_customize->changeset_post_id(),
'post_status' => 'auto-draft',
) );
// User cannot edit.
$post_type_obj = get_post_type_object( 'customize_changeset' );
@ -222,8 +225,10 @@ class Tests_Ajax_CustomizeManager extends WP_Ajax_UnitTestCase {
$this->make_ajax_call( 'customize_save' );
$this->assertTrue( $this->_last_response_parsed['success'] );
$this->assertEquals( 'future', get_post_status( $wp_customize->changeset_post_id() ) );
wp_update_post( array( 'ID' => $wp_customize->changeset_post_id(), 'post_status' => 'auto-draft' ) );
wp_update_post( array(
'ID' => $wp_customize->changeset_post_id(),
'post_status' => 'auto-draft',
) );
}
/**
@ -254,7 +259,6 @@ class Tests_Ajax_CustomizeManager extends WP_Ajax_UnitTestCase {
function test_save_success_publish_create() {
$wp_customize = $this->set_up_valid_state();
// Successful future.
$_POST['customize_changeset_status'] = 'publish';
$_POST['customize_changeset_title'] = 'Success Changeset';
$_POST['customize_changeset_data'] = wp_json_encode( array(
@ -268,9 +272,9 @@ class Tests_Ajax_CustomizeManager extends WP_Ajax_UnitTestCase {
$this->assertEquals( 'publish', $this->_last_response_parsed['data']['changeset_status'] );
$this->assertArrayHasKey( 'next_changeset_uuid', $this->_last_response_parsed['data'] );
$this->assertTrue( wp_is_uuid( $this->_last_response_parsed['data']['next_changeset_uuid'], 4 ) );
$this->assertEquals( 'Success Changeset', get_post( $wp_customize->changeset_post_id() )->post_title );
$this->assertEquals( 'Successful Site Title', get_option( 'blogname' ) );
}
/**
@ -295,7 +299,6 @@ class Tests_Ajax_CustomizeManager extends WP_Ajax_UnitTestCase {
) );
$wp_customize = $this->set_up_valid_state( $uuid );
// Successful future.
$_POST['customize_changeset_status'] = 'publish';
$_POST['customize_changeset_title'] = 'Published';
$this->make_ajax_call( 'customize_save' );
@ -304,6 +307,7 @@ class Tests_Ajax_CustomizeManager extends WP_Ajax_UnitTestCase {
$this->assertEquals( 'publish', $this->_last_response_parsed['data']['changeset_status'] );
$this->assertArrayHasKey( 'next_changeset_uuid', $this->_last_response_parsed['data'] );
$this->assertTrue( wp_is_uuid( $this->_last_response_parsed['data']['next_changeset_uuid'], 4 ) );
$this->assertEquals( 'New Site Title', get_option( 'blogname' ) );
$this->assertEquals( 'Published', get_post( $post_id )->post_title );
}
@ -336,6 +340,7 @@ class Tests_Ajax_CustomizeManager extends WP_Ajax_UnitTestCase {
$_POST['customize_changeset_date'] = $future_date;
$this->make_ajax_call( 'customize_save' );
$this->assertTrue( $this->_last_response_parsed['success'] );
$this->assertArrayHasKey( 'changeset_date', $this->_last_response_parsed['data'] );
$changeset_post_schedule = get_post( $post_id );
$this->assertEquals( $future_date, $changeset_post_schedule->post_date );
@ -344,6 +349,7 @@ class Tests_Ajax_CustomizeManager extends WP_Ajax_UnitTestCase {
$_POST['customize_changeset_status'] = 'draft';
$this->make_ajax_call( 'customize_save' );
$this->assertTrue( $this->_last_response_parsed['success'] );
$this->assertArrayNotHasKey( 'changeset_date', $this->_last_response_parsed['data'] );
$changeset_post_draft = get_post( $post_id );
$this->assertEquals( $future_date, $changeset_post_draft->post_date );
@ -351,6 +357,7 @@ class Tests_Ajax_CustomizeManager extends WP_Ajax_UnitTestCase {
$_POST['customize_changeset_status'] = 'future';
$this->make_ajax_call( 'customize_save' );
$this->assertTrue( $this->_last_response_parsed['success'] );
$this->assertArrayHasKey( 'changeset_date', $this->_last_response_parsed['data'] );
$changeset_post_schedule = get_post( $post_id );
$this->assertEquals( $future_date, $changeset_post_schedule->post_date );
// Success if draft with past date.
@ -380,7 +387,167 @@ class Tests_Ajax_CustomizeManager extends WP_Ajax_UnitTestCase {
$_POST['customize_changeset_status'] = 'publish';
$this->make_ajax_call( 'customize_save' );
$this->assertTrue( $this->_last_response_parsed['success'] );
$this->assertArrayHasKey( 'next_changeset_uuid', $this->_last_response_parsed['data'] );
$this->assertTrue( wp_is_uuid( $this->_last_response_parsed['data']['next_changeset_uuid'], 4 ) );
$changeset_post_publish = get_post( $post_id );
$this->assertNotEquals( $future_date, $changeset_post_publish->post_date );
// Check response when trying to update an already-published post.
$this->assertEquals( 'trash', get_post_status( $post_id ) );
$_POST['customize_changeset_status'] = 'publish';
$this->make_ajax_call( 'customize_save' );
$this->assertFalse( $this->_last_response_parsed['success'] );
$this->assertEquals( 'changeset_already_published', $this->_last_response_parsed['data']['code'] );
$this->assertArrayHasKey( 'next_changeset_uuid', $this->_last_response_parsed['data'] );
$this->assertTrue( wp_is_uuid( $this->_last_response_parsed['data']['next_changeset_uuid'], 4 ) );
}
/**
* Test WP_Customize_Manager::save().
*
* @ticket 39896
* @covers WP_Customize_Manager::save()
*/
public function test_save_autosave() {
$uuid = wp_generate_uuid4();
$post_id = $this->factory()->post->create( array(
'post_name' => $uuid,
'post_type' => 'customize_changeset',
'post_status' => 'draft',
'post_content' => wp_json_encode( array(
'blogname' => array(
'value' => 'New Site Title',
),
) ),
) );
$this->set_up_valid_state( $uuid );
$this->assertFalse( wp_get_post_autosave( $post_id ) );
$_POST['customize_changeset_data'] = wp_json_encode( array(
'blogname' => array(
'value' => 'Autosaved Site Title',
),
) );
$_POST['customize_changeset_autosave'] = 'on';
$this->make_ajax_call( 'customize_save' );
$this->assertTrue( $this->_last_response_parsed['success'] );
$this->assertEquals( 'draft', $this->_last_response_parsed['data']['changeset_status'] );
$autosave_revision = wp_get_post_autosave( $post_id );
$this->assertInstanceOf( 'WP_Post', $autosave_revision );
$this->assertContains( 'New Site Title', get_post( $post_id )->post_content );
$this->assertContains( 'Autosaved Site Title', $autosave_revision->post_content );
}
/**
* Test request for dismissing autosave changesets.
*
* @ticket 39896
* @covers WP_Customize_Manager::handle_dismiss_changeset_autosave_request()
* @covers WP_Customize_Manager::dismiss_user_auto_draft_changesets()
*/
public function test_handle_dismiss_changeset_autosave_request() {
$uuid = wp_generate_uuid4();
$wp_customize = $this->set_up_valid_state( $uuid );
$this->make_ajax_call( 'dismiss_customize_changeset_autosave' );
$this->assertFalse( $this->_last_response_parsed['success'] );
$this->assertEquals( 'invalid_nonce', $this->_last_response_parsed['data'] );
$nonce = wp_create_nonce( 'dismiss_customize_changeset_autosave' );
$_POST['nonce'] = $_GET['nonce'] = $_REQUEST['nonce'] = $nonce;
$this->make_ajax_call( 'dismiss_customize_changeset_autosave' );
$this->assertFalse( $this->_last_response_parsed['success'] );
$this->assertEquals( 'no_auto_draft_to_delete', $this->_last_response_parsed['data'] );
$other_user_id = $this->factory()->user->create();
// Create auto-drafts.
$user_auto_draft_ids = array();
for ( $i = 0; $i < 3; $i++ ) {
$user_auto_draft_ids[] = $this->factory()->post->create( array(
'post_name' => wp_generate_uuid4(),
'post_type' => 'customize_changeset',
'post_status' => 'auto-draft',
'post_author' => self::$admin_user_id,
'post_content' => wp_json_encode( array() ),
) );
}
$other_user_auto_draft_ids = array();
for ( $i = 0; $i < 3; $i++ ) {
$other_user_auto_draft_ids[] = $this->factory()->post->create( array(
'post_name' => wp_generate_uuid4(),
'post_type' => 'customize_changeset',
'post_status' => 'auto-draft',
'post_author' => $other_user_id,
'post_content' => wp_json_encode( array() ),
) );
}
foreach ( array_merge( $user_auto_draft_ids, $other_user_auto_draft_ids ) as $post_id ) {
$this->assertFalse( (bool) get_post_meta( $post_id, '_customize_restore_dismissed', true ) );
}
$this->make_ajax_call( 'dismiss_customize_changeset_autosave' );
$this->assertTrue( $this->_last_response_parsed['success'] );
$this->assertEquals( 'auto_draft_dismissed', $this->_last_response_parsed['data'] );
foreach ( $user_auto_draft_ids as $post_id ) {
$this->assertEquals( 'auto-draft', get_post_status( $post_id ) );
$this->assertTrue( (bool) get_post_meta( $post_id, '_customize_restore_dismissed', true ) );
}
foreach ( $other_user_auto_draft_ids as $post_id ) {
$this->assertEquals( 'auto-draft', get_post_status( $post_id ) );
$this->assertFalse( (bool) get_post_meta( $post_id, '_customize_restore_dismissed', true ) );
}
// Subsequent test results in none dismissed.
$this->make_ajax_call( 'dismiss_customize_changeset_autosave' );
$this->assertFalse( $this->_last_response_parsed['success'] );
$this->assertEquals( 'no_auto_draft_to_delete', $this->_last_response_parsed['data'] );
// Save a changeset as a draft.
$r = $wp_customize->save_changeset_post( array(
'data' => array(
'blogname' => array(
'value' => 'Foo',
),
),
'status' => 'draft',
) );
$this->assertNotInstanceOf( 'WP_Error', $r );
$this->assertFalse( wp_get_post_autosave( $wp_customize->changeset_post_id() ) );
$this->assertContains( 'Foo', get_post( $wp_customize->changeset_post_id() )->post_content );
// Since no autosave yet, confirm no action.
$this->make_ajax_call( 'dismiss_customize_changeset_autosave' );
$this->assertFalse( $this->_last_response_parsed['success'] );
$this->assertEquals( 'no_autosave_revision_to_delete', $this->_last_response_parsed['data'] );
// Add the autosave revision.
$r = $wp_customize->save_changeset_post( array(
'data' => array(
'blogname' => array(
'value' => 'Bar',
),
),
'autosave' => true,
) );
$this->assertNotInstanceOf( 'WP_Error', $r );
$autosave_revision = wp_get_post_autosave( $wp_customize->changeset_post_id() );
$this->assertInstanceOf( 'WP_Post', $autosave_revision );
$this->assertContains( 'Foo', get_post( $wp_customize->changeset_post_id() )->post_content );
$this->assertContains( 'Bar', $autosave_revision->post_content );
// Confirm autosave gets deleted.
$this->make_ajax_call( 'dismiss_customize_changeset_autosave' );
$this->assertTrue( $this->_last_response_parsed['success'] );
$this->assertEquals( 'autosave_revision_deleted', $this->_last_response_parsed['data'] );
$this->assertFalse( wp_get_post_autosave( $wp_customize->changeset_post_id() ) );
// Since no autosave yet, confirm no action.
$this->make_ajax_call( 'dismiss_customize_changeset_autosave' );
$this->assertFalse( $this->_last_response_parsed['success'] );
$this->assertEquals( 'no_autosave_revision_to_delete', $this->_last_response_parsed['data'] );
}
}

View File

@ -120,6 +120,13 @@ class Tests_WP_Customize_Manager extends WP_UnitTestCase {
$this->assertEquals( $uuid, $wp_customize->changeset_uuid() );
$this->assertEquals( $theme, $wp_customize->get_stylesheet() );
$this->assertEquals( $messenger_channel, $wp_customize->get_messenger_channel() );
$this->assertFalse( $wp_customize->autosaved() );
$this->assertTrue( $wp_customize->branching() );
$wp_customize = new WP_Customize_Manager( array(
'changeset_uuid' => null,
) );
$this->assertTrue( wp_is_uuid( $wp_customize->changeset_uuid(), 4 ) );
$theme = 'twentyfourteen';
$messenger_channel = 'preview-456';
@ -133,7 +140,35 @@ class Tests_WP_Customize_Manager extends WP_UnitTestCase {
$_REQUEST['customize_theme'] = $theme;
$wp_customize = new WP_Customize_Manager();
$this->assertEquals( $theme, $wp_customize->get_stylesheet() );
$this->assertNotEmpty( $wp_customize->changeset_uuid() );
$this->assertTrue( wp_is_uuid( $wp_customize->changeset_uuid(), 4 ) );
}
/**
* Test constructor when deferring UUID.
*
* @ticket 39896
* @covers WP_Customize_Manager::establish_loaded_changeset()
* @covers WP_Customize_Manager::__construct()
*/
public function test_constructor_deferred_changeset_uuid() {
$data = array(
'blogname' => array(
'value' => 'Test',
),
);
$uuid = wp_generate_uuid4();
$post_id = $this->factory()->post->create( array(
'post_type' => 'customize_changeset',
'post_name' => $uuid,
'post_status' => 'draft',
'post_content' => wp_json_encode( $data ),
) );
$wp_customize = new WP_Customize_Manager( array(
'changeset_uuid' => false, // Cause UUID to be deferred.
'branching' => false, // To cause drafted changeset to be autoloaded.
) );
$this->assertEquals( $uuid, $wp_customize->changeset_uuid() );
$this->assertEquals( $post_id, $wp_customize->changeset_post_id() );
}
/**
@ -253,6 +288,45 @@ class Tests_WP_Customize_Manager extends WP_UnitTestCase {
$this->assertSame( true, $wp_customize->settings_previewed() );
}
/**
* Test WP_Customize_Manager::autosaved().
*
* @ticket 39896
* @covers WP_Customize_Manager::autosaved()
*/
public function test_autosaved() {
$wp_customize = new WP_Customize_Manager();
$this->assertFalse( $wp_customize->autosaved() );
$wp_customize = new WP_Customize_Manager( array( 'autosaved' => false ) );
$this->assertFalse( $wp_customize->autosaved() );
$wp_customize = new WP_Customize_Manager( array( 'autosaved' => true ) );
$this->assertTrue( $wp_customize->autosaved() );
}
/**
* Test WP_Customize_Manager::branching().
*
* @ticket 39896
* @covers WP_Customize_Manager::branching()
*/
public function test_branching() {
$wp_customize = new WP_Customize_Manager();
$this->assertTrue( $wp_customize->branching(), 'Branching should default to true since it is original behavior in 4.7.' );
$wp_customize = new WP_Customize_Manager( array( 'branching' => false ) );
$this->assertFalse( $wp_customize->branching() );
add_filter( 'customize_changeset_branching', '__return_true' );
$this->assertTrue( $wp_customize->branching() );
remove_filter( 'customize_changeset_branching', '__return_true' );
$wp_customize = new WP_Customize_Manager( array( 'branching' => true ) );
$this->assertTrue( $wp_customize->branching() );
add_filter( 'customize_changeset_branching', '__return_false' );
$this->assertFalse( $wp_customize->branching() );
}
/**
* Test WP_Customize_Manager::changeset_uuid().
*
@ -337,20 +411,56 @@ class Tests_WP_Customize_Manager extends WP_UnitTestCase {
* @covers WP_Customize_Manager::changeset_data()
*/
function test_changeset_data() {
wp_set_current_user( self::$admin_user_id );
$uuid = wp_generate_uuid4();
$wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) );
$this->assertEquals( array(), $wp_customize->changeset_data() );
$uuid = wp_generate_uuid4();
$data = array( 'blogname' => array( 'value' => 'Hello World' ) );
$data = array(
'blogname' => array( 'value' => 'Hello World' ),
'blogdescription' => array( 'value' => 'Greet the world' ),
);
$this->factory()->post->create( array(
'post_name' => $uuid,
'post_type' => 'customize_changeset',
'post_status' => 'auto-draft',
'post_status' => 'draft',
'post_content' => wp_json_encode( $data ),
) );
$wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) );
$this->assertEquals( $data, $wp_customize->changeset_data() );
// Autosave.
$wp_customize->set_post_value( 'blogname', 'Hola Mundo' );
$wp_customize->register_controls(); // That is, settings, so blogname setting is registered.
$r = $wp_customize->save_changeset_post( array(
'autosave' => true,
) );
$this->assertNotInstanceOf( 'WP_Error', $r );
// No change to data if not requesting autosave.
$wp_customize = new WP_Customize_Manager( array(
'changeset_uuid' => $uuid,
'autosaved' => false,
) );
$wp_customize->register_controls(); // That is, settings.
$this->assertFalse( $wp_customize->autosaved() );
$this->assertEquals( $data, $wp_customize->changeset_data() );
// No change to data if not requesting autosave.
$wp_customize = new WP_Customize_Manager( array(
'changeset_uuid' => $uuid,
'autosaved' => true,
) );
$this->assertTrue( $wp_customize->autosaved() );
$this->assertNotEquals( $data, $wp_customize->changeset_data() );
$this->assertEquals(
array_merge(
wp_list_pluck( $data, 'value' ),
array( 'blogname' => 'Hola Mundo' )
),
wp_list_pluck( $wp_customize->changeset_data(), 'value' )
);
}
/**
@ -1272,6 +1382,98 @@ class Tests_WP_Customize_Manager extends WP_UnitTestCase {
$this->assertEquals( $other_admin_user_id, $data['scratchpad']['user_id'] );
}
/**
* Test writing changesets when user supplies unchanged values.
*
* @ticket 39896
* @covers WP_Customize_Manager::save_changeset_post()
* @covers WP_Customize_Manager::grant_edit_post_capability_for_changeset()
*/
public function test_save_changeset_post_with_autosave() {
wp_set_current_user( self::$admin_user_id );
$uuid = wp_generate_uuid4();
$changeset_post_id = wp_insert_post( array(
'post_type' => 'customize_changeset',
'post_content' => wp_json_encode( array(
'blogname' => array(
'value' => 'Auto-draft Title',
),
) ),
'post_author' => self::$admin_user_id,
'post_name' => $uuid,
'post_status' => 'auto-draft',
) );
$wp_customize = new WP_Customize_Manager( array(
'changeset_uuid' => $uuid,
) );
$wp_customize->register_controls(); // And settings too.
// Autosave of an auto-draft overwrites original.
$wp_customize->save_changeset_post( array(
'data' => array(
'blogname' => array(
'value' => 'Autosaved Auto-draft Title',
),
),
'autosave' => true,
) );
$this->assertFalse( wp_get_post_autosave( $changeset_post_id ) );
$this->assertContains( 'Autosaved Auto-draft Title', get_post( $changeset_post_id )->post_content );
// Update status to draft for subsequent tests.
$wp_customize->save_changeset_post( array(
'data' => array(
'blogname' => array(
'value' => 'Draft Title',
),
),
'status' => 'draft',
'autosave' => false,
) );
$this->assertContains( 'Draft Title', get_post( $changeset_post_id )->post_content );
// Fail: illegal_autosave_with_date_gmt.
$r = $wp_customize->save_changeset_post( array(
'autosave' => true,
'date_gmt' => ( gmdate( 'Y' ) + 1 ) . '-12-01 00:00:00',
) );
$this->assertInstanceOf( 'WP_Error', $r );
$this->assertEquals( 'illegal_autosave_with_date_gmt', $r->get_error_code() );
// Fail: illegal_autosave_with_status.
$r = $wp_customize->save_changeset_post( array(
'autosave' => true,
'status' => 'pending',
) );
$this->assertEquals( 'illegal_autosave_with_status', $r->get_error_code() );
// Fail: illegal_autosave_with_non_current_user.
$r = $wp_customize->save_changeset_post( array(
'autosave' => true,
'user_id' => $this->factory()->user->create( array( 'role' => 'administrator' ) ),
) );
$this->assertEquals( 'illegal_autosave_with_non_current_user', $r->get_error_code() );
// Try autosave.
$this->assertFalse( wp_get_post_autosave( $changeset_post_id ) );
$r = $wp_customize->save_changeset_post( array(
'data' => array(
'blogname' => array(
'value' => 'Autosave Title',
),
),
'autosave' => true,
) );
$this->assertInternalType( 'array', $r );
// Verify that autosave happened.
$autosave_revision = wp_get_post_autosave( $changeset_post_id );
$this->assertInstanceOf( 'WP_Post', $autosave_revision );
$this->assertContains( 'Draft Title', get_post( $changeset_post_id )->post_content );
$this->assertContains( 'Autosave Title', $autosave_revision->post_content );
}
/**
* Test passing `null` for a setting ID to remove it from the changeset.
*
@ -2351,10 +2553,24 @@ class Tests_WP_Customize_Manager extends WP_UnitTestCase {
$data = json_decode( $json, true );
$this->assertNotEmpty( $data );
$this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'changeset', 'timeouts' ), array_keys( $data ) );
$this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'changeset', 'timeouts', 'initialClientTimestamp', 'initialServerDate', 'initialServerTimestamp' ), array_keys( $data ) );
$this->assertEquals( $autofocus, $data['autofocus'] );
$this->assertArrayHasKey( 'save', $data['nonce'] );
$this->assertArrayHasKey( 'preview', $data['nonce'] );
$this->assertEqualSets(
array(
'branching',
'autosaved',
'hasAutosaveRevision',
'latestAutoDraftUuid',
'status',
'uuid',
'currentUserCanPublish',
'publishDate',
),
array_keys( $data['changeset'] )
);
}
/**

View File

@ -106,6 +106,14 @@ window._wpCustomizeSettings = {
'title': 'Fixture section of custom type re-using default template',
'type': 'reusing-default-template'
},
'publish_settings': {
'active': true,
'description': '',
'instanceNumber': 6,
'priority': 20,
'title': 'Fixture section of custom type re-using default template',
'type': 'outer'
},
'fixture-section-without-params': {}
},
'settings': {
@ -148,9 +156,18 @@ window._wpCustomizeSettings = {
'label': 'Enter mobile preview mode'
}
},
initialClientTimestamp: 1506510531595,
initialServerDate: '2017-09-27 16:38:49',
initialServerTimestamp: 1506510529913,
changeset: {
status: '',
uuid: '0c674ff4-c159-4e7a-beb4-cb830ae73979'
uuid: '0c674ff4-c159-4e7a-beb4-cb830ae73979',
autosaved: false,
branching: false,
currentUserCanPublish: false,
hasAutosaveRevision: false,
latestAutoDraftUuid: '341b06f6-3c1f-454f-96df-3cf197f3e347',
publishDate: ''
},
timeouts: {
windowRefresh: 250,

View File

@ -872,7 +872,88 @@
</div>
<# } #>
</script>
<script type="text/html" id="tmpl-customize-control-date_time-content">
<# _.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}}} ); #>
<span class="customize-control-title">
<label>{{ data.label }}</label>
</span>
<div class="customize-control-notifications-container"></div>
<span class="description customize-control-description">{{ data.description }}</span>
<div class="date-time-fields">
<div class="day-row">
<span class="title-day">Day</span>
<div class="day-fields clear">
<label class="month-field">
<span class="screen-reader-text">Month</span>
<select class="date-input month" data-component="month">
<# _.each( data.month_choices, function( choice ) {
if ( _.isObject( choice ) && ! _.isUndefined( choice.text ) && ! _.isUndefined( choice.value ) ) {
text = choice.text;
value = choice.value;
}
#>
<option value="{{ value }}" >
{{ text }}
</option>
<# } ); #>
</select>
</label>
<label class="day-field">
<span class="screen-reader-text">Day</span>
<input type="number" size="2" maxlength="2" autocomplete="off" class="date-input day" data-component="day" min="1" max="31"" />
</label>
<span class="time-special-char date-time-separator">,</span>
<label class="year-field">
<span class="screen-reader-text">Year</span>
<# var maxYearLength = String( data.maxYear ).length; #>
<input type="number" size="4" maxlength="{{ maxYearLength }}" autocomplete="off" class="date-input year" data-component="year" min="{{ data.minYear }}" max="{{ data.maxYear }}" />
</label>
</div>
</div>
<div class="time-row clear">
<span class="title-time">Time</span>
<div class="time-fields clear">
<label class="hour-field">
<span class="screen-reader-text">Hour</span>
<# var maxHour = data.twelveHourFormat ? 12 : 24; #>
<input type="number" size="2" maxlength="2" autocomplete="off" class="date-input hour" data-component="hour" min="1" max="{{ maxHour }}"" />
</label>
<span class="time-special-char date-time-separator">:</span>
<label class="minute-field">
<span class="screen-reader-text">Minute</span>
<input type="number" size="2" maxlength="2" autocomplete="off" class="date-input minute" data-component="minute" min="0" max="59" />
</label>
<# if ( data.twelveHourFormat ) { #>
<label class="am-pm-field">
<span class="screen-reader-text">AM / PM</span>
<select class="date-input" data-component="ampm">
<option value="am">AM</option>
<option value="pm">PM</option>
</select>
</label>
<# } #>
<abbr class="date-timezone" aria-label="Timezone" title="Timezone is Asia/Kolkata (IST), currently UTC+5:30.">IST</abbr>
</div>
</div>
</div>
</script>
<script type="text/html" id="tmpl-customize-preview-link-control" >
<span class="customize-control-title">
<label>Share Preview Link</label>
</span>
<span class="description customize-control-description">See how changes would look live on your website, and share the preview with people who can&#039;t access the Customizer.</span>
<div class="customize-control-notifications-container"></div>
<div class="preview-link-wrapper">
<label>
<span class="screen-reader-text">Preview Link</span>
<a class="preview-control-element" data-component="link" href="" target=""></a>
<input readonly class="preview-control-element" data-component="input" value="test" >
<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>
</label>
</div>
</script>
<script type="text/html" id="tmpl-media-modal">
<div class="media-modal wp-core-ui">
<button type="button" class="media-modal-close"><span class="media-modal-icon"><span class="screen-reader-text">Close media panel</span></span></button>

View File

@ -675,4 +675,256 @@ jQuery( window ).load( function (){
jQuery.ajaxSetup( { beforeSend: originalBeforeSetup } );
} );
} );
module( 'Customize Utils: wp.customize.utils.getRemainingTime()' );
test( 'utils.getRemainingTime calculates time correctly', function( assert ) {
var datetime = '2599-08-06 12:12:13', timeRemaining, timeRemainingWithDateInstance, timeRemaingingWithTimestamp;
timeRemaining = wp.customize.utils.getRemainingTime( datetime );
timeRemainingWithDateInstance = wp.customize.utils.getRemainingTime( new Date( datetime.replace( /-/g, '/' ) ) );
timeRemaingingWithTimestamp = wp.customize.utils.getRemainingTime( ( new Date( datetime.replace( /-/g, '/' ) ) ).getTime() );
assert.equal( typeof timeRemaining, 'number', timeRemaining );
assert.equal( typeof timeRemainingWithDateInstance, 'number', timeRemaining );
assert.equal( typeof timeRemaingingWithTimestamp, 'number', timeRemaining );
assert.deepEqual( timeRemaining, timeRemainingWithDateInstance );
assert.deepEqual( timeRemaining, timeRemaingingWithTimestamp );
});
module( 'Customize Utils: wp.customize.utils.getCurrentTimestamp()' );
test( 'utils.getCurrentTimestamp returns timestamp', function( assert ) {
var currentTimeStamp;
currentTimeStamp = wp.customize.utils.getCurrentTimestamp();
assert.equal( typeof currentTimeStamp, 'number' );
});
module( 'Customize Controls: wp.customize.DateTimeControl' );
test( 'Test DateTimeControl creation and its methods', function( assert ) {
var control, controlId = 'date_time', section, sectionId = 'fixture-section',
datetime = '2599-08-06 18:12:13', dateTimeArray, dateTimeArrayInampm, timeString,
day, year, month, minute, ampm, hour;
section = wp.customize.section( sectionId );
control = new wp.customize.DateTimeControl( controlId, {
params: {
section: section.id,
type: 'date_time',
content: '<li id="customize-control-' + controlId + '" class="customize-control"></li>',
defaultValue: datetime
}
} );
wp.customize.control.add( controlId, control );
// Test control creations.
assert.ok( control.templateSelector, '#customize-control-date_time-content' );
assert.ok( control.section(), sectionId );
assert.equal( _.size( control.inputElements ), control.elements.length );
assert.ok( control.setting(), datetime );
day = control.inputElements.day;
month = control.inputElements.month;
year = control.inputElements.year;
minute = control.inputElements.minute;
hour = control.inputElements.hour;
ampm = control.inputElements.ampm;
year( '23' );
assert.equal( typeof year(), 'number', 'Should always return integer' );
month( 'test' );
assert.notOk( month(), 'Should not accept text' );
// Test control.parseDateTime();
dateTimeArray = control.parseDateTime( datetime );
assert.deepEqual( dateTimeArray, {
year: '2599',
month: '08',
hour: '18',
minute: '12',
second: '13',
day: '06'
} );
dateTimeArrayInampm = control.parseDateTime( datetime, true );
assert.deepEqual( dateTimeArrayInampm, {
year: '2599',
month: '08',
hour: '6',
minute: '12',
ampm: 'pm',
day: '06'
} );
year( '2010' );
month( '12' );
day( '18' );
hour( '3' );
minute( '44' );
ampm( 'am' );
// Test control.convertInputDateToString().
timeString = control.convertInputDateToString();
assert.equal( timeString, '2010-12-18 03:44:00' );
ampm( 'pm' );
timeString = control.convertInputDateToString();
assert.equal( timeString, '2010-12-18 15:44:00' );
// Test control.updateDaysForMonth();.
year( 2017 );
month( 2 );
day( 31 );
control.updateDaysForMonth();
assert.deepEqual( day(), 28, 'Should update to the correct days' );
day( 20 );
assert.deepEqual( day(), 20, 'Should not update if its less the correct number of days' );
// Test control.convertHourToTwentyFourHourFormat().
assert.equal( control.convertHourToTwentyFourHourFormat( 11, 'pm' ), 23 );
assert.equal( control.convertHourToTwentyFourHourFormat( 12, 'pm' ), 12 );
assert.equal( control.convertHourToTwentyFourHourFormat( 12, 'am' ), 0 );
assert.equal( control.convertHourToTwentyFourHourFormat( 11, 'am' ), 11 );
// Test control.toggleFutureDateNotification().
assert.deepEqual( control.toggleFutureDateNotification(), control );
control.toggleFutureDateNotification( true );
assert.ok( control.notifications.has( 'not_future_date' ) );
control.toggleFutureDateNotification( false );
assert.notOk( control.notifications.has( 'not_future_date' ) );
// Test control.populateDateInputs();
control.populateDateInputs();
control.dateInputs.each( function() {
var node = jQuery( this );
assert.equal( node.val(), control.inputElements[ node.data( 'component' ) ].get() );
} );
// Test control.validateInputs();
hour( 33 );
assert.ok( control.validateInputs() );
hour( 10 );
assert.notOk( control.validateInputs() );
minute( 123 );
assert.ok( control.validateInputs() );
minute( 20 );
assert.notOk( control.validateInputs() );
// Test control.populateSetting();
day( 2 );
month( 11 );
year( 2018 );
hour( 4 );
minute( 20 );
ampm( 'pm' );
control.populateSetting();
assert.equal( control.setting(), '2018-11-02 16:20:00' );
hour( 123 );
control.populateSetting();
assert.equal( control.setting(), '2018-11-02 16:20:00' ); // Should not update if invalid hour.
hour( 5 );
control.populateSetting();
assert.equal( control.setting(), '2018-11-02 17:20:00' );
// Test control.isFutureDate();
day( 2 );
month( 11 );
year( 2318 );
hour( 4 );
minute( 20 );
ampm( 'pm' );
assert.ok( control.isFutureDate() );
year( 2016 );
assert.notOk( control.isFutureDate() );
/**
* Test control.updateMinutesForHour().
* Run this at the end or else the above tests may fail.
*/
hour( 24 );
minute( 32 );
control.inputElements.ampm = false; // Because it works only when the time is twenty four hour format.
control.updateMinutesForHour();
assert.deepEqual( minute(), 0 );
// Tear Down.
wp.customize.control.remove( controlId );
});
module( 'Customize Sections: wp.customize.OuterSection' );
test( 'Test OuterSection', function( assert ) {
var section, sectionId = 'test_outer_section', body = jQuery( 'body' ),
defaultSection, defaultSectionId = 'fixture-section';
defaultSection = wp.customize.section( defaultSectionId );
section = new wp.customize.OuterSection( sectionId, {
params: {
content: defaultSection.params.content,
type: 'outer'
}
} );
wp.customize.section.add( sectionId, section );
wp.customize.section.add( defaultSectionId, section );
assert.equal( section.containerPaneParent, '.customize-outer-pane-parent' );
assert.equal( section.containerParent.selector, '#customize-outer-theme-controls' );
defaultSection.expand();
section.expand();
assert.ok( body.hasClass( 'outer-section-open' ) );
assert.ok( section.container.hasClass( 'open' ) );
assert.ok( defaultSection.expanded() ); // Ensure it does not affect other sections state.
section.collapse();
assert.notOk( body.hasClass( 'outer-section-open' ) );
assert.notOk( section.container.hasClass( 'open' ) ); // Ensure it does not affect other sections state.
assert.ok( defaultSection.expanded() );
// Tear down
wp.customize.section.remove( sectionId );
});
module( 'Customize Controls: PreviewLinkControl' );
test( 'Test PreviewLinkControl creation and its methods', function( assert ) {
var section, sectionId = 'publish_settings', newLink;
section = wp.customize.section( sectionId );
section.deferred.embedded.resolve();
assert.expect( 9 );
section.deferred.embedded.done( function() {
_.each( section.controls(), function( control ) {
if ( 'changeset_preview_link' === control.id ) {
assert.equal( control.templateSelector, 'customize-preview-link-control' );
assert.equal( _.size( control.previewElements ), control.elements.length );
// Test control.ready().
newLink = 'http://example.org?' + wp.customize.settings.changeset.uuid;
control.setting.set( newLink );
assert.equal( control.previewElements.input(), newLink );
assert.equal( control.previewElements.link(), newLink );
assert.equal( control.previewElements.link.element.attr( 'href' ), newLink );
assert.equal( control.previewElements.link.element.attr( 'target' ), wp.customize.settings.changeset.uuid );
// Test control.toggleSaveNotification().
control.toggleSaveNotification( true );
assert.ok( control.notifications.has( 'changes_not_saved' ) );
control.toggleSaveNotification( false );
assert.notOk( control.notifications.has( 'changes_not_saved' ) );
// Test control.updatePreviewLink().
control.updatePreviewLink();
assert.equal( control.setting.get(), wp.customize.previewer.getFrontendPreviewUrl() );
}
} );
} );
});
});