diff options
-rw-r--r-- | src/wp-admin/css/customize-controls.css | 5 | ||||
-rw-r--r-- | src/wp-admin/css/themes.css | 129 | ||||
-rw-r--r-- | src/wp-admin/custom-background.php | 192 | ||||
-rw-r--r-- | src/wp-admin/js/custom-background.js | 16 | ||||
-rw-r--r-- | src/wp-admin/js/customize-controls.js | 144 | ||||
-rw-r--r-- | src/wp-includes/class-wp-customize-control.php | 3 | ||||
-rw-r--r-- | src/wp-includes/class-wp-customize-manager.php | 131 | ||||
-rw-r--r-- | src/wp-includes/customize/class-wp-customize-background-image-control.php | 2 | ||||
-rw-r--r-- | src/wp-includes/customize/class-wp-customize-background-position-control.php | 87 | ||||
-rw-r--r-- | src/wp-includes/js/customize-preview.js | 9 | ||||
-rw-r--r-- | src/wp-includes/theme.php | 49 |
11 files changed, 658 insertions, 109 deletions
diff --git a/src/wp-admin/css/customize-controls.css b/src/wp-admin/css/customize-controls.css index adbd862be8..f2fbc86ef4 100644 --- a/src/wp-admin/css/customize-controls.css +++ b/src/wp-admin/css/customize-controls.css @@ -1090,6 +1090,11 @@ p.customize-section-description { float: right; } +/* Background position control */ +.customize-control-background_position .background-position-control .button-group { + display: block; +} + /** * Custom CSS Section * diff --git a/src/wp-admin/css/themes.css b/src/wp-admin/css/themes.css index 885f3a0755..42721d2bee 100644 --- a/src/wp-admin/css/themes.css +++ b/src/wp-admin/css/themes.css @@ -1176,6 +1176,135 @@ div#custom-background-image img { max-height: 300px; } +.background-position-control input[type="radio"]:checked ~ .button { + background: #eee; + border-color: #999; + -webkit-box-shadow: inset 0 2px 5px -3px rgba( 0, 0, 0, .5 ); + box-shadow: inset 0 2px 5px -3px rgba( 0, 0, 0, .5 ); + z-index: 1; +} + +.background-position-control input[type="radio"]:focus ~ .button { + border-color: #5b9dd9; + -webkit-box-shadow: inset 0 2px 5px -3px rgba( 0, 0, 0, .5 ), 0 0 3px rgba( 0, 115, 170, .8 ); + box-shadow: inset 0 2px 5px -3px rgba( 0, 0, 0, .5 ), 0 0 3px rgba( 0, 115, 170, .8 ); + color: #23282d; +} + +.background-position-control .background-position-center-icon, +.background-position-control .background-position-center-icon:before { + display: inline-block; + line-height: 1; + text-align: center; + -webkit-transition: background-color .1s ease-in 0; + transition: background-color .1s ease-in 0; +} + +.background-position-control .background-position-center-icon { + height: 20px; + margin-top: 13px; + vertical-align: top; + width: 20px; +} + +.background-position-control .background-position-center-icon:before { + background-color: #555; + -webkit-border-radius: 50%; + border-radius: 50%; + content: ""; + height: 12px; + width: 12px; +} + +.background-position-control .button:hover .background-position-center-icon:before, +.background-position-control input[type="radio"]:focus ~ .button .background-position-center-icon:before { + background-color: #23282d; +} + +.background-position-control .button-group { + display: block; +} + +.background-position-control .button-group .button { + -webkit-border-radius: 0; + border-radius: 0; + -webkit-box-shadow: none; + box-shadow: none; + /* Following properties are overridden by buttons responsive styles (see: wp-includes/css/buttons.css). */ + height: 40px !important; + line-height: 37px !important; + margin: 0 -1px 0 0 !important; + padding: 0 10px 1px !important; + position: relative; +} + +.background-position-control .button-group .button:active, +.background-position-control .button-group .button:hover, +.background-position-control .button-group .button:focus { + z-index: 1; +} + +.background-position-control .button-group:last-child .button { + -webkit-box-shadow: 0 1px 0 #ccc; + box-shadow: 0 1px 0 #ccc; +} + +.background-position-control .button-group > label { + margin: 0 !important; +} + +.background-position-control .button-group:first-child > label:first-child .button { + -webkit-border-radius: 3px 0 0; + border-radius: 3px 0 0; +} + +.background-position-control .button-group:first-child > label:first-child .dashicons { + -webkit-transform: rotate( 45deg ); + -ms-transform: rotate( 45deg ); + transform: rotate( 45deg ); +} + +.background-position-control .button-group:first-child > label:last-child .button { + -webkit-border-radius: 0 3px 0 0; + border-radius: 0 3px 0 0; +} + +.background-position-control .button-group:first-child > label:last-child .dashicons { + -webkit-transform: rotate( -45deg ); + -ms-transform: rotate( -45deg ); + transform: rotate( -45deg ); +} + +.background-position-control .button-group:last-child > label:first-child .button { + -webkit-border-radius: 0 0 0 3px; + border-radius: 0 0 0 3px; +} + +.background-position-control .button-group:last-child > label:first-child .dashicons { + -webkit-transform: rotate( -45deg ); + -ms-transform: rotate( -45deg ); + transform: rotate( -45deg ); +} + +.background-position-control .button-group:last-child > label:last-child .button { + -webkit-border-radius: 0 0 3px 0; + border-radius: 0 0 3px 0; +} + +.background-position-control .button-group:last-child > label:last-child .dashicons { + -webkit-transform: rotate( 45deg ); + -ms-transform: rotate( 45deg ); + transform: rotate( 45deg ); +} + +.background-position-control .button-group .dashicons { + margin-top: 9px; +} + +.background-position-control .button-group + .button-group { + margin-top: -1px; +} + /*------------------------------------------------------------------------------ 23.0 - Full Overlay w/ Sidebar ------------------------------------------------------------------------------*/ diff --git a/src/wp-admin/custom-background.php b/src/wp-admin/custom-background.php index a2dcfa8964..bea4084c30 100644 --- a/src/wp-admin/custom-background.php +++ b/src/wp-admin/custom-background.php @@ -133,31 +133,73 @@ class Custom_Background { return; } - if ( isset($_POST['background-repeat']) ) { - check_admin_referer('custom-background'); - if ( in_array($_POST['background-repeat'], array('repeat', 'no-repeat', 'repeat-x', 'repeat-y')) ) - $repeat = $_POST['background-repeat']; - else - $repeat = 'repeat'; - set_theme_mod('background_repeat', $repeat); + if ( isset( $_POST['background-preset'] ) ) { + check_admin_referer( 'custom-background' ); + + if ( in_array( $_POST['background-preset'], array( 'default', 'fill', 'fit', 'repeat', 'custom' ), true ) ) { + $preset = $_POST['background-preset']; + } else { + $preset = 'default'; + } + + set_theme_mod( 'background_preset', $preset ); } - if ( isset($_POST['background-position-x']) ) { - check_admin_referer('custom-background'); - if ( in_array($_POST['background-position-x'], array('center', 'right', 'left')) ) - $position = $_POST['background-position-x']; - else - $position = 'left'; - set_theme_mod('background_position_x', $position); + if ( isset( $_POST['background-position'] ) ) { + check_admin_referer( 'custom-background' ); + + $position = explode( ' ', $_POST['background-position'] ); + + if ( in_array( $position[0], array( 'left', 'center', 'right' ), true ) ) { + $position_x = $position[0]; + } else { + $position_x = 'left'; + } + + if ( in_array( $position[1], array( 'top', 'center', 'bottom' ), true ) ) { + $position_y = $position[1]; + } else { + $position_y = 'top'; + } + + set_theme_mod( 'background_position_x', $position_x ); + set_theme_mod( 'background_position_y', $position_y ); } - if ( isset($_POST['background-attachment']) ) { - check_admin_referer('custom-background'); - if ( in_array($_POST['background-attachment'], array('fixed', 'scroll')) ) - $attachment = $_POST['background-attachment']; - else - $attachment = 'fixed'; - set_theme_mod('background_attachment', $attachment); + if ( isset( $_POST['background-size'] ) ) { + check_admin_referer( 'custom-background' ); + + if ( in_array( $_POST['background-size'], array( 'auto', 'contain', 'cover' ), true ) ) { + $size = $_POST['background-size']; + } else { + $size = 'auto'; + } + + set_theme_mod( 'background_size', $size ); + } + + if ( isset( $_POST['background-repeat'] ) ) { + check_admin_referer( 'custom-background' ); + + $repeat = $_POST['background-repeat']; + + if ( 'no-repeat' !== $repeat ) { + $repeat = 'repeat'; + } + + set_theme_mod( 'background_repeat', $repeat ); + } + + if ( isset( $_POST['background-attachment'] ) ) { + check_admin_referer( 'custom-background' ); + + $attachment = $_POST['background-attachment']; + + if ( 'fixed' !== $attachment ) { + $attachment = 'scroll'; + } + + set_theme_mod( 'background_attachment', $attachment ); } if ( isset($_POST['background-color']) ) { @@ -219,11 +261,18 @@ class Custom_Background { $background_image_thumb = get_background_image(); if ( $background_image_thumb ) { $background_image_thumb = esc_url( set_url_scheme( get_theme_mod( 'background_image_thumb', str_replace( '%', '%%', $background_image_thumb ) ) ) ); + $background_position_x = get_theme_mod( 'background_position_x', get_theme_support( 'custom-background', 'default-position-x' ) ); + $background_position_y = get_theme_mod( 'background_position_y', get_theme_support( 'custom-background', 'default-position-y' ) ); + $background_size = get_theme_mod( 'background_size', get_theme_support( 'custom-background', 'default-size' ) ); + $background_repeat = get_theme_mod( 'background_repeat', get_theme_support( 'custom-background', 'default-repeat' ) ); + $background_attachment = get_theme_mod( 'background_attachment', get_theme_support( 'custom-background', 'default-attachment' ) ); // Background-image URL must be single quote, see below. - $background_styles .= ' background-image: url(\'' . $background_image_thumb . '\');' - . ' background-repeat: ' . get_theme_mod( 'background_repeat', get_theme_support( 'custom-background', 'default-repeat' ) ) . ';' - . ' background-position: top ' . get_theme_mod( 'background_position_x', get_theme_support( 'custom-background', 'default-position-x' ) ); + $background_styles .= " background-image: url('$background_image_thumb');" + . " background-size: $background_size;" + . " background-position: $background_position_x $background_position_y;" + . " background-repeat: $background_repeat;" + . " background-attachment: $background_attachment;"; } ?> <div id="custom-background-image" style="<?php echo $background_styles; ?>"><?php // must be double quote, see above ?> @@ -287,50 +336,81 @@ class Custom_Background { </tbody> </table> -<h3><?php _e('Display Options') ?></h3> +<h3><?php _e( 'Display Options' ); ?></h3> <form method="post"> <table class="form-table"> <tbody> <?php if ( get_background_image() ) : ?> +<input name="background-preset" type="hidden" value="custom"> + +<?php +$background_position = sprintf( + '%s %s', + get_theme_mod( 'background_position_x', get_theme_support( 'custom-background', 'default-position-x' ) ), + get_theme_mod( 'background_position_y', get_theme_support( 'custom-background', 'default-position-y' ) ) +); + +$background_position_options = array( + array( + 'left top' => array( 'label' => __( 'Top Left' ), 'icon' => 'dashicons dashicons-arrow-left-alt' ), + 'center top' => array( 'label' => __( 'Top' ), 'icon' => 'dashicons dashicons-arrow-up-alt' ), + 'right top' => array( 'label' => __( 'Top Right' ), 'icon' => 'dashicons dashicons-arrow-right-alt' ), + ), + array( + 'left center' => array( 'label' => __( 'Left' ), 'icon' => 'dashicons dashicons-arrow-left-alt' ), + 'center center' => array( 'label' => __( 'Center' ), 'icon' => 'background-position-center-icon' ), + 'right center' => array( 'label' => __( 'Right' ), 'icon' => 'dashicons dashicons-arrow-right-alt' ), + ), + array( + 'left bottom' => array( 'label' => __( 'Bottom Left' ), 'icon' => 'dashicons dashicons-arrow-left-alt' ), + 'center bottom' => array( 'label' => __( 'Bottom' ), 'icon' => 'dashicons dashicons-arrow-down-alt' ), + 'right bottom' => array( 'label' => __( 'Bottom Right' ), 'icon' => 'dashicons dashicons-arrow-right-alt' ), + ), +); +?> +<tr> +<th scope="row"><?php _e( 'Image Position' ); ?></th> +<td><fieldset><legend class="screen-reader-text"><span><?php _e( 'Image Position' ); ?></span></legend> +<div class="background-position-control"> +<?php foreach ( $background_position_options as $group ) : ?> + <div class="button-group"> + <?php foreach ( $group as $value => $input ) : ?> + <label> + <input class="screen-reader-text" name="background-position" type="radio" value="<?php echo esc_attr( $value ); ?>"<?php checked( $value, $background_position ); ?>> + <span class="button display-options position"><span class="<?php echo esc_attr( $input['icon'] ); ?>" aria-hidden="true"></span></span> + <span class="screen-reader-text"><?php echo $input['label']; ?></span> + </label> + <?php endforeach; ?> + </div> +<?php endforeach; ?> +</div> +</fieldset></td> +</tr> + <tr> -<th scope="row"><?php _e( 'Position' ); ?></th> -<td><fieldset><legend class="screen-reader-text"><span><?php _e( 'Background Position' ); ?></span></legend> -<label> -<input name="background-position-x" type="radio" value="left"<?php checked( 'left', get_theme_mod( 'background_position_x', get_theme_support( 'custom-background', 'default-position-x' ) ) ); ?> /> -<?php _e('Left') ?> -</label> -<label> -<input name="background-position-x" type="radio" value="center"<?php checked( 'center', get_theme_mod( 'background_position_x', get_theme_support( 'custom-background', 'default-position-x' ) ) ); ?> /> -<?php _e('Center') ?> -</label> -<label> -<input name="background-position-x" type="radio" value="right"<?php checked( 'right', get_theme_mod( 'background_position_x', get_theme_support( 'custom-background', 'default-position-x' ) ) ); ?> /> -<?php _e('Right') ?> -</label> +<th scope="row"><label for="background-size"><?php _e( 'Image Size' ); ?></label></th> +<td><fieldset><legend class="screen-reader-text"><span><?php _e( 'Image Size' ); ?></span></legend> +<select id="background-size" name="background-size"> +<option value="auto"<?php selected( 'auto', get_theme_mod( 'background_size', get_theme_support( 'custom-background', 'default-size' ) ) ); ?>><?php _ex( 'Original', 'Original Size' ); ?></option> +<option value="contain"<?php selected( 'contain', get_theme_mod( 'background_size', get_theme_support( 'custom-background', 'default-size' ) ) ); ?>><?php _e( 'Fit to Screen' ); ?></option> +<option value="cover"<?php selected( 'cover', get_theme_mod( 'background_size', get_theme_support( 'custom-background', 'default-size' ) ) ); ?>><?php _e( 'Fill Screen' ); ?></option> +</select> </fieldset></td> </tr> <tr> -<th scope="row"><?php _e( 'Repeat' ); ?></th> -<td><fieldset><legend class="screen-reader-text"><span><?php _e( 'Background Repeat' ); ?></span></legend> -<label><input type="radio" name="background-repeat" value="no-repeat"<?php checked( 'no-repeat', get_theme_mod( 'background_repeat', get_theme_support( 'custom-background', 'default-repeat' ) ) ); ?> /> <?php _e('No Repeat'); ?></label> - <label><input type="radio" name="background-repeat" value="repeat"<?php checked( 'repeat', get_theme_mod( 'background_repeat', get_theme_support( 'custom-background', 'default-repeat' ) ) ); ?> /> <?php _e('Tile'); ?></label> - <label><input type="radio" name="background-repeat" value="repeat-x"<?php checked( 'repeat-x', get_theme_mod( 'background_repeat', get_theme_support( 'custom-background', 'default-repeat' ) ) ); ?> /> <?php _e('Tile Horizontally'); ?></label> - <label><input type="radio" name="background-repeat" value="repeat-y"<?php checked( 'repeat-y', get_theme_mod( 'background_repeat', get_theme_support( 'custom-background', 'default-repeat' ) ) ); ?> /> <?php _e('Tile Vertically'); ?></label> +<th scope="row"><?php _ex( 'Repeat', 'Background Repeat' ); ?></th> +<td><fieldset><legend class="screen-reader-text"><span><?php _ex( 'Repeat', 'Background Repeat' ); ?></span></legend> +<input name="background-repeat" type="hidden" value="no-repeat"> +<label><input type="checkbox" name="background-repeat" value="repeat"<?php checked( 'repeat', get_theme_mod( 'background_repeat', get_theme_support( 'custom-background', 'default-repeat' ) ) ); ?>> <?php _e( 'Repeat Background Image' ); ?></label> </fieldset></td> </tr> <tr> -<th scope="row"><?php _ex( 'Attachment', 'Background Attachment' ); ?></th> -<td><fieldset><legend class="screen-reader-text"><span><?php _e( 'Background Attachment' ); ?></span></legend> -<label> -<input name="background-attachment" type="radio" value="scroll" <?php checked( 'scroll', get_theme_mod( 'background_attachment', get_theme_support( 'custom-background', 'default-attachment' ) ) ); ?> /> -<?php _e( 'Scroll' ); ?> -</label> -<label> -<input name="background-attachment" type="radio" value="fixed" <?php checked( 'fixed', get_theme_mod( 'background_attachment', get_theme_support( 'custom-background', 'default-attachment' ) ) ); ?> /> -<?php _e( 'Fixed' ); ?> -</label> +<th scope="row"><?php _ex( 'Scroll', 'Background Scroll' ); ?></th> +<td><fieldset><legend class="screen-reader-text"><span><?php _ex( 'Scroll', 'Background Scroll' ); ?></span></legend> +<input name="background-attachment" type="hidden" value="fixed"> +<label><input name="background-attachment" type="checkbox" value="scroll" <?php checked( 'scroll', get_theme_mod( 'background_attachment', get_theme_support( 'custom-background', 'default-attachment' ) ) ); ?>> <?php _e( 'Scroll with Page' ); ?></label> </fieldset></td> </tr> <?php endif; // get_background_image() ?> @@ -342,7 +422,7 @@ $default_color = ''; if ( current_theme_supports( 'custom-background', 'default-color' ) ) $default_color = ' data-default-color="#' . esc_attr( get_theme_support( 'custom-background', 'default-color' ) ) . '"'; ?> -<input type="text" name="background-color" id="background-color" value="#<?php echo esc_attr( get_background_color() ); ?>"<?php echo $default_color ?> /> +<input type="text" name="background-color" id="background-color" value="#<?php echo esc_attr( get_background_color() ); ?>"<?php echo $default_color ?>> </fieldset></td> </tr> </tbody> diff --git a/src/wp-admin/js/custom-background.js b/src/wp-admin/js/custom-background.js index 81fd59170c..c3f4e294f1 100644 --- a/src/wp-admin/js/custom-background.js +++ b/src/wp-admin/js/custom-background.js @@ -13,12 +13,20 @@ } }); - $('input[name="background-position-x"]').change(function() { - bgImage.css('background-position', $(this).val() + ' top'); + $( 'select[name="background-size"]' ).change( function() { + bgImage.css( 'background-size', $( this ).val() ); }); - $('input[name="background-repeat"]').change(function() { - bgImage.css('background-repeat', $(this).val()); + $( 'input[name="background-position"]' ).change( function() { + bgImage.css( 'background-position', $( this ).val() ); + }); + + $( 'input[name="background-repeat"]' ).change( function() { + bgImage.css( 'background-repeat', $( this ).is( ':checked' ) ? 'repeat' : 'no-repeat' ); + }); + + $( 'input[name="background-attachment"]' ).change( function() { + bgImage.css( 'background-attachment', $( this ).is( ':checked' ) ? 'scroll' : 'fixed' ); }); $('#choose-from-library-link').click( function( event ) { diff --git a/src/wp-admin/js/customize-controls.js b/src/wp-admin/js/customize-controls.js index 45fb65f138..886038eb93 100644 --- a/src/wp-admin/js/customize-controls.js +++ b/src/wp-admin/js/customize-controls.js @@ -3158,6 +3158,46 @@ }); /** + * A control for positioning a background image. + * + * @since 4.7.0 + * + * @class + * @augments wp.customize.Control + * @augments wp.customize.Class + */ + api.BackgroundPositionControl = api.Control.extend( { + + /** + * Set up control UI once embedded in DOM and settings are created. + * + * @since 4.7.0 + */ + ready: function() { + var control = this, updateRadios; + + control.container.on( 'change', 'input[name="background-position"]', function() { + var position = $( this ).val().split( ' ' ); + control.settings.x( position[0] ); + control.settings.y( position[1] ); + } ); + + updateRadios = _.debounce( function() { + var x, y, radioInput, inputValue; + x = control.settings.x.get(); + y = control.settings.y.get(); + inputValue = String( x ) + ' ' + String( y ); + radioInput = control.container.find( 'input[name="background-position"][value="' + inputValue + '"]' ); + radioInput.click(); + } ); + control.settings.x.bind( updateRadios ); + control.settings.y.bind( updateRadios ); + + updateRadios(); // Set initial UI. + } + } ); + + /** * A control for selecting and cropping an image. * * @class @@ -4507,15 +4547,16 @@ api.settingConstructor = {}; api.controlConstructor = { - color: api.ColorControl, - media: api.MediaControl, - upload: api.UploadControl, - image: api.ImageControl, - cropped_image: api.CroppedImageControl, - site_icon: api.SiteIconControl, - header: api.HeaderControl, - background: api.BackgroundControl, - theme: api.ThemeControl + color: api.ColorControl, + media: api.MediaControl, + upload: api.UploadControl, + image: api.ImageControl, + cropped_image: api.CroppedImageControl, + site_icon: api.SiteIconControl, + header: api.HeaderControl, + background: api.BackgroundControl, + background_position: api.BackgroundPositionControl, + theme: api.ThemeControl }; api.panelConstructor = { themes: api.ThemesPanel @@ -5531,7 +5572,7 @@ // Control visibility for default controls $.each({ 'background_image': { - controls: [ 'background_repeat', 'background_position_x', 'background_attachment' ], + controls: [ 'background_preset', 'background_position', 'background_size', 'background_repeat', 'background_attachment' ], callback: function( to ) { return !! to; } }, 'show_on_front': { @@ -5557,6 +5598,89 @@ }); }); + api.control( 'background_preset', function( control ) { + var visibility, defaultValues, values, toggleVisibility, updateSettings, preset; + + visibility = { // position, size, repeat, attachment + 'default': [ false, false, false, false ], + 'fill': [ true, false, false, false ], + 'fit': [ true, false, true, false ], + 'repeat': [ true, false, false, true ], + 'custom': [ true, true, true, true ] + }; + + defaultValues = [ + _wpCustomizeBackground.defaults['default-position-x'], + _wpCustomizeBackground.defaults['default-position-y'], + _wpCustomizeBackground.defaults['default-size'], + _wpCustomizeBackground.defaults['default-repeat'], + _wpCustomizeBackground.defaults['default-attachment'] + ]; + + values = { // position_x, position_y, size, repeat, attachment + 'default': defaultValues, + 'fill': [ 'left', 'top', 'cover', 'no-repeat', 'fixed' ], + 'fit': [ 'left', 'top', 'contain', 'no-repeat', 'fixed' ], + 'repeat': [ 'left', 'top', 'auto', 'repeat', 'scroll' ] + }; + + // @todo These should actually toggle the active state, but without the preview overriding the state in data.activeControls. + toggleVisibility = function( preset ) { + api.control( 'background_position' ).container.toggle( visibility[ preset ][0] ); + api.control( 'background_size' ).container.toggle( visibility[ preset ][1] ); + api.control( 'background_repeat' ).container.toggle( visibility[ preset ][2] ); + api.control( 'background_attachment' ).container.toggle( visibility[ preset ][3] ); + }; + + updateSettings = function( preset ) { + api( 'background_position_x' ).set( values[ preset ][0] ); + api( 'background_position_y' ).set( values[ preset ][1] ); + api( 'background_size' ).set( values[ preset ][2] ); + api( 'background_repeat' ).set( values[ preset ][3] ); + api( 'background_attachment' ).set( values[ preset ][4] ); + }; + + preset = control.setting.get(); + toggleVisibility( preset ); + + control.setting.bind( 'change', function( preset ) { + toggleVisibility( preset ); + if ( 'custom' !== preset ) { + updateSettings( preset ); + } + } ); + } ); + + api.control( 'background_repeat', function( control ) { + control.elements[0].unsync( api( 'background_repeat' ) ); + + control.element = new api.Element( control.container.find( 'input' ) ); + control.element.set( 'no-repeat' !== control.setting() ); + + control.element.bind( function( to ) { + control.setting.set( to ? 'repeat' : 'no-repeat' ); + } ); + + control.setting.bind( function( to ) { + control.element.set( 'no-repeat' !== to ); + } ); + } ); + + api.control( 'background_attachment', function( control ) { + control.elements[0].unsync( api( 'background_attachment' ) ); + + control.element = new api.Element( control.container.find( 'input' ) ); + control.element.set( 'fixed' !== control.setting() ); + + control.element.bind( function( to ) { + control.setting.set( to ? 'scroll' : 'fixed' ); + } ); + + control.setting.bind( function( to ) { + control.element.set( 'fixed' !== to ); + } ); + } ); + // Juggle the two controls that use header_textcolor api.control( 'display_header_text', function( control ) { var last = ''; diff --git a/src/wp-includes/class-wp-customize-control.php b/src/wp-includes/class-wp-customize-control.php index 7bed232913..25b8ee6613 100644 --- a/src/wp-includes/class-wp-customize-control.php +++ b/src/wp-includes/class-wp-customize-control.php @@ -660,6 +660,9 @@ require_once( ABSPATH . WPINC . '/customize/class-wp-customize-image-control.php /** WP_Customize_Background_Image_Control class */ require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-control.php' ); +/** WP_Customize_Background_Position_Control class */ +require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-position-control.php' ); + /** WP_Customize_Cropped_Image_Control class */ require_once( ABSPATH . WPINC . '/customize/class-wp-customize-cropped-image-control.php' ); diff --git a/src/wp-includes/class-wp-customize-manager.php b/src/wp-includes/class-wp-customize-manager.php index 653cb420c3..d32eca6b9c 100644 --- a/src/wp-includes/class-wp-customize-manager.php +++ b/src/wp-includes/class-wp-customize-manager.php @@ -279,6 +279,7 @@ final class WP_Customize_Manager { require_once( ABSPATH . WPINC . '/customize/class-wp-customize-upload-control.php' ); require_once( ABSPATH . WPINC . '/customize/class-wp-customize-image-control.php' ); require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-control.php' ); + require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-position-control.php' ); require_once( ABSPATH . WPINC . '/customize/class-wp-customize-cropped-image-control.php' ); require_once( ABSPATH . WPINC . '/customize/class-wp-customize-site-icon-control.php' ); require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-control.php' ); @@ -3026,6 +3027,7 @@ final class WP_Customize_Manager { $this->register_control_type( 'WP_Customize_Upload_Control' ); $this->register_control_type( 'WP_Customize_Image_Control' ); $this->register_control_type( 'WP_Customize_Background_Image_Control' ); + $this->register_control_type( 'WP_Customize_Background_Position_Control' ); $this->register_control_type( 'WP_Customize_Cropped_Image_Control' ); $this->register_control_type( 'WP_Customize_Site_Icon_Control' ); $this->register_control_type( 'WP_Customize_Theme_Control' ); @@ -3276,66 +3278,102 @@ final class WP_Customize_Manager { $this->add_setting( 'background_image', array( 'default' => get_theme_support( 'custom-background', 'default-image' ), 'theme_supports' => 'custom-background', + 'sanitize_callback' => array( $this, '_sanitize_background_setting' ), ) ); $this->add_setting( new WP_Customize_Background_Image_Setting( $this, 'background_image_thumb', array( 'theme_supports' => 'custom-background', + 'sanitize_callback' => array( $this, '_sanitize_background_setting' ), ) ) ); $this->add_control( new WP_Customize_Background_Image_Control( $this ) ); - $this->add_setting( 'background_repeat', array( - 'default' => get_theme_support( 'custom-background', 'default-repeat' ), + $this->add_setting( 'background_preset', array( + 'default' => get_theme_support( 'custom-background', 'default-preset' ), 'theme_supports' => 'custom-background', + 'sanitize_callback' => array( $this, '_sanitize_background_setting' ), ) ); - $this->add_control( 'background_repeat', array( - 'label' => __( 'Background Repeat' ), + $this->add_control( 'background_preset', array( + 'label' => _x( 'Preset', 'Background Preset' ), 'section' => 'background_image', - 'type' => 'radio', + 'type' => 'select', 'choices' => array( - 'no-repeat' => __('No Repeat'), - 'repeat' => __('Tile'), - 'repeat-x' => __('Tile Horizontally'), - 'repeat-y' => __('Tile Vertically'), + 'default' => _x( 'Default', 'Default Preset' ), + 'fill' => __( 'Fill Screen' ), + 'fit' => __( 'Fit to Screen' ), + 'repeat' => _x( 'Repeat', 'Repeat Image' ), + 'custom' => _x( 'Custom', 'Custom Preset' ), ), ) ); $this->add_setting( 'background_position_x', array( 'default' => get_theme_support( 'custom-background', 'default-position-x' ), 'theme_supports' => 'custom-background', + 'sanitize_callback' => array( $this, '_sanitize_background_setting' ), + ) ); + + $this->add_setting( 'background_position_y', array( + 'default' => get_theme_support( 'custom-background', 'default-position-y' ), + 'theme_supports' => 'custom-background', + 'sanitize_callback' => array( $this, '_sanitize_background_setting' ), + ) ); + + $this->add_control( new WP_Customize_Background_Position_Control( $this, 'background_position', array( + 'label' => __( 'Image Position' ), + 'section' => 'background_image', + 'settings' => array( + 'x' => 'background_position_x', + 'y' => 'background_position_y', + ), + ) ) ); + + $this->add_setting( 'background_size', array( + 'default' => get_theme_support( 'custom-background', 'default-size' ), + 'theme_supports' => 'custom-background', + 'sanitize_callback' => array( $this, '_sanitize_background_setting' ), ) ); - $this->add_control( 'background_position_x', array( - 'label' => __( 'Background Position' ), + $this->add_control( 'background_size', array( + 'label' => __( 'Image Size' ), 'section' => 'background_image', - 'type' => 'radio', + 'type' => 'select', 'choices' => array( - 'left' => __('Left'), - 'center' => __('Center'), - 'right' => __('Right'), + 'auto' => __( 'Original' ), + 'contain' => __( 'Fit to Screen' ), + 'cover' => __( 'Fill Screen' ), ), ) ); + $this->add_setting( 'background_repeat', array( + 'default' => get_theme_support( 'custom-background', 'default-repeat' ), + 'sanitize_callback' => array( $this, '_sanitize_background_setting' ), + 'theme_supports' => 'custom-background', + ) ); + + $this->add_control( 'background_repeat', array( + 'label' => __( 'Repeat Background Image' ), + 'section' => 'background_image', + 'type' => 'checkbox', + ) ); + $this->add_setting( 'background_attachment', array( - 'default' => get_theme_support( 'custom-background', 'default-attachment' ), - 'theme_supports' => 'custom-background', + 'default' => get_theme_support( 'custom-background', 'default-attachment' ), + 'sanitize_callback' => array( $this, '_sanitize_background_setting' ), + 'theme_supports' => 'custom-background', ) ); $this->add_control( 'background_attachment', array( - 'label' => __( 'Background Attachment' ), - 'section' => 'background_image', - 'type' => 'radio', - 'choices' => array( - 'scroll' => __('Scroll'), - 'fixed' => __('Fixed'), - ), + 'label' => __( 'Scroll with Page' ), + 'section' => 'background_image', + 'type' => 'checkbox', ) ); + // If the theme is using the default background callback, we can update // the background CSS using postMessage. if ( get_theme_support( 'custom-background', 'wp-head-callback' ) === '_custom_background_cb' ) { - foreach ( array( 'color', 'image', 'position_x', 'repeat', 'attachment' ) as $prop ) { + foreach ( array( 'color', 'image', 'preset', 'position_x', 'position_y', 'size', 'repeat', 'attachment' ) as $prop ) { $this->get_setting( 'background_' . $prop )->transport = 'postMessage'; } } @@ -3624,6 +3662,49 @@ final class WP_Customize_Manager { } /** + * Callback for validating a background setting value. + * + * @since 4.7.0 + * @access private + * + * @param string $value Repeat value. + * @param WP_Customize_Setting $setting Setting. + * @return string|WP_Error Background value or validation error. + */ + public function _sanitize_background_setting( $value, $setting ) { + if ( 'background_repeat' === $setting->id ) { + if ( ! in_array( $value, array( 'repeat-x', 'repeat-y', 'repeat', 'no-repeat' ) ) ) { + return new WP_Error( 'invalid_value', __( 'Invalid value for background repeat.' ) ); + } + } else if ( 'background_attachment' === $setting->id ) { + if ( ! in_array( $value, array( 'fixed', 'scroll' ) ) ) { + return new WP_Error( 'invalid_value', __( 'Invalid value for background attachment.' ) ); + } + } else if ( 'background_position_x' === $setting->id ) { + if ( ! in_array( $value, array( 'left', 'center', 'right' ), true ) ) { + return new WP_Error( 'invalid_value', __( 'Invalid value for background position X.' ) ); + } + } else if ( 'background_position_y' === $setting->id ) { + if ( ! in_array( $value, array( 'top', 'center', 'bottom' ), true ) ) { + return new WP_Error( 'invalid_value', __( 'Invalid value for background position Y.' ) ); + } + } else if ( 'background_size' === $setting->id ) { + if ( ! in_array( $value, array( 'auto', 'contain', 'cover' ), true ) ) { + return new WP_Error( 'invalid_value', __( 'Invalid value for background size.' ) ); + } + } else if ( 'background_preset' === $setting->id ) { + if ( ! in_array( $value, array( 'default', 'fill', 'fit', 'repeat', 'custom' ), true ) ) { + return new WP_Error( 'invalid_value', __( 'Invalid value for background size.' ) ); + } + } else if ( 'background_image' === $setting->id || 'background_image_thumb' === $setting->id ) { + $value = empty( $value ) ? '' : esc_url_raw( $value ); + } else { + return new WP_Error( 'unrecognized_setting', __( 'Unrecognized background setting.' ) ); + } + return $value; + } + + /** * Callback for rendering the custom logo, used in the custom_logo partial. * * This method exists because the partial object and context data are passed diff --git a/src/wp-includes/customize/class-wp-customize-background-image-control.php b/src/wp-includes/customize/class-wp-customize-background-image-control.php index 588e32a9c8..50956f8522 100644 --- a/src/wp-includes/customize/class-wp-customize-background-image-control.php +++ b/src/wp-includes/customize/class-wp-customize-background-image-control.php @@ -40,7 +40,9 @@ class WP_Customize_Background_Image_Control extends WP_Customize_Image_Control { public function enqueue() { parent::enqueue(); + $custom_background = get_theme_support( 'custom-background' ); wp_localize_script( 'customize-controls', '_wpCustomizeBackground', array( + 'defaults' => ! empty( $custom_background[0] ) ? $custom_background[0] : array(), 'nonces' => array( 'add' => wp_create_nonce( 'background-add' ), ), diff --git a/src/wp-includes/customize/class-wp-customize-background-position-control.php b/src/wp-includes/customize/class-wp-customize-background-position-control.php new file mode 100644 index 0000000000..475a01a548 --- /dev/null +++ b/src/wp-includes/customize/class-wp-customize-background-position-control.php @@ -0,0 +1,87 @@ +<?php +/** + * Customize API: WP_Customize_Background_Position_Control class + * + * @package WordPress + * @subpackage Customize + * @since 4.7.0 + */ + +/** + * Customize Background Position Control class. + * + * @since 4.7.0 + * + * @see WP_Customize_Control + */ +class WP_Customize_Background_Position_Control extends WP_Customize_Control { + + /** + * Type. + * + * @since 4.7.0 + * @access public + * @var string + */ + public $type = 'background_position'; + + /** + * Don't render the control content from PHP, as it's rendered via JS on load. + * + * @since 4.7.0 + * @access public + */ + public function render_content() {} + + /** + * Render a JS template for the content of the position control. + * + * @since 4.7.0 + * @access public + */ + public function content_template() { + $options = array( + array( + 'left top' => array( 'label' => __( 'Top Left' ), 'icon' => 'dashicons dashicons-arrow-left-alt' ), + 'center top' => array( 'label' => __( 'Top' ), 'icon' => 'dashicons dashicons-arrow-up-alt' ), + 'right top' => array( 'label' => __( 'Top Right' ), 'icon' => 'dashicons dashicons-arrow-right-alt' ), + ), + array( + 'left center' => array( 'label' => __( 'Left' ), 'icon' => 'dashicons dashicons-arrow-left-alt' ), + 'center center' => array( 'label' => __( 'Center' ), 'icon' => 'background-position-center-icon' ), + 'right center' => array( 'label' => __( 'Right' ), 'icon' => 'dashicons dashicons-arrow-right-alt' ), + ), + array( + 'left bottom' => array( 'label' => __( 'Bottom Left' ), 'icon' => 'dashicons dashicons-arrow-left-alt' ), + 'center bottom' => array( 'label' => __( 'Bottom' ), 'icon' => 'dashicons dashicons-arrow-down-alt' ), + 'right bottom' => array( 'label' => __( 'Bottom Right' ), 'icon' => 'dashicons dashicons-arrow-right-alt' ), + ), + ); + ?> + <# if ( data.label ) { #> + <span class="customize-control-title">{{{ data.label }}}</span> + <# } #> + <# if ( data.description ) { #> + <span class="description customize-control-description">{{{ data.description }}}</span> + <# } #> + <div class="customize-control-content"> + <fieldset> + <legend class="screen-reader-text"><span><?php _e( 'Image Position' ); ?></span></legend> + <div class="background-position-control"> + <?php foreach ( $options as $group ) : ?> + <div class="button-group"> + <?php foreach ( $group as $value => $input ) : ?> + <label> + <input class="screen-reader-text" name="background-position" type="radio" value="<?php echo esc_attr( $value ); ?>"> + <span class="button display-options position"><span class="<?php echo esc_attr( $input['icon'] ); ?>" aria-hidden="true"></span></span> + <span class="screen-reader-text"><?php echo $input['label']; ?></span> + </label> + <?php endforeach; ?> + </div> + <?php endforeach; ?> + </div> + </fieldset> + </div> + <?php + } +} diff --git a/src/wp-includes/js/customize-preview.js b/src/wp-includes/js/customize-preview.js index 12da70f6ac..94173ef0bc 100644 --- a/src/wp-includes/js/customize-preview.js +++ b/src/wp-includes/js/customize-preview.js @@ -735,11 +735,11 @@ }); /* Custom Backgrounds */ - bg = $.map(['color', 'image', 'position_x', 'repeat', 'attachment'], function( prop ) { + bg = $.map( ['color', 'image', 'preset', 'position_x', 'position_y', 'size', 'repeat', 'attachment'], function( prop ) { return 'background_' + prop; - }); + } ); - api.when.apply( api, bg ).done( function( color, image, position_x, repeat, attachment ) { + api.when.apply( api, bg ).done( function( color, image, preset, positionX, positionY, size, repeat, attachment ) { var body = $(document.body), head = $('head'), style = $('#custom-background-css'), @@ -759,7 +759,8 @@ if ( image() ) { css += 'background-image: url("' + image() + '");'; - css += 'background-position: top ' + position_x() + ';'; + css += 'background-size: ' + size() + ';'; + css += 'background-position: ' + positionX() + ' ' + positionY() + ';'; css += 'background-repeat: ' + repeat() + ';'; css += 'background-attachment: ' + attachment() + ';'; } diff --git a/src/wp-includes/theme.php b/src/wp-includes/theme.php index 9ec5ec09de..606e254d2c 100644 --- a/src/wp-includes/theme.php +++ b/src/wp-includes/theme.php @@ -1374,24 +1374,50 @@ function _custom_background_cb() { $style = $color ? "background-color: #$color;" : ''; if ( $background ) { - $image = " background-image: url('$background');"; + $image = " background-image: url(" . wp_json_encode( $background ) . ");"; + // Background Position. + $position_x = get_theme_mod( 'background_position_x', get_theme_support( 'custom-background', 'default-position-x' ) ); + $position_y = get_theme_mod( 'background_position_y', get_theme_support( 'custom-background', 'default-position-y' ) ); + + if ( ! in_array( $position_x, array( 'left', 'center', 'right' ), true ) ) { + $position_x = 'left'; + } + + if ( ! in_array( $position_y, array( 'top', 'center', 'bottom' ), true ) ) { + $position_y = 'top'; + } + + $position = " background-position: $position_x $position_y;"; + + // Background Size. + $size = get_theme_mod( 'background_size', get_theme_support( 'custom-background', 'default-size' ) ); + + if ( ! in_array( $size, array( 'auto', 'contain', 'cover' ), true ) ) { + $size = 'auto'; + } + + $size = " background-size: $size;"; + + // Background Repeat. $repeat = get_theme_mod( 'background_repeat', get_theme_support( 'custom-background', 'default-repeat' ) ); - if ( ! in_array( $repeat, array( 'no-repeat', 'repeat-x', 'repeat-y', 'repeat' ) ) ) + + if ( ! in_array( $repeat, array( 'repeat-x', 'repeat-y', 'repeat', 'no-repeat' ), true ) ) { $repeat = 'repeat'; - $repeat = " background-repeat: $repeat;"; + } - $position = get_theme_mod( 'background_position_x', get_theme_support( 'custom-background', 'default-position-x' ) ); - if ( ! in_array( $position, array( 'center', 'right', 'left' ) ) ) - $position = 'left'; - $position = " background-position: top $position;"; + $repeat = " background-repeat: $repeat;"; + // Background Scroll. $attachment = get_theme_mod( 'background_attachment', get_theme_support( 'custom-background', 'default-attachment' ) ); - if ( ! in_array( $attachment, array( 'fixed', 'scroll' ) ) ) + + if ( 'fixed' !== $attachment ) { $attachment = 'scroll'; + } + $attachment = " background-attachment: $attachment;"; - $style .= $image . $repeat . $position . $attachment; + $style .= $image . $position . $size . $repeat . $attachment; } ?> <style type="text/css" id="custom-background-css"> @@ -1772,8 +1798,11 @@ function add_theme_support( $feature ) { $defaults = array( 'default-image' => '', - 'default-repeat' => 'repeat', + 'default-preset' => 'default', 'default-position-x' => 'left', + 'default-position-y' => 'top', + 'default-size' => 'auto', + 'default-repeat' => 'repeat', 'default-attachment' => 'scroll', 'default-color' => '', 'wp-head-callback' => '_custom_background_cb', |