Make WordPress Core

Ticket #29211: 29211.4.diff

File 29211.4.diff, 27.4 KB (added by celloexpressions, 10 years ago)
  • src/wp-admin/css/customize-controls.css

     
    762788.customize-control-upload .current,
    763789.customize-control-image .current,
    764790.customize-control-background .current,
     791.customize-control-cropped_image .current,
    765792.customize-control-header .current {
    766793        margin-bottom: 8px;
    767794}
     
    797824.customize-control-background .remove-button,
    798825.customize-control-background .default-button,
    799826.customize-control-background .upload-button,
     827.customize-control-cropped_image .remove-button,
     828.customize-control-cropped_image .default-button,
     829.customize-control-cropped_image .upload-button,
    800830.customize-control-header button.new,
    801831.customize-control-header button.remove {
    802832        white-space: normal;
     
    808838.customize-control-upload .current .container,
    809839.customize-control-image .current .container,
    810840.customize-control-background .current .container,
     841.customize-control-cropped_image .current .container,
    811842.customize-control-header .current .container {
    812843        overflow: hidden;
    813844        -webkit-border-radius: 2px;
     
    819850.customize-control-media .current .container,
    820851.customize-control-upload .current .container,
    821852.customize-control-background .current .container,
     853.customize-control-cropped_image .current .container,
    822854.customize-control-image .current .container {
    823855        min-height: 40px;
    824856}
     
    827859.customize-control-upload .placeholder,
    828860.customize-control-image .placeholder,
    829861.customize-control-background .placeholder,
     862.customize-control-cropped_image .placeholder,
    830863.customize-control-header .placeholder {
    831864        width: 100%;
    832865        position: relative;
     
    838871.customize-control-upload .inner,
    839872.customize-control-image .inner,
    840873.customize-control-background .inner,
     874.customize-control-cropped_image .inner,
    841875.customize-control-header .inner {
    842876        display: none;
    843877        position: absolute;
     
    851885.customize-control-media .inner,
    852886.customize-control-upload .inner,
    853887.customize-control-background .inner,
     888.customize-control-cropped_image .inner,
    854889.customize-control-image .inner {
    855890        display: block;
    856891        min-height: 40px;
     
    860895.customize-control-upload .inner,
    861896.customize-control-image .inner,
    862897.customize-control-background .inner,
     898.customize-control-cropped_image .inner,
    863899.customize-control-header .inner,
    864900.customize-control-header .inner .dashicons {
    865901        line-height: 20px;
     
    963999.customize-control-upload .actions,
    9641000.customize-control-image .actions,
    9651001.customize-control-background .actions,
     1002.customize-control-cropped_image .actions,
    9661003.customize-control-header .actions {
    9671004        margin-bottom: 32px;
    9681005}
     
    9811018.customize-control-upload img,
    9821019.customize-control-image img,
    9831020.customize-control-background img,
     1021.customize-control-cropped_image img,
    9841022.customize-control-header img {
    9851023        width: 100%;
    9861024        -webkit-border-radius: 2px;
     
    9951033.customize-control-image .default-button,
    9961034.customize-control-background .remove-button,
    9971035.customize-control-background .default-button,
     1036.customize-control-cropped_image .remove-button,
     1037.customize-control-cropped_image .default-button,
    9981038.customize-control-header .remove {
    9991039        float: left;
    10001040        margin-right: 3px;
     
    10041044.customize-control-upload .upload-button,
    10051045.customize-control-image .upload-button,
    10061046.customize-control-background .upload-button,
     1047.customize-control-cropped_image .upload-button,
    10071048.customize-control-header .new {
    10081049        float: right;
    10091050}
  • src/wp-admin/js/customize-controls.js

     
    17951795         * @augments wp.customize.Control
    17961796         * @augments wp.customize.Class
    17971797         */
    1798         api.ImageControl = api.UploadControl.extend({
     1798        api.ImageControl = api.MediaControl.extend({
    17991799                // @deprecated
    18001800                thumbnailSrc: function() {}
    18011801        });
     
    18361836        });
    18371837
    18381838        /**
     1839         * A control for selecting and cropping an image.
     1840         *
    18391841         * @class
     1842         * @augments wp.customize.MediaControl
    18401843         * @augments wp.customize.Control
    18411844         * @augments wp.customize.Class
    18421845         */
    1843         api.HeaderControl = api.Control.extend({
    1844                 ready: function() {
    1845                         this.btnRemove = $('#customize-control-header_image .actions .remove');
    1846                         this.btnNew    = $('#customize-control-header_image .actions .new');
     1846        api.CroppedImageControl = api.MediaControl.extend({
     1847                /**
     1848                 * Open the media modal to the library state.
     1849                 */
     1850                openFrame: function( event ) {
     1851                        if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     1852                                return;
     1853                        }
    18471854
    1848                         _.bindAll(this, 'openMedia', 'removeImage');
     1855                        if ( this.frame ) {
     1856                                this.frame.setState( 'library' );
     1857                        }
    18491858
    1850                         this.btnNew.on( 'click', this.openMedia );
    1851                         this.btnRemove.on( 'click', this.removeImage );
     1859                        api.MediaControl.prototype.openFrame.call( this, event );
     1860                },
    18521861
    1853                         api.HeaderTool.currentHeader = this.getInitialHeaderImage();
     1862                /**
     1863                 * Create a media modal select frame, and store it so the instance can be reused when needed.
     1864                 */
     1865                initFrame: function() {
     1866                        var l10n = _wpMediaViewsL10n;
    18541867
    1855                         new api.HeaderTool.CurrentView({
    1856                                 model: api.HeaderTool.currentHeader,
    1857                                 el: '#customize-control-header_image .current .container'
     1868                        this.frame = wp.media({
     1869                                button: {
     1870                                        text: l10n.selectAndCrop,
     1871                                        close: false
     1872                                },
     1873                                states: [
     1874                                        new wp.media.controller.Library({
     1875                                                title:     this.params.button_labels.frame_title,
     1876                                                library:   wp.media.query({ type: 'image' }),
     1877                                                multiple:  false,
     1878                                                date:      false,
     1879                                                priority:  20,
     1880                                                suggestedWidth: this.params.width,
     1881                                                suggestedHeight: this.params.height
     1882                                        }),
     1883                                        new wp.media.controller.customizeImageCropper({
     1884                                                imgSelectOptions: this.calculateImageSelectOptions,
     1885                                                control: this
     1886                                        })
     1887                                ]
    18581888                        });
    18591889
    1860                         new api.HeaderTool.ChoiceListView({
    1861                                 collection: api.HeaderTool.UploadsList = new api.HeaderTool.ChoiceList(),
    1862                                 el: '#customize-control-header_image .choices .uploaded .list'
    1863                         });
     1890                        this.frame.on( 'select', this.onSelect, this );
     1891                        this.frame.on( 'cropped', this.onCropped, this );
     1892                        this.frame.on( 'skippedcrop', this.onSkippedCrop, this );
     1893                },
    18641894
    1865                         new api.HeaderTool.ChoiceListView({
    1866                                 collection: api.HeaderTool.DefaultsList = new api.HeaderTool.DefaultsList(),
    1867                                 el: '#customize-control-header_image .choices .default .list'
    1868                         });
    1869 
    1870                         api.HeaderTool.combinedList = api.HeaderTool.CombinedList = new api.HeaderTool.CombinedList([
    1871                                 api.HeaderTool.UploadsList,
    1872                                 api.HeaderTool.DefaultsList
    1873                         ]);
     1895                /**
     1896                 * After an image is selected in the media modal,
     1897                 * switch to the cropper state.
     1898                 */
     1899                onSelect: function() {
     1900                        this.frame.setState( 'cropper' );
    18741901                },
    18751902
    18761903                /**
    1877                  * Returns a new instance of api.HeaderTool.ImageModel based on the currently
    1878                  * saved header image (if any).
     1904                 * After the image has been cropped, apply the cropped image data to the setting.
    18791905                 *
    1880                  * @since 4.2.0
    1881                  *
    1882                  * @returns {Object} Options
     1906                 * @param {object} croppedImage Cropped attachment data.
    18831907                 */
    1884                 getInitialHeaderImage: function() {
    1885                         if ( ! api.get().header_image || ! api.get().header_image_data || _.contains( [ 'remove-header', 'random-default-image', 'random-uploaded-image' ], api.get().header_image ) ) {
    1886                                 return new api.HeaderTool.ImageModel();
    1887                         }
    1888 
    1889                         // Get the matching uploaded image object.
    1890                         var currentHeaderObject = _.find( _wpCustomizeHeader.uploads, function( imageObj ) {
    1891                                 return ( imageObj.attachment_id === api.get().header_image_data.attachment_id );
    1892                         } );
    1893                         // Fall back to raw current header image.
    1894                         if ( ! currentHeaderObject ) {
    1895                                 currentHeaderObject = {
    1896                                         url: api.get().header_image,
    1897                                         thumbnail_url: api.get().header_image,
    1898                                         attachment_id: api.get().header_image_data.attachment_id
    1899                                 };
    1900                         }
    1901 
    1902                         return new api.HeaderTool.ImageModel({
    1903                                 header: currentHeaderObject,
    1904                                 choice: currentHeaderObject.url.split( '/' ).pop()
    1905                         });
     1908                onCropped: function( croppedImage ) {
     1909                        this.setImageFromPost( croppedImage );
    19061910                },
    19071911
    19081912                /**
    19091913                 * Returns a set of options, computed from the attached image data and
    1910                  * theme-specific data, to be fed to the imgAreaSelect plugin in
     1914                 * control-specific data, to be fed to the imgAreaSelect plugin in
    19111915                 * wp.media.view.Cropper.
    19121916                 *
    19131917                 * @param {wp.media.model.Attachment} attachment
     
    19141918                 * @param {wp.media.controller.Cropper} controller
    19151919                 * @returns {Object} Options
    19161920                 */
    1917                 calculateImageSelectOptions: function(attachment, controller) {
    1918                         var xInit = parseInt(_wpCustomizeHeader.data.width, 10),
    1919                                 yInit = parseInt(_wpCustomizeHeader.data.height, 10),
    1920                                 flexWidth = !! parseInt(_wpCustomizeHeader.data['flex-width'], 10),
    1921                                 flexHeight = !! parseInt(_wpCustomizeHeader.data['flex-height'], 10),
    1922                                 ratio, xImg, yImg, realHeight, realWidth,
    1923                                 imgSelectOptions;
     1921                calculateImageSelectOptions: function( attachment, controller ) {
     1922                        var control = controller.get( 'control' ),
     1923                            xInit = parseInt( control.params.width, 10 ),
     1924                            yInit = parseInt( control.params.height, 10 ),
     1925                            flexWidth = !! parseInt( control.params.flex_width, 10 ),
     1926                            flexHeight = !! parseInt( control.params.flex_height, 10 ),
     1927                            ratio, xImg, yImg, realHeight, realWidth,
     1928                            imgSelectOptions;
    19241929
    1925                         realWidth = attachment.get('width');
    1926                         realHeight = attachment.get('height');
     1930                        realWidth = attachment.get( 'width' );
     1931                        realHeight = attachment.get( 'height' );
    19271932
    1928                         this.headerImage = new api.HeaderTool.ImageModel();
    1929                         this.headerImage.set({
    1930                                 themeWidth: xInit,
    1931                                 themeHeight: yInit,
    1932                                 themeFlexWidth: flexWidth,
    1933                                 themeFlexHeight: flexHeight,
    1934                                 imageWidth: realWidth,
    1935                                 imageHeight: realHeight
    1936                         });
     1933                        controller.set( 'canSkipCrop', ! control.mustBeCropped( flexWidth, flexHeight, xInit, yInit, realWidth, realHeight ) );
    19371934
    1938                         controller.set( 'canSkipCrop', ! this.headerImage.shouldBeCropped() );
    1939 
    19401935                        ratio = xInit / yInit;
    19411936                        xImg = realWidth;
    19421937                        yImg = realHeight;
     
    19621957                                y2: yInit
    19631958                        };
    19641959
    1965                         if (flexHeight === false && flexWidth === false) {
     1960                        if ( flexHeight === false && flexWidth === false ) {
    19661961                                imgSelectOptions.aspectRatio = xInit + ':' + yInit;
    19671962                        }
    1968                         if (flexHeight === false ) {
     1963                        if ( flexHeight === false ) {
    19691964                                imgSelectOptions.maxHeight = yInit;
    19701965                        }
    1971                         if (flexWidth === false ) {
     1966                        if ( flexWidth === false ) {
    19721967                                imgSelectOptions.maxWidth = xInit;
    19731968                        }
    19741969
     
    19761971                },
    19771972
    19781973                /**
    1979                  * Sets up and opens the Media Manager in order to select an image.
    1980                  * Depending on both the size of the image and the properties of the
    1981                  * current theme, a cropping step after selection may be required or
    1982                  * skippable.
    1983                  *
    1984                  * @param {event} event
     1974                 * Return whether the image must be cropped, based on required dimensions.
    19851975                 */
    1986                 openMedia: function(event) {
    1987                         var l10n = _wpMediaViewsL10n;
     1976                mustBeCropped: function( flexW, flexH, dstW, dstH, imgW, imgH ) {
     1977                        if ( flexW === true && flexH === true ) {
     1978                                return false;
     1979                        }
    19881980
    1989                         event.preventDefault();
     1981                        if ( flexW === true && dstH === imgH ) {
     1982                                return false;
     1983                        }
    19901984
    1991                         this.frame = wp.media({
    1992                                 button: {
    1993                                         text: l10n.selectAndCrop,
    1994                                         close: false
    1995                                 },
    1996                                 states: [
    1997                                         new wp.media.controller.Library({
    1998                                                 title:     l10n.chooseImage,
    1999                                                 library:   wp.media.query({ type: 'image' }),
    2000                                                 multiple:  false,
    2001                                                 date:      false,
    2002                                                 priority:  20,
    2003                                                 suggestedWidth: _wpCustomizeHeader.data.width,
    2004                                                 suggestedHeight: _wpCustomizeHeader.data.height
    2005                                         }),
    2006                                         new wp.media.controller.Cropper({
    2007                                                 imgSelectOptions: this.calculateImageSelectOptions
    2008                                         })
    2009                                 ]
    2010                         });
     1985                        if ( flexH === true && dstW === imgW ) {
     1986                                return false;
     1987                        }
    20111988
    2012                         this.frame.on('select', this.onSelect, this);
    2013                         this.frame.on('cropped', this.onCropped, this);
    2014                         this.frame.on('skippedcrop', this.onSkippedCrop, this);
     1989                        if ( dstW === imgW && dstH === imgH ) {
     1990                                return false;
     1991                        }
    20151992
    2016                         this.frame.open();
    2017                 },
     1993                        if ( imgW <= dstW ) {
     1994                                return false;
     1995                        }
    20181996
    2019                 /**
    2020                  * After an image is selected in the media modal,
    2021                  * switch to the cropper state.
    2022                  */
    2023                 onSelect: function() {
    2024                         this.frame.setState('cropper');
     1997                        return true;
    20251998                },
    20261999
    20272000                /**
    2028                  * After the image has been cropped, apply the cropped image data to the setting.
    2029                  *
    2030                  * @param {object} croppedImage Cropped attachment data.
    2031                  */
    2032                 onCropped: function(croppedImage) {
    2033                         var url = croppedImage.post_content,
    2034                                 attachmentId = croppedImage.attachment_id,
    2035                                 w = croppedImage.width,
    2036                                 h = croppedImage.height;
    2037                         this.setImageFromURL(url, attachmentId, w, h);
    2038                 },
    2039 
    2040                 /**
    20412001                 * If cropping was skipped, apply the image data directly to the setting.
    20422002                 *
    20432003                 * @param {object} selection
    20442004                 */
    2045                 onSkippedCrop: function(selection) {
    2046                         var url = selection.get('url'),
    2047                                 w = selection.get('width'),
    2048                                 h = selection.get('height');
    2049                         this.setImageFromURL(url, selection.id, w, h);
     2005                onSkippedCrop: function( selection ) {
     2006                        this.setImageFromPost( selection );
    20502007                },
    20512008
    20522009                /**
    2053                  * Creates a new wp.customize.HeaderTool.ImageModel from provided
    2054                  * header image data and inserts it into the user-uploaded headers
    2055                  * collection.
     2010                 * Creates a new attachment model from provided header image data
     2011                 * and updates the setting, re-rendering the control UI.
    20562012                 *
    2057                  * @param {String} url
    2058                  * @param {Number} attachmentId
    2059                  * @param {Number} width
    2060                  * @param {Number} height
     2013                 * @param {object} post
    20612014                 */
    2062                 setImageFromURL: function(url, attachmentId, width, height) {
    2063                         var choice, data = {};
     2015                setImageFromPost: function( post ) {
     2016                        var attachment = {
     2017                                id: post.attachment_id,
     2018                                type: 'image',
     2019                                sizes: {
     2020                                        full: {
     2021                                                url: post.guid
     2022                                        }
     2023                                }
     2024                        };
     2025                        this.params.attachment = attachment;
    20642026
    2065                         data.url = url;
    2066                         data.thumbnail_url = url;
    2067                         data.timestamp = _.now();
     2027                        // Set the Customizer setting; the callback takes care of rendering.
     2028                        this.setting( attachment.id );
     2029                },
    20682030
    2069                         if (attachmentId) {
    2070                                 data.attachment_id = attachmentId;
    2071                         }
     2031        });
    20722032
    2073                         if (width) {
    2074                                 data.width = width;
    2075                         }
     2033        /**
     2034         * @class
     2035         * @augments wp.customize.Control
     2036         * @augments wp.customize.Class
     2037         */
     2038        api.HeaderControl = api.CroppedImageControl.extend({
    20762039
    2077                         if (height) {
    2078                                 data.height = height;
    2079                         }
    2080 
    2081                         choice = new api.HeaderTool.ImageModel({
    2082                                 header: data,
    2083                                 choice: url.split('/').pop()
    2084                         });
    2085                         api.HeaderTool.UploadsList.add(choice);
    2086                         api.HeaderTool.currentHeader.set(choice.toJSON());
    2087                         choice.save();
    2088                         choice.importImage();
    2089                 },
    2090 
    2091                 /**
    2092                  * Triggers the necessary events to deselect an image which was set as
    2093                  * the currently selected one.
    2094                  */
    2095                 removeImage: function() {
    2096                         api.HeaderTool.currentHeader.trigger('hide');
    2097                         api.HeaderTool.CombinedList.trigger('control:removeImage');
    2098                 }
    2099 
    21002040        });
    21012041
    21022042        /**
     
    26952635        });
    26962636
    26972637        api.controlConstructor = {
    2698                 color:      api.ColorControl,
    2699                 media:      api.MediaControl,
    2700                 upload:     api.UploadControl,
    2701                 image:      api.ImageControl,
    2702                 header:     api.HeaderControl,
    2703                 background: api.BackgroundControl,
    2704                 theme:      api.ThemeControl
     2638                color:         api.ColorControl,
     2639                media:         api.MediaControl,
     2640                upload:        api.UploadControl,
     2641                image:         api.ImageControl,
     2642                cropped_image: api.CroppedImageControl,
     2643                header:        api.HeaderControl,
     2644                background:    api.BackgroundControl,
     2645                theme:         api.ThemeControl
    27052646        };
    27062647        api.panelConstructor = {};
    27072648        api.sectionConstructor = {
  • src/wp-includes/class-wp-customize-ajax-actions.php

     
     1<?php
     2/**
     3 * Ajax Actions for the Customizer.
     4 *
     5 * @package WordPress
     6 * @subpackage Customize
     7 */
     8
     9/**
     10 * The custom header image class.
     11 *
     12 * @since 2.1.0
     13 * @package WordPress
     14 * @subpackage Administration
     15 */
     16class WP_Customize_Ajax_Actions {
     17        public function __construct() {
     18                add_action( 'wp_ajax_customize-image-crop', array( $this, 'ajax_image_crop' ) );
     19        }
     20
     21        /**
     22         * Gets attachment uploaded by Media Manager, crops it, then saves it as a
     23         * new object. Returns JSON-encoded object details.
     24         */
     25        public function ajax_image_crop() {
     26                check_ajax_referer( 'image_editor-' . $_POST['id'], 'nonce' );
     27                if ( ! current_user_can( 'customize' ) ) {
     28                        wp_send_json_error();
     29                }
     30                $crop_details = $_POST['cropDetails'];
     31                $attachment_id = absint( $_POST['id'] );
     32                $cropped = wp_crop_image(
     33                        $attachment_id,
     34                        (int) $crop_details['x1'],
     35                        (int) $crop_details['y1'],
     36                        (int) $crop_details['width'],
     37                        (int) $crop_details['height'],
     38                        (int) $crop_details['dst_width'],
     39                        (int) $crop_details['dst_height']
     40                );
     41                if ( ! $cropped || is_wp_error( $cropped ) ) {
     42                        wp_send_json_error( array( 'message' => __( 'Image could not be processed. Please go back and try again.' ) ) );
     43                }
     44                /** This filter is documented in wp-admin/custom-header.php */
     45                $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication
     46                $object = $this->create_attachment_object( $cropped, $attachment_id );
     47                unset( $object['ID'] );
     48                $new_attachment_id = $this->insert_attachment( $object, $cropped );
     49//              $object['attachment_id'] = $new_attachment_id;
     50//              $object['width']         = $dimensions['dst_width'];
     51//              $object['height']        = $dimensions['dst_height'];
     52                wp_send_json_success( wp_prepare_attachment_for_js( $new_attachment_id ) ); // was object, no prepare attachment
     53        }
     54
     55        /**
     56         * Create an attachment 'object'.
     57         *
     58         * @param string $cropped              Cropped image URL.
     59         * @param int    $parent_attachment_id Attachment ID of parent image.
     60         *
     61         * @return array Attachment object.
     62         */
     63        final public function create_attachment_object( $cropped, $parent_attachment_id ) {
     64                $parent = get_post( $parent_attachment_id );
     65                $parent_url = $parent->guid;
     66                $url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
     67                $size = @getimagesize( $cropped );
     68                $image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
     69                $object = array(
     70                        'ID' => $parent_attachment_id,
     71                        'post_title' => basename($cropped),
     72                        'post_content' => $url,
     73                        'post_mime_type' => $image_type,
     74                        'guid' => $url,
     75                        'context' => 'customize-cropped'
     76                );
     77                return $object;
     78        }
     79
     80        /**
     81         * Insert an attachment and its metadata.
     82         *
     83         * @param array  $object  Attachment object.
     84         * @param string $cropped Cropped image URL.
     85         *
     86         * @return int Attachment ID.
     87         */
     88        final public function insert_attachment( $object, $cropped ) {
     89                $attachment_id = wp_insert_attachment( $object, $cropped );
     90                $metadata = wp_generate_attachment_metadata( $attachment_id, $cropped );
     91                wp_update_attachment_metadata( $attachment_id, $metadata );
     92                return $attachment_id;
     93        }
     94}
  • src/wp-includes/class-wp-customize-control.php

     
    10011001}
    10021002
    10031003/**
    1004  * Customize Header Image Control class.
     1004 * Customize Cropped Image Control class.
    10051005 *
    1006  * @since 3.4.0
     1006 * @since 4.3.0
    10071007 *
    10081008 * @see WP_Customize_Image_Control
    10091009 */
    1010 class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control {
    1011         public $type = 'header';
    1012         public $uploaded_headers;
    1013         public $default_headers;
     1010class WP_Customize_Cropped_Image_Control extends WP_Customize_Image_Control {
     1011        public $type = 'cropped_image';
    10141012
     1013        protected $width  = 150;
     1014        protected $height = 150;
     1015        protected $flex_width  = false;
     1016        protected $flex_height = false;
     1017
    10151018        /**
     1019         * Constructor.
     1020         *
     1021         * @since 4.3.0
     1022         * @uses WP_Customize_Image_Control::__construct()
     1023         *
    10161024         * @param WP_Customize_Manager $manager
    10171025         */
    1018         public function __construct( $manager ) {
    1019                 parent::__construct( $manager, 'header_image', array(
    1020                         'label'    => __( 'Header Image' ),
    1021                         'settings' => array(
    1022                                 'default' => 'header_image',
    1023                                 'data'    => 'header_image_data',
    1024                         ),
    1025                         'section'  => 'header_image',
    1026                         'removed'  => 'remove-header',
    1027                         'get_url'  => 'get_header_image',
    1028                 ) );
    1029 
     1026        public function __construct( $manager, $id, $args ) {
     1027                parent::__construct( $manager, $id, $args );
    10301028        }
    10311029
    10321030        /**
     
    10331031         * @access public
    10341032         */
    10351033        public function enqueue() {
    1036                 wp_enqueue_media();
    10371034                wp_enqueue_script( 'customize-views' );
    10381035
    1039                 $this->prepare_control();
     1036                parent::enqueue();
     1037        }
    10401038
    1041                 wp_localize_script( 'customize-views', '_wpCustomizeHeader', array(
    1042                         'data' => array(
    1043                                 'width' => absint( get_theme_support( 'custom-header', 'width' ) ),
    1044                                 'height' => absint( get_theme_support( 'custom-header', 'height' ) ),
    1045                                 'flex-width' => absint( get_theme_support( 'custom-header', 'flex-width' ) ),
    1046                                 'flex-height' => absint( get_theme_support( 'custom-header', 'flex-height' ) ),
    1047                                 'currentImgSrc' => $this->get_current_image_src(),
     1039        /**
     1040         * Refresh the parameters passed to the JavaScript via JSON.
     1041         *
     1042         * @since 4.3.0
     1043         * @access public
     1044         * @uses WP_Customize_Image_Control::to_json()
     1045         *
     1046         * @see WP_Customize_Control::to_json()
     1047         */
     1048        public function to_json() {
     1049                parent::to_json();
     1050
     1051                $this->json['width']       = absint( $this->width );
     1052                $this->json['height']      = absint( $this->height );
     1053                $this->json['flex_width']  = absint( $this->flex_width );
     1054                $this->json['flex_height'] = absint( $this->flex_height );
     1055        }
     1056
     1057}
     1058
     1059/**
     1060 * Customize Header Image Control class.
     1061 *
     1062 * @since 3.4.0
     1063 * @since 4.3.0 extends WP_Customize_Cropped_Image_Control
     1064 *
     1065 * @see WP_Customize_Cropped_Image_Control
     1066 */
     1067class WP_Customize_Header_Image_Control extends WP_Customize_Cropped_Image_Control {
     1068        public $type = 'header';
     1069
     1070        /**
     1071         * @param WP_Customize_Manager $manager
     1072         */
     1073        public function __construct( $manager ) {
     1074                parent::__construct( $manager, 'header_image', array(
     1075                        'label'         => __( 'Header Image' ),
     1076                        'current_title' => __( 'Current header' ),
     1077                        'settings' => array(
     1078                                'default' => 'header_image',
     1079                                'data'    => 'header_image_data',
    10481080                        ),
    1049                         'nonces' => array(
    1050                                 'add' => wp_create_nonce( 'header-add' ),
    1051                                 'remove' => wp_create_nonce( 'header-remove' ),
    1052                         ),
    1053                         'uploads' => $this->uploaded_headers,
    1054                         'defaults' => $this->default_headers
     1081                        'section'       => 'header_image',
     1082                        'removed'       => 'remove-header',
    10551083                ) );
    10561084
    1057                 parent::enqueue();
     1085                $this->width  = get_theme_support( 'custom-header', 'width' );
     1086                $this->height = get_theme_support( 'custom-header', 'height' );
     1087                $this->flex_width  = get_theme_support( 'custom-header', 'flex-width' );
     1088                $this->flex_height = get_theme_support( 'custom-header', 'flex-height' );
     1089                $this->localized_name = '_wpCustomizeHeader';
    10581090        }
    10591091
    10601092        /**
     
    10631095         */
    10641096        public function prepare_control() {
    10651097                global $custom_image_header;
     1098
    10661099                if ( empty( $custom_image_header ) ) {
    10671100                        return;
    10681101                }
     
    10691102
    10701103                // Process default headers and uploaded headers.
    10711104                $custom_image_header->process_default_headers();
    1072                 $this->default_headers = $custom_image_header->get_default_header_images();
    1073                 $this->uploaded_headers = $custom_image_header->get_uploaded_header_images();
     1105                $this->default_images = $custom_image_header->get_default_header_images();
     1106                $this->uploaded_images = $custom_image_header->get_uploaded_header_images();
    10741107        }
    10751108
    10761109        /**
  • src/wp-includes/class-wp-customize-manager.php

     
    112112                require_once( ABSPATH . WPINC . '/class-wp-customize-control.php' );
    113113                require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
    114114                require_once( ABSPATH . WPINC . '/class-wp-customize-nav-menus.php' );
     115                require_once( ABSPATH . WPINC . '/class-wp-customize-ajax-actions.php' );
    115116
    116117                $this->widgets = new WP_Customize_Widgets( $this );
    117118                $this->nav_menus = new WP_Customize_Nav_Menus( $this );
     119                $this->ajax_actions = new WP_Customize_Ajax_Actions();
    118120
    119121                add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
    120122
     
    12781280                $this->register_control_type( 'WP_Customize_Upload_Control' );
    12791281                $this->register_control_type( 'WP_Customize_Image_Control' );
    12801282                $this->register_control_type( 'WP_Customize_Background_Image_Control' );
     1283                $this->register_control_type( 'WP_Customize_Cropped_Image_Control' );
    12811284                $this->register_control_type( 'WP_Customize_Theme_Control' );
    12821285
    12831286                /* Themes */
     
    13241327                        ) ) );
    13251328                }
    13261329
    1327                 /* Site Title & Tagline */
     1330                /* Site Identity */
    13281331
    13291332                $this->add_section( 'title_tagline', array(
    1330                         'title'    => __( 'Site Title & Tagline' ),
     1333                        'title'    => __( 'Site Identity' ),
    13311334                        'priority' => 20,
    13321335                ) );
    13331336
     
    13531356                        'section'    => 'title_tagline',
    13541357                ) );
    13551358
     1359                $this->add_setting( 'site_icon', array(
     1360                        'type'       => 'option',
     1361                        'capability' => 'manage_options',
     1362                ) );
     1363
     1364                $this->add_control( new WP_Customize_Cropped_Image_Control( $this, 'site_icon', array(
     1365                        'label'       => __( 'Site Icon' ),
     1366                        'description' => __( 'The site icon is used as the browser and device icon for your site. Your theme may also display the site icon. Icons must be square, and at least 512px wide and tall.' ),
     1367                        'section'     => 'title_tagline',
     1368                        'priority'    => 60,
     1369                ) ) );
     1370
    13561371                /* Colors */
    13571372
    13581373                $this->add_section( 'colors', array(
     
    13751390                        'label'    => __( 'Display Header Text' ),
    13761391                        'section'  => 'title_tagline',
    13771392                        'type'     => 'checkbox',
     1393                        'priority' => 40,
    13781394                ) );
    13791395
    13801396                $this->add_control( new WP_Customize_Color_Control( $this, 'header_textcolor', array(
  • src/wp-includes/js/customize-views.js

     
    33        if ( ! wp || ! wp.customize ) { return; }
    44        var api = wp.customize;
    55
     6        /**
     7         * Use a custom ajax action for cropped image controls
     8         */
     9        wp.media.controller.customizeImageCropper = wp.media.controller.Cropper.extend( {
     10                doCrop: function( attachment ) {
     11                        var cropDetails = attachment.get( 'cropDetails' );
     12                        cropDetails.dst_height = this.collection.models[0].attributes.suggestedHeight;
     13                        cropDetails.dst_width = this.collection.models[0].attributes.suggestedWidth;
     14                        return wp.ajax.post( 'customize-image-crop', {
     15                                nonce: attachment.get( 'nonces' ).edit,
     16                                id: attachment.get( 'id' ),
     17                                cropDetails: cropDetails
     18                        } );
     19                }
     20        } );
    621
    722        /**
    823         * wp.customize.HeaderTool.CurrentView
     
    1631         * @augments wp.Backbone.View
    1732         */
    1833        api.HeaderTool.CurrentView = wp.Backbone.View.extend({
    19                 template: wp.template('header-current'),
     34                template: wp.template('cropped-current'),
    2035
    2136                initialize: function() {
    2237                        this.listenTo(this.model, 'change', this.render);
     
    86101         * Represents a choosable header image, be it user-uploaded,
    87102         * theme-suggested or a special Randomize choice.
    88103         *
    89          * Takes a wp.customize.HeaderTool.ImageModel.
     104         * Takes a wp.customize.CropTool.ImageModel.
    90105         *
    91106         * Manually changes model wp.customize.HeaderTool.currentHeader via the
    92107         * `select` method.