WordPress.org

Make WordPress Core

Changeset 41376


Ignore:
Timestamp:
09/13/17 06:07:48 (9 days ago)
Author:
westonruter
Message:

Editor: Add CodeMirror-powered code editor with syntax highlighting, linting, and auto-completion.

  • Code editor is integrated into the Theme/Plugin Editor, Additional CSS in Customizer, and Custom HTML widget. Code editor is not yet integrated into the post editor, and it may not be until accessibility concerns are addressed.
  • The CodeMirror component in the Custom HTML widget is integrated in a similar way to TinyMCE being integrated into the Text widget, adopting the same approach for integrating dynamic JavaScript-initialized fields.
  • Linting is performed for JS, CSS, HTML, and JSON via JSHint, CSSLint, HTMLHint, and JSONLint respectively. Linting is not yet supported for PHP.
  • When user lacks unfiltered_html the capability, the Custom HTML widget will report any Kses-invalid elements and attributes as errors via a custom Kses rule for HTMLHint.
  • When linting errors are detected, the user will be prevented from saving the code until the errors are fixed, reducing instances of broken websites.
  • The placeholder value is removed from Custom CSS in favor of a fleshed-out section description which now auto-expands when the CSS field is empty. See #39892.
  • The CodeMirror library is included as wp.CodeMirror to prevent conflicts with any existing CodeMirror global.
  • An wp.codeEditor.initialize() API in JS is provided to convert a textarea into CodeMirror, with a wp_enqueue_code_editor() function in PHP to manage enqueueing the assets and settings needed to edit a given type of code.
  • A user preference is added to manage whether or not "syntax highlighting" is enabled. The feature is opt-out, being enabled by default.
  • Allowed file extensions in the theme and plugin editors have been updated to include formats which CodeMirror has modes for: conf, css, diff, patch, html, htm, http, js, json, jsx, less, md, php, phtml, php3, php4, php5, php7, phps, scss, sass, sh, bash, sql, svg, xml, yml, yaml, txt.

Props westonruter, georgestephanis, obenland, melchoyce, pixolin, mizejewski, michelleweber, afercia, grahamarmfield, samikeijonen, rianrietveld, iseulde.
See #38707.
Fixes #12423, #39892.

Location:
trunk
Files:
12 added
21 edited

Legend:

Unmodified
Added
Removed
  • trunk/Gruntfile.js

    r41375 r41376  
    361361                    'wp-includes/js/tinymce/plugins/wp*/plugin.js', 
    362362                    // Third party scripts 
     363                    '!wp-includes/js/codemirror/*.js', 
    363364                    '!wp-admin/js/farbtastic.js', 
    364365                    '!wp-includes/js/backbone*.js', 
  • trunk/src/wp-admin/css/common.css

    r41356 r41376  
    22012201} 
    22022202 
    2203 #template div { 
     2203#template > div { 
    22042204    margin-right: 190px; 
    22052205} 
     
    30163016    font-family: Consolas, Monaco, monospace; 
    30173017    font-size: 13px; 
    3018     width: 97%; 
    30193018    background: #f9f9f9; 
    30203019    -moz-tab-size: 4; 
     
    30233022} 
    30243023 
     3024#template textarea, 
     3025#template .CodeMirror { 
     3026    width: 97%; 
     3027    height: calc( 100vh - 220px ); 
     3028} 
     3029 
    30253030/* rtl:ignore */ 
    30263031#template textarea, 
     
    30333038} 
    30343039 
     3040#file-editor-linting-error { 
     3041    margin-top: 1em; 
     3042    margin-bottom: 1em; 
     3043} 
     3044#file-editor-linting-error > .notice { 
     3045    margin: 0; 
     3046    display: inline-block; 
     3047} 
     3048#file-editor-linting-error > .notice > p { 
     3049    width: auto; 
     3050} 
     3051#template .submit { 
     3052    margin-top: 1em; 
     3053    padding: 0; 
     3054} 
     3055 
     3056#template .submit input[type=submit][disabled] { 
     3057    cursor: not-allowed; 
     3058} 
    30353059#templateside { 
    30363060    float: right; 
     
    35863610    } 
    35873611 
    3588     #template div { 
     3612    #template > div { 
    35893613        float: none; 
    35903614        margin: 0; 
     
    35923616    } 
    35933617 
     3618    #template .CodeMirror, 
    35943619    #template textarea { 
    35953620        width: 100%; 
  • trunk/src/wp-admin/css/customize-controls.css

    r41374 r41376  
    551551    margin-bottom: 0; 
    552552} 
     553.customize-section-description ul { 
     554    margin-left: 1em; 
     555} 
     556.customize-section-description ul > li { 
     557    list-style: disc; 
     558} 
     559.section-description-buttons { 
     560    text-align: right; 
     561} 
     562 
     563.section-description-buttons button.button-link { 
     564    color: #0073aa; 
     565    text-decoration: underline; 
     566} 
    553567 
    554568.customize-control { 
     
    11531167    width: calc( 100% + 24px ); 
    11541168    margin-bottom: -12px; 
     1169} 
     1170 
     1171.customize-section-description-container + #customize-control-custom_css:last-child .CodeMirror { 
     1172    height: calc( 100vh - 185px ); 
     1173} 
     1174.CodeMirror-lint-tooltip, 
     1175.CodeMirror-hints { 
     1176    z-index: 500000 !important; 
     1177} 
     1178 
     1179.customize-section-description-container + #customize-control-custom_css:last-child .customize-control-notifications-container { 
     1180    margin-left: 12px; 
     1181    margin-right: 12px; 
    11551182} 
    11561183 
  • trunk/src/wp-admin/css/widgets.css

    r41352 r41376  
    646646} 
    647647 
     648.custom-html-widget-fields > p > .CodeMirror { 
     649    border: 1px solid #e5e5e5; 
     650} 
     651.custom-html-widget-fields code { 
     652    padding-top: 1px; 
     653    padding-bottom: 1px; 
     654} 
     655ul.CodeMirror-hints { 
     656    z-index: 101; /* Due to z-index 100 set on .widget.open */ 
     657} 
     658.widget-control-actions .custom-html-widget-save-button.button.validation-blocked { 
     659    cursor: not-allowed; 
     660} 
     661 
    648662/* =Media Queries 
    649663-------------------------------------------------------------- */ 
  • trunk/src/wp-admin/includes/user.php

    r40940 r41376  
    9292 
    9393    if ( $update ) { 
    94         $user->rich_editing = isset( $_POST['rich_editing'] ) && 'false' == $_POST['rich_editing'] ? 'false' : 'true'; 
     94        $user->rich_editing = isset( $_POST['rich_editing'] ) && 'false' === $_POST['rich_editing'] ? 'false' : 'true'; 
     95        $user->syntax_highlighting = isset( $_POST['syntax_highlighting'] ) && 'false' === $_POST['syntax_highlighting'] ? 'false' : 'true'; 
    9596        $user->admin_color = isset( $_POST['admin_color'] ) ? sanitize_text_field( $_POST['admin_color'] ) : 'fresh'; 
    9697        $user->show_admin_bar_front = isset( $_POST['admin_bar_front'] ) ? 'true' : 'false'; 
  • trunk/src/wp-admin/js/customize-controls.js

    r41374 r41376  
    56325632        }); 
    56335633 
    5634         // Allow tabs to be entered in Custom CSS textarea. 
    5635         api.control( 'custom_css', function setupCustomCssControl( control ) { 
    5636             control.deferred.embedded.done( function allowTabs() { 
    5637                 var $textarea = control.container.find( 'textarea' ), textarea = $textarea[0]; 
    5638  
    5639                 $textarea.on( 'blur', function onBlur() { 
    5640                     $textarea.data( 'next-tab-blurs', false ); 
    5641                 } ); 
    5642  
    5643                 $textarea.on( 'keydown', function onKeydown( event ) { 
    5644                     var selectionStart, selectionEnd, value, tabKeyCode = 9, escKeyCode = 27; 
    5645  
    5646                     if ( escKeyCode === event.keyCode ) { 
    5647                         if ( ! $textarea.data( 'next-tab-blurs' ) ) { 
    5648                             $textarea.data( 'next-tab-blurs', true ); 
    5649                             event.stopPropagation(); // Prevent collapsing the section. 
     5634        // Add code editor for Custom CSS. 
     5635        (function() { 
     5636            var ready, sectionReady = $.Deferred(), controlReady = $.Deferred(); 
     5637 
     5638            api.section( 'custom_css', function( section ) { 
     5639                section.deferred.embedded.done( function() { 
     5640                    if ( section.expanded() ) { 
     5641                        sectionReady.resolve( section ); 
     5642                    } else { 
     5643                        section.expanded.bind( function( isExpanded ) { 
     5644                            if ( isExpanded ) { 
     5645                                sectionReady.resolve( section ); 
     5646                            } 
     5647                        } ); 
     5648                    } 
     5649                }); 
     5650            }); 
     5651            api.control( 'custom_css', function( control ) { 
     5652                control.deferred.embedded.done( function() { 
     5653                    controlReady.resolve( control ); 
     5654                }); 
     5655            }); 
     5656 
     5657            ready = $.when( sectionReady, controlReady ); 
     5658 
     5659            // Set up the section desription behaviors. 
     5660            ready.done( function setupSectionDescription( section, control ) { 
     5661 
     5662                // Close the section description when clicking the close button. 
     5663                section.container.find( '.section-description-buttons .section-description-close' ).on( 'click', function() { 
     5664                    section.container.find( '.section-meta .customize-section-description:first' ) 
     5665                        .removeClass( 'open' ) 
     5666                        .slideUp() 
     5667                        .attr( 'aria-expanded', 'false' ); 
     5668                }); 
     5669 
     5670                // Reveal help text if setting is empty. 
     5671                if ( ! control.setting.get() ) { 
     5672                    section.container.find( '.section-meta .customize-section-description:first' ) 
     5673                        .addClass( 'open' ) 
     5674                        .show() 
     5675                        .attr( 'aria-expanded', 'true' ); 
     5676                } 
     5677            }); 
     5678 
     5679            // Set up the code editor itself. 
     5680            if ( api.settings.customCss && api.settings.customCss.codeEditor ) { 
     5681 
     5682                // Set up the syntax highlighting editor. 
     5683                ready.done( function setupSyntaxHighlightingEditor( section, control ) { 
     5684                    var $textarea = control.container.find( 'textarea' ), settings, suspendEditorUpdate = false; 
     5685 
     5686                    // Make sure editor gets focused when control is focused. 
     5687                    control.focus = (function( originalFocus ) { // eslint-disable-line max-nested-callbacks 
     5688                        return function( params ) { // eslint-disable-line max-nested-callbacks 
     5689                            var extendedParams = _.extend( {}, params ), originalCompleteCallback; 
     5690                            originalCompleteCallback = extendedParams.completeCallback; 
     5691                            extendedParams.completeCallback = function() { 
     5692                                if ( originalCompleteCallback ) { 
     5693                                    originalCompleteCallback(); 
     5694                                } 
     5695                                if ( control.editor ) { 
     5696                                    control.editor.codemirror.focus(); 
     5697                                } 
     5698                            }; 
     5699                            originalFocus.call( this, extendedParams ); 
     5700                        }; 
     5701                    })( control.focus ); 
     5702 
     5703                    settings = _.extend( {}, api.settings.customCss.codeEditor, { 
     5704 
     5705                        /** 
     5706                         * Handle tabbing to the field after the editor. 
     5707                         * 
     5708                         * @returns {void} 
     5709                         */ 
     5710                        onTabNext: function onTabNext() { 
     5711                            var controls, controlIndex; 
     5712                            controls = section.controls(); 
     5713                            controlIndex = controls.indexOf( control ); 
     5714                            if ( controls.length === controlIndex + 1 ) { 
     5715                                $( '#customize-footer-actions .collapse-sidebar' ).focus(); 
     5716                            } else { 
     5717                                controls[ controlIndex + 1 ].container.find( ':focusable:first' ).focus(); 
     5718                            } 
     5719                        }, 
     5720 
     5721                        /** 
     5722                         * Handle tabbing to the field before the editor. 
     5723                         * 
     5724                         * @returns {void} 
     5725                         */ 
     5726                        onTabPrevious: function onTabPrevious() { 
     5727                            var controls, controlIndex; 
     5728                            controls = section.controls(); 
     5729                            controlIndex = controls.indexOf( control ); 
     5730                            if ( 0 === controlIndex ) { 
     5731                                section.contentContainer.find( '.customize-section-title .customize-help-toggle, .customize-section-title .customize-section-description.open .section-description-close' ).last().focus(); 
     5732                            } else { 
     5733                                controls[ controlIndex - 1 ].contentContainer.find( ':focusable:first' ).focus(); 
     5734                            } 
     5735                        }, 
     5736 
     5737                        /** 
     5738                         * Update error notice. 
     5739                         * 
     5740                         * @param {Array} errorAnnotations - Error annotations. 
     5741                         * @returns {void} 
     5742                         */ 
     5743                        onUpdateErrorNotice: function onUpdateErrorNotice( errorAnnotations ) { 
     5744                            var message; 
     5745                            control.setting.notifications.remove( 'csslint_error' ); 
     5746 
     5747                            if ( 0 !== errorAnnotations.length ) { 
     5748                                if ( 1 === errorAnnotations.length ) { 
     5749                                    message = api.l10n.customCssError.singular.replace( '%d', '1' ); 
     5750                                } else { 
     5751                                    message = api.l10n.customCssError.plural.replace( '%d', String( errorAnnotations.length ) ); 
     5752                                } 
     5753                                control.setting.notifications.add( 'csslint_error', new api.Notification( 'csslint_error', { 
     5754                                    message: message, 
     5755                                    type: 'error' 
     5756                                } ) ); 
     5757                            } 
    56505758                        } 
    5651                         return; 
    5652                     } 
    5653  
    5654                     // Short-circuit if tab key is not being pressed or if a modifier key *is* being pressed. 
    5655                     if ( tabKeyCode !== event.keyCode || event.ctrlKey || event.altKey || event.shiftKey ) { 
    5656                         return; 
    5657                     } 
    5658  
    5659                     // Prevent capturing Tab characters if Esc was pressed. 
    5660                     if ( $textarea.data( 'next-tab-blurs' ) ) { 
    5661                         return; 
    5662                     } 
    5663  
    5664                     selectionStart = textarea.selectionStart; 
    5665                     selectionEnd = textarea.selectionEnd; 
    5666                     value = textarea.value; 
    5667  
    5668                     if ( selectionStart >= 0 ) { 
    5669                         textarea.value = value.substring( 0, selectionStart ).concat( '\t', value.substring( selectionEnd ) ); 
    5670                         $textarea.selectionStart = textarea.selectionEnd = selectionStart + 1; 
    5671                     } 
    5672  
    5673                     event.stopPropagation(); 
    5674                     event.preventDefault(); 
    5675                 } ); 
    5676             } ); 
    5677         } ); 
     5759                    }); 
     5760 
     5761                    control.editor = wp.codeEditor.initialize( $textarea, settings ); 
     5762 
     5763                    // Refresh when receiving focus. 
     5764                    control.editor.codemirror.on( 'focus', function( codemirror ) { 
     5765                        codemirror.refresh(); 
     5766                    }); 
     5767 
     5768                    /* 
     5769                     * When the CodeMirror instance changes, mirror to the textarea, 
     5770                     * where we have our "true" change event handler bound. 
     5771                     */ 
     5772                    control.editor.codemirror.on( 'change', function( codemirror ) { 
     5773                        suspendEditorUpdate = true; 
     5774                        $textarea.val( codemirror.getValue() ).trigger( 'change' ); 
     5775                        suspendEditorUpdate = false; 
     5776                    }); 
     5777 
     5778                    // Update CodeMirror when the setting is changed by another plugin. 
     5779                    control.setting.bind( function( value ) { 
     5780                        if ( ! suspendEditorUpdate ) { 
     5781                            control.editor.codemirror.setValue( value ); 
     5782                        } 
     5783                    }); 
     5784 
     5785                    // Prevent collapsing section when hitting Esc to tab out of editor. 
     5786                    control.editor.codemirror.on( 'keydown', function onKeydown( codemirror, event ) { 
     5787                        var escKeyCode = 27; 
     5788                        if ( escKeyCode === event.keyCode ) { 
     5789                            event.stopPropagation(); 
     5790                        } 
     5791                    }); 
     5792                }); 
     5793            } else { 
     5794 
     5795                // Allow tabs to be entered in Custom CSS textarea. 
     5796                ready.done( function allowTabs( section, control ) { 
     5797 
     5798                    var $textarea = control.container.find( 'textarea' ), textarea = $textarea[0]; 
     5799 
     5800                    $textarea.on( 'blur', function onBlur() { 
     5801                        $textarea.data( 'next-tab-blurs', false ); 
     5802                    } ); 
     5803 
     5804                    $textarea.on( 'keydown', function onKeydown( event ) { 
     5805                        var selectionStart, selectionEnd, value, tabKeyCode = 9, escKeyCode = 27; 
     5806 
     5807                        if ( escKeyCode === event.keyCode ) { 
     5808                            if ( ! $textarea.data( 'next-tab-blurs' ) ) { 
     5809                                $textarea.data( 'next-tab-blurs', true ); 
     5810                                event.stopPropagation(); // Prevent collapsing the section. 
     5811                            } 
     5812                            return; 
     5813                        } 
     5814 
     5815                        // Short-circuit if tab key is not being pressed or if a modifier key *is* being pressed. 
     5816                        if ( tabKeyCode !== event.keyCode || event.ctrlKey || event.altKey || event.shiftKey ) { 
     5817                            return; 
     5818                        } 
     5819 
     5820                        // Prevent capturing Tab characters if Esc was pressed. 
     5821                        if ( $textarea.data( 'next-tab-blurs' ) ) { 
     5822                            return; 
     5823                        } 
     5824 
     5825                        selectionStart = textarea.selectionStart; 
     5826                        selectionEnd = textarea.selectionEnd; 
     5827                        value = textarea.value; 
     5828 
     5829                        if ( selectionStart >= 0 ) { 
     5830                            textarea.value = value.substring( 0, selectionStart ).concat( '\t', value.substring( selectionEnd ) ); 
     5831                            $textarea.selectionStart = textarea.selectionEnd = selectionStart + 1; 
     5832                        } 
     5833 
     5834                        event.stopPropagation(); 
     5835                        event.preventDefault(); 
     5836                    }); 
     5837                }); 
     5838            } 
     5839        })(); 
    56785840 
    56795841        // Toggle visibility of Header Video notice when active state change. 
  • trunk/src/wp-admin/plugin-editor.php

    r38745 r41376  
    116116 
    117117    // List of allowable extensions 
    118     $editable_extensions = array('php', 'txt', 'text', 'js', 'css', 'html', 'htm', 'xml', 'inc', 'include'); 
     118    $editable_extensions = array( 
     119        'bash', 
     120        'conf', 
     121        'css', 
     122        'diff', 
     123        'htm', 
     124        'html', 
     125        'http', 
     126        'inc', 
     127        'include', 
     128        'js', 
     129        'json', 
     130        'jsx', 
     131        'less', 
     132        'md', 
     133        'patch', 
     134        'php', 
     135        'php3', 
     136        'php4', 
     137        'php5', 
     138        'php7', 
     139        'phps', 
     140        'phtml', 
     141        'sass', 
     142        'scss', 
     143        'sh', 
     144        'sql', 
     145        'svg', 
     146        'text', 
     147        'txt', 
     148        'xml', 
     149        'yaml', 
     150        'yml', 
     151    ); 
    119152 
    120153    /** 
     
    157190        '<p>' . __('<a href="https://wordpress.org/support/">Support Forums</a>') . '</p>' 
    158191    ); 
     192 
     193    $settings = wp_enqueue_code_editor( array( 'file' => $real_file ) ); 
     194    if ( ! empty( $settings ) ) { 
     195        wp_enqueue_script( 'wp-theme-plugin-editor' ); 
     196        wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function() { wp.themePluginEditor.init( %s ); } )', wp_json_encode( $settings ) ) ); 
     197    } 
    159198 
    160199    require_once(ABSPATH . 'wp-admin/admin-header.php'); 
  • trunk/src/wp-admin/theme-editor.php

    r38722 r41376  
    6464$allowed_files = $style_files = array(); 
    6565$has_templates = false; 
    66 $default_types = array( 'php', 'css' ); 
     66$default_types = array( 
     67    'bash', 
     68    'conf', 
     69    'css', 
     70    'diff', 
     71    'htm', 
     72    'html', 
     73    'http', 
     74    'inc', 
     75    'include', 
     76    'js', 
     77    'json', 
     78    'jsx', 
     79    'less', 
     80    'md', 
     81    'patch', 
     82    'php', 
     83    'php3', 
     84    'php4', 
     85    'php5', 
     86    'php7', 
     87    'phps', 
     88    'phtml', 
     89    'sass', 
     90    'scss', 
     91    'sh', 
     92    'sql', 
     93    'svg', 
     94    'text', 
     95    'txt', 
     96    'xml', 
     97    'yaml', 
     98    'yml', 
     99); 
    67100 
    68101/** 
     
    126159 
    127160default: 
     161 
     162    $settings = wp_enqueue_code_editor( compact( 'file' ) ); 
     163    if ( ! empty( $settings ) ) { 
     164        wp_enqueue_script( 'wp-theme-plugin-editor' ); 
     165        wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function() { wp.themePluginEditor.init( %s ); } )', wp_json_encode( $settings ) ) ); 
     166    } 
    128167 
    129168    require_once( ABSPATH . 'wp-admin/admin-header.php' ); 
  • trunk/src/wp-admin/user-edit.php

    r41163 r41376  
    246246        <th scope="row"><?php _e( 'Visual Editor' ); ?></th> 
    247247        <td><label for="rich_editing"><input name="rich_editing" type="checkbox" id="rich_editing" value="false" <?php if ( ! empty( $profileuser->rich_editing ) ) checked( 'false', $profileuser->rich_editing ); ?> /> <?php _e( 'Disable the visual editor when writing' ); ?></label></td> 
     248    </tr> 
     249<?php endif; ?> 
     250<?php 
     251$show_syntax_highlighting_preference = ( 
     252    // For Custom HTML widget and Additional CSS in Customizer. 
     253    user_can( $profileuser, 'edit_theme_options' ) 
     254    || 
     255    // Edit plugins. 
     256    user_can( $profileuser, 'edit_plugins' ) 
     257    || 
     258    // Edit themes. 
     259    user_can( $profileuser, 'edit_themes' ) 
     260); 
     261?> 
     262<?php if ( $show_syntax_highlighting_preference ) : ?> 
     263    <tr class="user-syntax-highlighting-wrap"> 
     264        <th scope="row"><?php _e( 'Syntax Highlighting' ); ?></th> 
     265        <td> 
     266            <label for="syntax_highlighting"><input name="syntax_highlighting" type="checkbox" id="syntax_highlighting" value="false" <?php if ( ! empty( $profileuser->syntax_highlighting ) ) checked( 'false', $profileuser->syntax_highlighting ); ?> /> <?php _e( 'Disable syntax highlighting when editing code' ); ?></label> 
     267        </td> 
    248268    </tr> 
    249269<?php endif; ?> 
  • trunk/src/wp-includes/author-template.php

    r40952 r41376  
    129129 * - plugins_per_page 
    130130 * - rich_editing 
     131 * - syntax_highlighting 
    131132 * - user_activation_key 
    132133 * - user_description 
  • trunk/src/wp-includes/class-wp-customize-manager.php

    r41374 r41376  
    212212     */ 
    213213    private $_changeset_data; 
     214 
     215    /** 
     216     * Code Editor Settings for Custom CSS. 
     217     * 
     218     * This variable contains the settings returned by `wp_enqueue_code_editor()` which are then later output 
     219     * to the client in `WP_Customize_Manager::customize_pane_settings()`. A value of false means that the 
     220     * Custom CSS section or control was removed, or that the Syntax Highlighting user pref was turned off. 
     221     * 
     222     * @see wp_enqueue_code_editor() 
     223     * @see WP_Customize_Manager::enqueue_control_scripts() 
     224     * @see WP_Customize_Manager::customize_pane_settings() 
     225     * @since 4.9.0 
     226     * @var array|false 
     227     */ 
     228    private $_custom_css_code_editor_settings = false; 
    214229 
    215230    /** 
     
    33233338            $control->enqueue(); 
    33243339        } 
     3340 
     3341        if ( $this->get_section( 'custom_css' ) && $this->get_control( 'custom_css' ) ) { 
     3342            $this->_custom_css_code_editor_settings = wp_enqueue_code_editor( array( 
     3343                'type' => 'text/css', 
     3344            ) ); 
     3345        } 
    33253346    } 
    33263347 
     
    35793600                'stylesheet' => $this->get_stylesheet(), 
    35803601                'active'     => $this->is_theme_active(), 
     3602            ), 
     3603            'customCss' => array( 
     3604                'codeEditor' => $this->_custom_css_code_editor_settings, 
    35813605            ), 
    35823606            'url'      => array( 
     
    41784202 
    41794203        /* Custom CSS */ 
     4204        $section_description = '<p>'; 
     4205        $section_description .= __( 'Add your own CSS code here to customize the appearance and layout of your site.', 'better-code-editing' ); 
     4206        $section_description .= sprintf( 
     4207            ' <a href="%1$s" class="external-link" target="_blank">%2$s<span class="screen-reader-text">%3$s</span></a>', 
     4208            esc_url( __( 'https://codex.wordpress.org/CSS', 'default' ) ), 
     4209            __( 'Learn more about CSS', 'default' ), 
     4210            /* translators: accessibility text */ 
     4211            __( '(opens in a new window)', 'default' ) 
     4212        ); 
     4213        $section_description .= '</p>'; 
     4214 
     4215        $section_description .= '<p>' . __( 'When using a keyboard to navigate:', 'better-code-editing' ) . '</p>'; 
     4216        $section_description .= '<ul>'; 
     4217        $section_description .= '<li>' . __( 'In the CSS edit field, Tab enters a tab character.', 'better-code-editing' ) . '</li>'; 
     4218        $section_description .= '<li>' . __( 'To move keyboard focus, press Esc then Tab for the next element, or Esc then Shift+Tab for the previous element.', 'better-code-editing' ) . '</li>'; 
     4219        $section_description .= '</ul>'; 
     4220 
     4221        if ( 'false' !== wp_get_current_user()->syntax_highlighting ) { 
     4222            $section_description .= '<p>'; 
     4223            $section_description .= sprintf( 
     4224                /* translators: placeholder is link to user profile */ 
     4225                __( 'The edit field automatically highlights code syntax. You can disable this in your %s to work in plain text mode.', 'better-code-editing' ), 
     4226                sprintf( 
     4227                    ' <a href="%1$s" class="external-link" target="_blank">%2$s<span class="screen-reader-text">%3$s</span></a>', 
     4228                    esc_url( get_edit_profile_url() . '#syntax_highlighting' ), 
     4229                    __( 'user profile', 'better-code-editing' ), 
     4230                    /* translators: accessibility text */ 
     4231                    __( '(opens in a new window)', 'default' ) 
     4232                ) 
     4233            ); 
     4234            $section_description .= '</p>'; 
     4235        } 
     4236 
     4237        $section_description .= '<p class="section-description-buttons">'; 
     4238        $section_description .= '<button type="button" class="button-link section-description-close">' . __( 'Close', 'default' ) . '</button>'; 
     4239        $section_description .= '</p>'; 
     4240 
    41804241        $this->add_section( 'custom_css', array( 
    41814242            'title'              => __( 'Additional CSS' ), 
    41824243            'priority'           => 200, 
    41834244            'description_hidden' => true, 
    4184             'description'        => sprintf( '%s<br /><a href="%s" class="external-link" target="_blank">%s<span class="screen-reader-text">%s</span></a>', 
    4185                 __( 'CSS allows you to customize the appearance and layout of your site with code. Separate CSS is saved for each of your themes. In the editing area the Tab key enters a tab character. To move below this area by pressing Tab, press the Esc key followed by the Tab key.' ), 
    4186                 esc_url( __( 'https://codex.wordpress.org/CSS' ) ), 
    4187                 __( 'Learn more about CSS' ), 
    4188                 /* translators: accessibility text */ 
    4189                 __( '(opens in a new window)' ) 
    4190             ), 
     4245            'description'        => $section_description, 
    41914246        ) ); 
    41924247 
    41934248        $custom_css_setting = new WP_Customize_Custom_CSS_Setting( $this, sprintf( 'custom_css[%s]', get_stylesheet() ), array( 
    41944249            'capability' => 'edit_css', 
    4195             'default' => sprintf( "/*\n%s\n*/", __( "You can add your own CSS here.\n\nClick the help icon above to learn more." ) ), 
     4250            'default' => '', 
    41964251        ) ); 
    41974252        $this->add_setting( $custom_css_setting ); 
  • trunk/src/wp-includes/class-wp-user.php

    r41366 r41376  
    3333 * @property string $deleted 
    3434 * @property string $locale 
     35 * @property string $rich_editing 
     36 * @property string $syntax_highlighting 
    3537 */ 
    3638class WP_User { 
  • trunk/src/wp-includes/customize/class-wp-customize-custom-css-setting.php

    r41162 r41376  
    149149     * Notifications are rendered when the customizer state is saved. 
    150150     * 
    151      * @todo There are cases where valid CSS can be incorrectly marked as invalid when strings or comments include balancing characters. To fix, CSS tokenization needs to be used. 
    152      * 
    153151     * @since 4.7.0 
     152     * @since 4.9.0 Checking for balanced characters has been moved client-side via linting in code editor. 
    154153     * 
    155154     * @param string $css The input string. 
     
    161160        if ( preg_match( '#</?\w+#', $css ) ) { 
    162161            $validity->add( 'illegal_markup', __( 'Markup is not allowed in CSS.' ) ); 
    163         } 
    164  
    165         $imbalanced = false; 
    166  
    167         // Make sure that there is a closing brace for each opening brace. 
    168         if ( ! $this->validate_balanced_characters( '{', '}', $css ) ) { 
    169             $validity->add( 'imbalanced_curly_brackets', sprintf( 
    170                 /* translators: 1: {}, 2: }, 3: { */ 
    171                 __( 'Your curly brackets %1$s are imbalanced. Make sure there is a closing %2$s for every opening %3$s.' ), 
    172                 '<code>{}</code>', 
    173                 '<code>}</code>', 
    174                 '<code>{</code>' 
    175             ) ); 
    176             $imbalanced = true; 
    177         } 
    178  
    179         // Ensure brackets are balanced. 
    180         if ( ! $this->validate_balanced_characters( '[', ']', $css ) ) { 
    181             $validity->add( 'imbalanced_braces', sprintf( 
    182                 /* translators: 1: [], 2: ], 3: [ */ 
    183                 __( 'Your brackets %1$s are imbalanced. Make sure there is a closing %2$s for every opening %3$s.' ), 
    184                 '<code>[]</code>', 
    185                 '<code>]</code>', 
    186                 '<code>[</code>' 
    187             ) ); 
    188             $imbalanced = true; 
    189         } 
    190  
    191         // Ensure parentheses are balanced. 
    192         if ( ! $this->validate_balanced_characters( '(', ')', $css ) ) { 
    193             $validity->add( 'imbalanced_parentheses', sprintf( 
    194                 /* translators: 1: (), 2: ), 3: ( */ 
    195                 __( 'Your parentheses %1$s are imbalanced. Make sure there is a closing %2$s for every opening %3$s.' ), 
    196                 '<code>()</code>', 
    197                 '<code>)</code>', 
    198                 '<code>(</code>' 
    199             ) ); 
    200             $imbalanced = true; 
    201         } 
    202  
    203         // Ensure double quotes are equal. 
    204         if ( ! $this->validate_equal_characters( '"', $css ) ) { 
    205             $validity->add( 'unequal_double_quotes', sprintf( 
    206                 /* translators: 1: " (double quote) */ 
    207                 __( 'Your double quotes %1$s are uneven. Make sure there is a closing %1$s for every opening %1$s.' ), 
    208                 '<code>"</code>' 
    209             ) ); 
    210             $imbalanced = true; 
    211         } 
    212  
    213         /* 
    214          * Make sure any code comments are closed properly. 
    215          * 
    216          * The first check could miss stray an unpaired comment closing figure, so if 
    217          * The number appears to be balanced, then check for equal numbers 
    218          * of opening/closing comment figures. 
    219          * 
    220          * Although it may initially appear redundant, we use the first method 
    221          * to give more specific feedback to the user. 
    222          */ 
    223         $unclosed_comment_count = $this->validate_count_unclosed_comments( $css ); 
    224         if ( 0 < $unclosed_comment_count ) { 
    225             $validity->add( 'unclosed_comment', sprintf( 
    226                 /* translators: 1: number of unclosed comments, 2: *​/ */ 
    227                 _n( 
    228                     'There is %1$s unclosed code comment. Close each comment with %2$s.', 
    229                     'There are %1$s unclosed code comments. Close each comment with %2$s.', 
    230                     $unclosed_comment_count 
    231                 ), 
    232                 $unclosed_comment_count, 
    233                 '<code>*/</code>' 
    234             ) ); 
    235             $imbalanced = true; 
    236         } elseif ( ! $this->validate_balanced_characters( '/*', '*/', $css ) ) { 
    237             $validity->add( 'imbalanced_comments', sprintf( 
    238                 /* translators: 1: *​/, 2: /​* */ 
    239                 __( 'There is an extra %1$s, indicating an end to a comment. Be sure that there is an opening %2$s for every closing %1$s.' ), 
    240                 '<code>*/</code>', 
    241                 '<code>/*</code>' 
    242             ) ); 
    243             $imbalanced = true; 
    244         } 
    245         if ( $imbalanced && $this->is_possible_content_error( $css ) ) { 
    246             $validity->add( 'possible_false_positive', sprintf( 
    247                 /* translators: %s: content: ""; */ 
    248                 __( 'Imbalanced/unclosed character errors can be caused by %s declarations. You may need to remove this or add it to a custom CSS file.' ), 
    249                 '<code>content: "";</code>' 
    250             ) ); 
    251162        } 
    252163 
     
    286197        return $post_id; 
    287198    } 
    288  
    289     /** 
    290      * Ensure there are a balanced number of paired characters. 
    291      * 
    292      * This is used to check that the number of opening and closing 
    293      * characters is equal. 
    294      * 
    295      * For instance, there should be an equal number of braces ("{", "}") 
    296      * in the CSS. 
    297      * 
    298      * @since 4.7.0 
    299      * 
    300      * @param string $opening_char The opening character. 
    301      * @param string $closing_char The closing character. 
    302      * @param string $css The CSS input string. 
    303      * 
    304      * @return bool 
    305      */ 
    306     private function validate_balanced_characters( $opening_char, $closing_char, $css ) { 
    307         return substr_count( $css, $opening_char ) === substr_count( $css, $closing_char ); 
    308     } 
    309  
    310     /** 
    311      * Ensure there are an even number of paired characters. 
    312      * 
    313      * This is used to check that the number of a specific 
    314      * character is even. 
    315      * 
    316      * For instance, there should be an even number of double quotes 
    317      * in the CSS. 
    318      * 
    319      * @since 4.7.0 
    320      * 
    321      * @param string $char A character. 
    322      * @param string $css The CSS input string. 
    323      * @return bool Equality. 
    324      */ 
    325     private function validate_equal_characters( $char, $css ) { 
    326         $char_count = substr_count( $css, $char ); 
    327         return ( 0 === $char_count % 2 ); 
    328     } 
    329  
    330     /** 
    331      * Count unclosed CSS Comments. 
    332      * 
    333      * Used during validation. 
    334      * 
    335      * @see self::validate() 
    336      * 
    337      * @since 4.7.0 
    338      * 
    339      * @param string $css The CSS input string. 
    340      * @return int Count. 
    341      */ 
    342     private function validate_count_unclosed_comments( $css ) { 
    343         $count = 0; 
    344         $comments = explode( '/*', $css ); 
    345  
    346         if ( ! is_array( $comments ) || ( 1 >= count( $comments ) ) ) { 
    347             return $count; 
    348         } 
    349  
    350         unset( $comments[0] ); // The first item is before the first comment. 
    351         foreach ( $comments as $comment ) { 
    352             if ( false === strpos( $comment, '*/' ) ) { 
    353                 $count++; 
    354             } 
    355         } 
    356         return $count; 
    357     } 
    358  
    359     /** 
    360      * Find "content:" within a string. 
    361      * 
    362      * Imbalanced/Unclosed validation errors may be caused 
    363      * when a character is used in a "content:" declaration. 
    364      * 
    365      * This function is used to detect if this is a possible 
    366      * cause of the validation error, so that if it is, 
    367      * a notification may be added to the Validation Errors. 
    368      * 
    369      * Example: 
    370      * .element::before { 
    371      *   content: "(\""; 
    372      * } 
    373      * .element::after { 
    374      *   content: "\")"; 
    375      * } 
    376      * 
    377      * Using ! empty() because strpos() may return non-boolean values 
    378      * that evaluate to false. This would be problematic when 
    379      * using a strict "false === strpos()" comparison. 
    380      * 
    381      * @since 4.7.0 
    382      * 
    383      * @param string $css The CSS input string. 
    384      * @return bool 
    385      */ 
    386     private function is_possible_content_error( $css ) { 
    387         $found = preg_match( '/\bcontent\s*:/', $css ); 
    388         if ( ! empty( $found ) ) { 
    389             return true; 
    390         } 
    391         return false; 
    392     } 
    393199} 
  • trunk/src/wp-includes/general-template.php

    r41371 r41376  
    31173117 
    31183118/** 
     3119 * Enqueue assets needed by the code editor for the given settings. 
     3120 * 
     3121 * @since 4.9.0 
     3122 * 
     3123 * @see wp_enqueue_editor() 
     3124 * @see _WP_Editors::parse_settings() 
     3125 * @param array $args { 
     3126 *     Args. 
     3127 * 
     3128 *     @type string   $type     The MIME type of the file to be edited. 
     3129 *     @type string   $file     Filename to be edited. Extension is used to sniff the type. Can be supplied as alternative to `$type` param. 
     3130 *     @type array    $settings Settings to merge on top of defaults which derive from `$type` or `$file` args. 
     3131 *     @type WP_Theme $theme    Theme being edited when on theme editor. 
     3132 *     @type string   $plugin   Plugin being edited when on plugin editor. 
     3133 * } 
     3134 * @returns array|false Settings for the enqueued code editor, or false if the editor was not enqueued . 
     3135 */ 
     3136function wp_enqueue_code_editor( $args ) { 
     3137    if ( is_user_logged_in() && 'false' === wp_get_current_user()->syntax_highlighting ) { 
     3138        return false; 
     3139    } 
     3140 
     3141    $settings = array( 
     3142        'codemirror' => array( 
     3143            'indentUnit' => 4, 
     3144            'indentWithTabs' => true, 
     3145            'inputStyle' => 'contenteditable', 
     3146            'lineNumbers' => true, 
     3147            'lineWrapping' => true, 
     3148            'styleActiveLine' => true, 
     3149            'continueComments' => true, 
     3150            'extraKeys' => array( 
     3151                'Ctrl-Space' => 'autocomplete', 
     3152                'Ctrl-/' => 'toggleComment', 
     3153                'Cmd-/' => 'toggleComment', 
     3154                'Alt-F' => 'findPersistent', 
     3155            ), 
     3156            'direction' => 'ltr', // Code is shown in LTR even in RTL languages. 
     3157        ), 
     3158        'csslint' => array( 
     3159            'errors' => true, // Parsing errors. 
     3160            'box-model' => true, 
     3161            'display-property-grouping' => true, 
     3162            'duplicate-properties' => true, 
     3163            'known-properties' => true, 
     3164            'outline-none' => true, 
     3165        ), 
     3166        'jshint' => array( 
     3167            // The following are copied from <https://github.com/WordPress/wordpress-develop/blob/4.8.1/.jshintrc>. 
     3168            'boss' => true, 
     3169            'curly' => true, 
     3170            'eqeqeq' => true, 
     3171            'eqnull' => true, 
     3172            'es3' => true, 
     3173            'expr' => true, 
     3174            'immed' => true, 
     3175            'noarg' => true, 
     3176            'nonbsp' => true, 
     3177            'onevar' => true, 
     3178            'quotmark' => 'single', 
     3179            'trailing' => true, 
     3180            'undef' => true, 
     3181            'unused' => true, 
     3182 
     3183            'browser' => true, 
     3184 
     3185            'globals' => array( 
     3186                '_' => false, 
     3187                'Backbone' => false, 
     3188                'jQuery' => false, 
     3189                'JSON' => false, 
     3190                'wp' => false, 
     3191            ), 
     3192        ), 
     3193        'htmlhint' => array( 
     3194            'tagname-lowercase' => true, 
     3195            'attr-lowercase' => true, 
     3196            'attr-value-double-quotes' => true, 
     3197            'doctype-first' => false, 
     3198            'tag-pair' => true, 
     3199            'spec-char-escape' => true, 
     3200            'id-unique' => true, 
     3201            'src-not-empty' => true, 
     3202            'attr-no-duplication' => true, 
     3203            'alt-require' => true, 
     3204            'space-tab-mixed-disabled' => 'tab', 
     3205            'attr-unsafe-chars' => true, 
     3206        ), 
     3207    ); 
     3208 
     3209    $type = ''; 
     3210    if ( isset( $args['type'] ) ) { 
     3211        $type = $args['type']; 
     3212 
     3213        // Remap MIME types to ones that CodeMirror modes will recognize. 
     3214        if ( 'application/x-patch' === $type || 'text/x-patch' === $type ) { 
     3215            $type = 'text/x-diff'; 
     3216        } 
     3217    } elseif ( isset( $args['file'] ) && false !== strpos( basename( $args['file'] ), '.' ) ) { 
     3218        $extension = strtolower( pathinfo( $args['file'], PATHINFO_EXTENSION ) ); 
     3219        foreach ( wp_get_mime_types() as $exts => $mime ) { 
     3220            if ( preg_match( '!^(' . $exts . ')$!i', $extension ) ) { 
     3221                $type = $mime; 
     3222                break; 
     3223            } 
     3224        } 
     3225 
     3226        // Supply any types that are not matched by wp_get_mime_types(). 
     3227        if ( empty( $type ) ) { 
     3228            switch ( $extension ) { 
     3229                case 'conf': 
     3230                    $type = 'text/nginx'; 
     3231                    break; 
     3232                case 'css': 
     3233                    $type = 'text/css'; 
     3234                    break; 
     3235                case 'diff': 
     3236                case 'patch': 
     3237                    $type = 'text/x-diff'; 
     3238                    break; 
     3239                case 'html': 
     3240                case 'htm': 
     3241                    $type = 'text/html'; 
     3242                    break; 
     3243                case 'http': 
     3244                    $type = 'message/http'; 
     3245                    break; 
     3246                case 'js': 
     3247                    $type = 'text/javascript'; 
     3248                    break; 
     3249                case 'json': 
     3250                    $type = 'application/json'; 
     3251                    break; 
     3252                case 'jsx': 
     3253                    $type = 'text/jsx'; 
     3254                    break; 
     3255                case 'less': 
     3256                    $type = 'text/x-less'; 
     3257                    break; 
     3258                case 'md': 
     3259                    $type = 'text/x-gfm'; 
     3260                    break; 
     3261                case 'php': 
     3262                case 'phtml': 
     3263                case 'php3': 
     3264                case 'php4': 
     3265                case 'php5': 
     3266                case 'php7': 
     3267                case 'phps': 
     3268                    $type = 'application/x-httpd-php'; 
     3269                    break; 
     3270                case 'scss': 
     3271                    $type = 'text/x-scss'; 
     3272                    break; 
     3273                case 'sass': 
     3274                    $type = 'text/x-sass'; 
     3275                    break; 
     3276                case 'sh': 
     3277                case 'bash': 
     3278                    $type = 'text/x-sh'; 
     3279                    break; 
     3280                case 'sql': 
     3281                    $type = 'text/x-sql'; 
     3282                    break; 
     3283                case 'svg': 
     3284                    $type = 'application/svg+xml'; 
     3285                    break; 
     3286                case 'xml': 
     3287                    $type = 'text/xml'; 
     3288                    break; 
     3289                case 'yml': 
     3290                case 'yaml': 
     3291                    $type = 'text/x-yaml'; 
     3292                    break; 
     3293                case 'txt': 
     3294                default: 
     3295                    $type = 'text/plain'; 
     3296                    break; 
     3297            } 
     3298        } 
     3299    } 
     3300 
     3301    if ( 'text/css' === $type ) { 
     3302        $settings['codemirror'] = array_merge( $settings['codemirror'], array( 
     3303            'mode' => 'css', 
     3304            'lint' => true, 
     3305            'autoCloseBrackets' => true, 
     3306            'matchBrackets' => true, 
     3307        ) ); 
     3308    } elseif ( 'text/x-scss' === $type || 'text/x-less' === $type || 'text/x-sass' === $type ) { 
     3309        $settings['codemirror'] = array_merge( $settings['codemirror'], array( 
     3310            'mode' => $type, 
     3311            'autoCloseBrackets' => true, 
     3312            'matchBrackets' => true, 
     3313        ) ); 
     3314    } elseif ( 'text/x-diff' === $type ) { 
     3315        $settings['codemirror'] = array_merge( $settings['codemirror'], array( 
     3316            'mode' => 'diff', 
     3317        ) ); 
     3318    } elseif ( 'text/html' === $type ) { 
     3319        $settings['codemirror'] = array_merge( $settings['codemirror'], array( 
     3320            'mode' => 'htmlmixed', 
     3321            'lint' => true, 
     3322            'autoCloseBrackets' => true, 
     3323            'autoCloseTags' => true, 
     3324            'matchTags' => array( 
     3325                'bothTags' => true, 
     3326            ), 
     3327        ) ); 
     3328 
     3329        if ( ! current_user_can( 'unfiltered_html' ) ) { 
     3330            $settings['htmlhint']['kses'] = wp_kses_allowed_html( 'post' ); 
     3331        } 
     3332    } elseif ( 'text/x-gfm' === $type ) { 
     3333        $settings['codemirror'] = array_merge( $settings['codemirror'], array( 
     3334            'mode' => 'gfm', 
     3335            'highlightFormatting' => true, 
     3336        ) ); 
     3337    } elseif ( 'application/javascript' === $type || 'text/javascript' === $type ) { 
     3338        $settings['codemirror'] = array_merge( $settings['codemirror'], array( 
     3339            'mode' => 'javascript', 
     3340            'lint' => true, 
     3341            'autoCloseBrackets' => true, 
     3342            'matchBrackets' => true, 
     3343        ) ); 
     3344    } elseif ( false !== strpos( $type, 'json' ) ) { 
     3345        $settings['codemirror'] = array_merge( $settings['codemirror'], array( 
     3346            'mode' => array( 
     3347                'name' => 'javascript', 
     3348            ), 
     3349            'lint' => true, 
     3350            'autoCloseBrackets' => true, 
     3351            'matchBrackets' => true, 
     3352        ) ); 
     3353        if ( 'application/ld+json' === $type ) { 
     3354            $settings['codemirror']['mode']['jsonld'] = true; 
     3355        } else { 
     3356            $settings['codemirror']['mode']['json'] = true; 
     3357        } 
     3358    } elseif ( false !== strpos( $type, 'jsx' ) ) { 
     3359        $settings['codemirror'] = array_merge( $settings['codemirror'], array( 
     3360            'mode' => 'jsx', 
     3361            'autoCloseBrackets' => true, 
     3362            'matchBrackets' => true, 
     3363        ) ); 
     3364    } elseif ( 'text/x-markdown' === $type ) { 
     3365        $settings['codemirror'] = array_merge( $settings['codemirror'], array( 
     3366            'mode' => 'markdown', 
     3367            'highlightFormatting' => true, 
     3368        ) ); 
     3369    } elseif ( 'text/nginx' === $type ) { 
     3370        $settings['codemirror'] = array_merge( $settings['codemirror'], array( 
     3371            'mode' => 'nginx', 
     3372        ) ); 
     3373    } elseif ( 'application/x-httpd-php' === $type ) { 
     3374        $settings['codemirror'] = array_merge( $settings['codemirror'], array( 
     3375            'mode' => 'php', 
     3376            'autoCloseBrackets' => true, 
     3377            'autoCloseTags' => true, 
     3378            'matchBrackets' => true, 
     3379            'matchTags' => array( 
     3380                'bothTags' => true, 
     3381            ), 
     3382        ) ); 
     3383    } elseif ( 'text/x-sql' === $type || 'text/x-mysql' === $type ) { 
     3384        $settings['codemirror'] = array_merge( $settings['codemirror'], array( 
     3385            'mode' => 'sql', 
     3386            'autoCloseBrackets' => true, 
     3387            'matchBrackets' => true, 
     3388        ) ); 
     3389    } elseif ( false !== strpos( $type, 'xml' ) ) { 
     3390        $settings['codemirror'] = array_merge( $settings['codemirror'], array( 
     3391            'mode' => 'xml', 
     3392            'autoCloseBrackets' => true, 
     3393            'autoCloseTags' => true, 
     3394            'matchTags' => array( 
     3395                'bothTags' => true, 
     3396            ), 
     3397        ) ); 
     3398    } elseif ( 'text/x-yaml' === $type ) { 
     3399        $settings['codemirror'] = array_merge( $settings['codemirror'], array( 
     3400            'mode' => 'yaml', 
     3401        ) ); 
     3402    } else { 
     3403        $settings['codemirror']['mode'] = $type; 
     3404    } 
     3405 
     3406    if ( ! empty( $settings['codemirror']['lint'] ) ) { 
     3407        $settings['codemirror']['gutters'][] = 'CodeMirror-lint-markers'; 
     3408    } 
     3409 
     3410    // Let settings supplied via args override any defaults. 
     3411    if ( isset( $args['settings'] ) ) { 
     3412        foreach ( $args['settings'] as $key => $value ) { 
     3413            $settings[ $key ] = array_merge( 
     3414                $settings[ $key ], 
     3415                $value 
     3416            ); 
     3417        } 
     3418    } 
     3419 
     3420    /** 
     3421     * Filters settings that are passed into the code editor. 
     3422     * 
     3423     * Returning a falsey value will disable the syntax-highlighting code editor. 
     3424     * 
     3425     * @since 4.9.0 
     3426     * 
     3427     * @param array $settings The array of settings passed to the code editor. A falsey value disables the editor. 
     3428     * @param array $args { 
     3429     *     Args passed when calling `wp_enqueue_code_editor()`. 
     3430     * 
     3431     *     @type string   $type     The MIME type of the file to be edited. 
     3432     *     @type string   $file     Filename being edited. 
     3433     *     @type array    $settings Settings to merge on top of defaults which derive from `$type` or `$file` args. 
     3434     *     @type WP_Theme $theme    Theme being edited when on theme editor. 
     3435     *     @type string   $plugin   Plugin being edited when on plugin editor. 
     3436     * } 
     3437     */ 
     3438    $settings = apply_filters( 'wp_code_editor_settings', $settings, $args ); 
     3439 
     3440    if ( empty( $settings ) || empty( $settings['codemirror'] ) ) { 
     3441        return false; 
     3442    } 
     3443 
     3444    wp_enqueue_script( 'code-editor' ); 
     3445    wp_enqueue_style( 'code-editor' ); 
     3446 
     3447    wp_enqueue_script( 'codemirror' ); 
     3448    wp_enqueue_style( 'codemirror' ); 
     3449 
     3450    if ( isset( $settings['codemirror']['mode'] ) ) { 
     3451        $mode = $settings['codemirror']['mode']; 
     3452        if ( is_string( $mode ) ) { 
     3453            $mode = array( 
     3454                'name' => $mode, 
     3455            ); 
     3456        } 
     3457 
     3458        if ( ! empty( $settings['codemirror']['lint'] ) ) { 
     3459            switch ( $mode['name'] ) { 
     3460                case 'css': 
     3461                case 'text/css': 
     3462                case 'text/x-scss': 
     3463                case 'text/x-less': 
     3464                    wp_enqueue_script( 'csslint' ); 
     3465                    break; 
     3466                case 'htmlmixed': 
     3467                case 'text/html': 
     3468                case 'php': 
     3469                case 'application/x-httpd-php': 
     3470                case 'text/x-php': 
     3471                    wp_enqueue_script( 'htmlhint' ); 
     3472                    wp_enqueue_script( 'csslint' ); 
     3473                    wp_enqueue_script( 'jshint' ); 
     3474                    if ( ! current_user_can( 'unfiltered_html' ) ) { 
     3475                        wp_enqueue_script( 'htmlhint-kses' ); 
     3476                    } 
     3477                    break; 
     3478                case 'javascript': 
     3479                case 'application/ecmascript': 
     3480                case 'application/json': 
     3481                case 'application/javascript': 
     3482                case 'application/ld+json': 
     3483                case 'text/typescript': 
     3484                case 'application/typescript': 
     3485                    wp_enqueue_script( 'jshint' ); 
     3486                    wp_enqueue_script( 'jsonlint' ); 
     3487                    break; 
     3488            } 
     3489        } 
     3490    } 
     3491 
     3492    wp_add_inline_script( 'code-editor', sprintf( 'jQuery.extend( wp.codeEditor.defaultSettings, %s );', wp_json_encode( $settings ) ) ); 
     3493 
     3494    /** 
     3495     * Fires when scripts and styles are enqueued for the code editor. 
     3496     * 
     3497     * @since 4.9.0 
     3498     * 
     3499     * @param array $settings Settings for the enqueued code editor. 
     3500     */ 
     3501    do_action( 'wp_enqueue_code_editor', $settings ); 
     3502 
     3503    return $settings; 
     3504} 
     3505 
     3506/** 
    31193507 * Retrieves the contents of the search WordPress query variable. 
    31203508 * 
  • trunk/src/wp-includes/script-loader.php

    r41375 r41376  
    467467    ); 
    468468 
     469    $scripts->add( 'codemirror', '/wp-includes/js/codemirror/codemirror.min.js', array(), '5.29.1-alpha-ee20357' ); 
     470    $scripts->add( 'csslint', '/wp-includes/js/codemirror/csslint.js', array(), '1.0.5' ); 
     471    $scripts->add( 'jshint', '/wp-includes/js/codemirror/jshint.js', array(), '2.9.5' ); 
     472    $scripts->add( 'jsonlint', '/wp-includes/js/codemirror/jsonlint.js', array(), '1.6.2' ); 
     473    $scripts->add( 'htmlhint', '/wp-includes/js/codemirror/htmlhint.js', array(), '0.9.14-xwp' ); 
     474    $scripts->add( 'htmlhint-kses', '/wp-includes/js/codemirror/htmlhint-kses.js', array( 'htmlhint' ) ); 
     475    $scripts->add( 'code-editor', "/wp-admin/js/code-editor$suffix.js", array( 'jquery', 'codemirror' ) ); 
     476    $scripts->add( 'wp-theme-plugin-editor', "/wp-admin/js/theme-plugin-editor$suffix.js", array( 'code-editor', 'jquery', 'jquery-ui-core', 'wp-a11y', 'underscore' ) ); 
     477    did_action( 'init' ) && $scripts->add_inline_script( 'wp-theme-plugin-editor', sprintf( 'wp.themePluginEditor.l10n = %s;', wp_json_encode( wp_array_slice_assoc( 
     478        /* translators: placeholder is error count */ 
     479        _n_noop( 'There is %d error which must be fixed before you can save.', 'There are %d errors which must be fixed before you can save.' ), 
     480        array( 'singular', 'plural' ) 
     481    ) ) ) ); 
     482 
    469483    $scripts->add( 'wp-playlist', "/wp-includes/js/mediaelement/wp-playlist$suffix.js", array( 'wp-util', 'backbone', 'mediaelement' ), false, 1 ); 
    470484 
     
    552566        // Used for overriding the file types allowed in plupload. 
    553567        'allowedFiles'       => __( 'Allowed Files' ), 
     568        'customCssError'     => wp_array_slice_assoc( 
     569            /* translators: placeholder is error count */ 
     570            _n_noop( 'There is %d error which must be fixed before you can save.', 'There are %d errors which must be fixed before you can save.' ), 
     571            array( 'singular', 'plural' ) 
     572        ), 
    554573    ) ); 
    555574    $scripts->add( 'customize-selective-refresh', "/wp-includes/js/customize-selective-refresh$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 ); 
     
    689708        $scripts->add( 'media-video-widget', "/wp-admin/js/widgets/media-video-widget$suffix.js", array( 'media-widgets', 'media-audiovideo', 'wp-api-request' ) ); 
    690709        $scripts->add( 'text-widgets', "/wp-admin/js/widgets/text-widgets$suffix.js", array( 'jquery', 'backbone', 'editor', 'wp-util', 'wp-a11y' ) ); 
    691         $scripts->add_inline_script( 'text-widgets', 'wp.textWidgets.init();', 'after' ); 
     710        $scripts->add( 'custom-html-widgets', "/wp-admin/js/widgets/custom-html-widgets$suffix.js", array( 'code-editor', 'jquery', 'backbone', 'wp-util', 'jquery-ui-core', 'wp-a11y' ) ); 
    692711 
    693712        $scripts->add( 'theme', "/wp-admin/js/theme$suffix.js", array( 'wp-backbone', 'wp-a11y' ), false, 1 ); 
     
    930949    $styles->add( 'site-icon',           "/wp-admin/css/site-icon$suffix.css" ); 
    931950    $styles->add( 'l10n',                "/wp-admin/css/l10n$suffix.css" ); 
     951    $styles->add( 'code-editor',         "/wp-admin/css/code-editor$suffix.css", array( 'codemirror' ) ); 
    932952 
    933953    $styles->add( 'wp-admin', false, array( 'dashicons', 'common', 'forms', 'admin-menu', 'dashboard', 'list-tables', 'edit', 'revisions', 'media', 'themes', 'about', 'nav-menus', 'widgets', 'site-icon', 'l10n' ) ); 
     
    964984    $styles->add( 'wp-mediaelement',     "/wp-includes/js/mediaelement/wp-mediaelement$suffix.css", array( 'mediaelement' ) ); 
    965985    $styles->add( 'thickbox',            '/wp-includes/js/thickbox/thickbox.css', array( 'dashicons' ) ); 
     986    $styles->add( 'codemirror',          '/wp-includes/js/codemirror/codemirror.min.css', array(), '5.29.1-alpha-ee20357' ); 
    966987 
    967988    // Deprecated CSS 
  • trunk/src/wp-includes/user.php

    r41289 r41376  
    13771377 * 
    13781378 * Most of the `$userdata` array fields have filters associated with the values. Exceptions are 
    1379  * 'ID', 'rich_editing', 'comment_shortcuts', 'admin_color', 'use_ssl', 
     1379 * 'ID', 'rich_editing', 'syntax_highlighting', 'comment_shortcuts', 'admin_color', 'use_ssl', 
    13801380 * 'user_registered', and 'role'. The filters have the prefix 'pre_user_' followed by the field 
    13811381 * name. An example using 'description' would have the filter called, 'pre_user_description' that 
     
    14111411 *     @type string|bool $rich_editing         Whether to enable the rich-editor for the user. 
    14121412 *                                             False if not empty. 
     1413 *     @type string|bool $syntax_highlighting  Whether to enable the rich code editor for the user. 
     1414 *                                             False if not empty. 
    14131415 *     @type string|bool $comment_shortcuts    Whether to enable comment moderation keyboard 
    14141416 *                                             shortcuts for the user. Default false. 
     
    16231625 
    16241626    $meta['rich_editing'] = empty( $userdata['rich_editing'] ) ? 'true' : $userdata['rich_editing']; 
     1627 
     1628    $meta['syntax_highlighting'] = empty( $userdata['syntax_highlighting'] ) ? 'true' : $userdata['syntax_highlighting']; 
    16251629 
    16261630    $meta['comment_shortcuts'] = empty( $userdata['comment_shortcuts'] ) || 'false' === $userdata['comment_shortcuts'] ? 'false' : 'true'; 
     
    17101714     *     @type string   $description          The user's description. 
    17111715     *     @type bool     $rich_editing         Whether to enable the rich-editor for the user. False if not empty. 
     1716     *     @type bool     $syntax_highlighting  Whether to enable the rich code editor for the user. False if not empty. 
    17121717     *     @type bool     $comment_shortcuts    Whether to enable keyboard shortcuts for the user. Default false. 
    17131718     *     @type string   $admin_color          The color scheme for a user's admin screen. Default 'fresh'. 
     
    20362041 */ 
    20372042function _get_additional_user_keys( $user ) { 
    2038     $keys = array( 'first_name', 'last_name', 'nickname', 'description', 'rich_editing', 'comment_shortcuts', 'admin_color', 'use_ssl', 'show_admin_bar_front', 'locale' ); 
     2043    $keys = array( 'first_name', 'last_name', 'nickname', 'description', 'rich_editing', 'syntax_highlighting', 'comment_shortcuts', 'admin_color', 'use_ssl', 'show_admin_bar_front', 'locale' ); 
    20392044    return array_merge( $keys, array_keys( wp_get_user_contact_methods( $user ) ) ); 
    20402045} 
  • trunk/src/wp-includes/widgets/class-wp-widget-custom-html.php

    r41132 r41376  
    1818 
    1919    /** 
     20     * Whether or not the widget has been registered yet. 
     21     * 
     22     * @since 4.9.0 
     23     * @var bool 
     24     */ 
     25    protected $registered = false; 
     26 
     27    /** 
    2028     * Default instance. 
    2129     * 
     
    4452        ); 
    4553        parent::__construct( 'custom_html', __( 'Custom HTML' ), $widget_ops, $control_ops ); 
     54    } 
     55 
     56    /** 
     57     * Add hooks for enqueueing assets when registering all widget instances of this widget class. 
     58     * 
     59     * @since 4.9.0 
     60     * 
     61     * @param integer $number Optional. The unique order number of this widget instance 
     62     *                        compared to other instances of the same class. Default -1. 
     63     */ 
     64    public function _register_one( $number = -1 ) { 
     65        parent::_register_one( $number ); 
     66        if ( $this->registered ) { 
     67            return; 
     68        } 
     69        $this->registered = true; 
     70 
     71        wp_add_inline_script( 'custom-html-widgets', sprintf( 'wp.customHtmlWidgets.idBases.push( %s );', wp_json_encode( $this->id_base ) ) ); 
     72 
     73        // Note that the widgets component in the customizer will also do the 'admin_print_scripts-widgets.php' action in WP_Customize_Widgets::print_scripts(). 
     74        add_action( 'admin_print_scripts-widgets.php', array( $this, 'enqueue_admin_scripts' ) ); 
     75 
     76        // Note that the widgets component in the customizer will also do the 'admin_footer-widgets.php' action in WP_Customize_Widgets::print_footer_scripts(). 
     77        add_action( 'admin_footer-widgets.php', array( 'WP_Widget_Custom_HTML', 'render_control_template_scripts' ) ); 
     78 
     79        // Note this action is used to ensure the help text is added to the end. 
     80        add_action( 'admin_head-widgets.php', array( 'WP_Widget_Custom_HTML', 'add_help_text' ) ); 
    4681    } 
    4782 
     
    119154 
    120155    /** 
     156     * Loads the required scripts and styles for the widget control. 
     157     * 
     158     * @since 4.9.0 
     159     */ 
     160    public function enqueue_admin_scripts() { 
     161        $settings = wp_enqueue_code_editor( array( 
     162            'type' => 'text/html', 
     163        ) ); 
     164 
     165        wp_enqueue_script( 'custom-html-widgets' ); 
     166        if ( empty( $settings ) ) { 
     167            $settings = array( 
     168                'disabled' => true, 
     169            ); 
     170        } 
     171        wp_add_inline_script( 'custom-html-widgets', sprintf( 'wp.customHtmlWidgets.init( %s );', wp_json_encode( $settings ) ), 'after' ); 
     172 
     173        $l10n = array( 
     174            'errorNotice' => wp_array_slice_assoc( 
     175                /* translators: placeholder is error count */ 
     176                _n_noop( 'There is %d error which must be fixed before you can save.', 'There are %d errors which must be fixed before you can save.' ), 
     177                array( 'singular', 'plural' ) 
     178            ), 
     179        ); 
     180        wp_add_inline_script( 'custom-html-widgets', sprintf( 'jQuery.extend( wp.customHtmlWidgets.l10n, %s );', wp_json_encode( $l10n ) ), 'after' ); 
     181    } 
     182 
     183    /** 
    121184     * Outputs the Custom HTML widget settings form. 
    122185     * 
    123186     * @since 4.8.1 
    124      * 
     187     * @since 4.9.0 The form contains only hidden sync inputs. For the control UI, see `WP_Widget_Custom_HTML::render_control_template_scripts()`. 
     188     * 
     189     * @see WP_Widget_Custom_HTML::render_control_template_scripts() 
    125190     * @param array $instance Current instance. 
    126191     * @returns void 
     
    129194        $instance = wp_parse_args( (array) $instance, $this->default_instance ); 
    130195        ?> 
    131         <p> 
    132             <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label> 
    133             <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $instance['title'] ); ?>"/> 
    134         </p> 
    135  
    136         <p> 
    137             <label for="<?php echo $this->get_field_id( 'content' ); ?>"><?php _e( 'Content:' ); ?></label> 
    138             <textarea class="widefat code" rows="16" cols="20" id="<?php echo $this->get_field_id( 'content' ); ?>" name="<?php echo $this->get_field_name( 'content' ); ?>"><?php echo esc_textarea( $instance['content'] ); ?></textarea> 
    139         </p> 
    140  
    141         <?php if ( ! current_user_can( 'unfiltered_html' ) ) : ?> 
    142             <?php 
    143             $probably_unsafe_html = array( 'script', 'iframe', 'form', 'input', 'style' ); 
    144             $allowed_html = wp_kses_allowed_html( 'post' ); 
    145             $disallowed_html = array_diff( $probably_unsafe_html, array_keys( $allowed_html ) ); 
    146             ?> 
    147             <?php if ( ! empty( $disallowed_html ) ) : ?> 
    148                 <p> 
    149                     <?php _e( 'Some HTML tags are not permitted, including:' ); ?> 
    150                     <code><?php echo join( '</code>, <code>', $disallowed_html ); ?></code> 
    151                 </p> 
     196        <input id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" class="title sync-input" type="hidden" value="<?php echo esc_attr( $instance['title'] ); ?>"/> 
     197        <textarea id="<?php echo $this->get_field_id( 'content' ); ?>" name="<?php echo $this->get_field_name( 'content' ); ?>" class="content sync-input" hidden><?php echo esc_textarea( $instance['content'] ); ?></textarea> 
     198        <?php 
     199    } 
     200 
     201    /** 
     202     * Render form template scripts. 
     203     * 
     204     * @since 4.9.0 
     205     */ 
     206    public static function render_control_template_scripts() { 
     207        ?> 
     208        <script type="text/html" id="tmpl-widget-custom-html-control-fields"> 
     209            <# var elementIdPrefix = 'el' + String( Math.random() ).replace( /\D/g, '' ) + '_' #> 
     210            <p> 
     211                <label for="{{ elementIdPrefix }}title"><?php esc_html_e( 'Title:', 'default' ); ?></label> 
     212                <input id="{{ elementIdPrefix }}title" type="text" class="widefat title"> 
     213            </p> 
     214 
     215            <p> 
     216                <label for="{{ elementIdPrefix }}content" class="screen-reader-text"><?php esc_html_e( 'Content:', 'default' ); ?></label> 
     217                <textarea id="{{ elementIdPrefix }}content" class="widefat code content" rows="16" cols="20"></textarea> 
     218            </p> 
     219 
     220            <?php if ( ! current_user_can( 'unfiltered_html' ) ) : ?> 
     221                <?php 
     222                $probably_unsafe_html = array( 'script', 'iframe', 'form', 'input', 'style' ); 
     223                $allowed_html = wp_kses_allowed_html( 'post' ); 
     224                $disallowed_html = array_diff( $probably_unsafe_html, array_keys( $allowed_html ) ); 
     225                ?> 
     226                <?php if ( ! empty( $disallowed_html ) ) : ?> 
     227                    <# if ( data.codeEditorDisabled ) { #> 
     228                        <p> 
     229                            <?php _e( 'Some HTML tags are not permitted, including:', 'default' ); ?> 
     230                            <code><?php echo join( '</code>, <code>', $disallowed_html ); ?></code> 
     231                        </p> 
     232                    <# } #> 
     233                <?php endif; ?> 
    152234            <?php endif; ?> 
    153         <?php endif; ?> 
     235 
     236            <div class="code-editor-error-container"></div> 
     237        </script> 
    154238        <?php 
    155239    } 
     240 
     241    /** 
     242     * Add help text to widgets admin screen. 
     243     * 
     244     * @since 4.9.0 
     245     */ 
     246    public static function add_help_text() { 
     247        $screen = get_current_screen(); 
     248 
     249        $content = '<p>'; 
     250        $content .= __( 'Use the Custom HTML widget to add arbitrary HTML code to your widget areas.' ); 
     251        $content .= '</p>'; 
     252 
     253        $content .= '<p>' . __( 'When using a keyboard to navigate:' ) . '</p>'; 
     254        $content .= '<ul>'; 
     255        $content .= '<li>' . __( 'In the HTML edit field, Tab enters a tab character.' ) . '</li>'; 
     256        $content .= '<li>' . __( 'To move keyboard focus, press Esc then Tab for the next element, or Esc then Shift+Tab for the previous element.' ) . '</li>'; 
     257        $content .= '</ul>'; 
     258 
     259        $content .= '<p>'; 
     260        $content .= sprintf( 
     261            /* translators: placeholder is link to user profile */ 
     262            __( 'The edit field automatically highlights code syntax. You can disable this in your %s to work in plan text mode.' ), 
     263            sprintf( 
     264                ' <a href="%1$s" class="external-link" target="_blank">%2$s<span class="screen-reader-text">%3$s</span></a>', 
     265                esc_url( get_edit_profile_url() . '#syntax_highlighting' ), 
     266                __( 'user profile' ), 
     267                /* translators: accessibility text */ 
     268                __( '(opens in a new window)', 'default' ) 
     269            ) 
     270        ); 
     271        $content .= '</p>'; 
     272 
     273        $screen->add_help_tab( array( 
     274            'id' => 'custom_html_widget', 
     275            'title' => __( 'Custom HTML Widget' ), 
     276            'content' => $content, 
     277        ) ); 
     278    } 
    156279} 
  • trunk/src/wp-includes/widgets/class-wp-widget-text.php

    r41361 r41376  
    354354        wp_enqueue_editor(); 
    355355        wp_enqueue_script( 'text-widgets' ); 
     356        wp_add_inline_script( 'text-widgets', 'wp.textWidgets.init();', 'after' ); 
    356357    } 
    357358 
  • trunk/tests/phpunit/tests/customize/custom-css-setting.php

    r39919 r41376  
    356356        $this->assertTrue( $result ); 
    357357 
    358         // Check for Unclosed Comment. 
    359         $unclosed_comment = $basic_css . ' /* This is a comment. '; 
     358        // Check for markup. 
     359        $unclosed_comment = $basic_css . '</style>'; 
    360360        $result = $this->setting->validate( $unclosed_comment ); 
    361         $this->assertTrue( array_key_exists( 'unclosed_comment', $result->errors ) ); 
    362  
    363         // Check for Unopened Comment. 
    364         $unclosed_comment = $basic_css . ' This is a comment.*/'; 
    365         $result = $this->setting->validate( $unclosed_comment ); 
    366         $this->assertTrue( array_key_exists( 'imbalanced_comments', $result->errors ) ); 
    367  
    368         // Check for Unclosed Curly Brackets. 
    369         $unclosed_curly_bracket = $basic_css . '  a.link { text-decoration: none;'; 
    370         $result = $this->setting->validate( $unclosed_curly_bracket ); 
    371         $this->assertTrue( array_key_exists( 'imbalanced_curly_brackets', $result->errors ) ); 
    372  
    373         // Check for Unopened Curly Brackets. 
    374         $unopened_curly_bracket = $basic_css . '  a.link text-decoration: none; }'; 
    375         $result = $this->setting->validate( $unopened_curly_bracket ); 
    376         $this->assertTrue( array_key_exists( 'imbalanced_curly_brackets', $result->errors ) ); 
    377  
    378         // Check for Unclosed Braces. 
    379         $unclosed_brace = $basic_css . '  input[type="text" { color: #f00; } '; 
    380         $result = $this->setting->validate( $unclosed_brace ); 
    381         $this->assertTrue( array_key_exists( 'imbalanced_braces', $result->errors ) ); 
    382  
    383         // Check for Unopened Braces. 
    384         $unopened_brace = $basic_css . ' inputtype="text"] { color: #f00; } '; 
    385         $result = $this->setting->validate( $unopened_brace ); 
    386         $this->assertTrue( array_key_exists( 'imbalanced_braces', $result->errors ) ); 
    387  
    388         // Check for Imbalanced Double Quotes. 
    389         $imbalanced_double_quotes = $basic_css . ' div.background-image { background-image: url( "image.jpg ); } '; 
    390         $result = $this->setting->validate( $imbalanced_double_quotes ); 
    391         $this->assertTrue( array_key_exists( 'unequal_double_quotes', $result->errors ) ); 
    392  
    393         // Check for Unclosed Parentheses. 
    394         $unclosed_parentheses = $basic_css . ' div.background-image { background-image: url( "image.jpg" ; } '; 
    395         $result = $this->setting->validate( $unclosed_parentheses ); 
    396         $this->assertTrue( array_key_exists( 'imbalanced_parentheses', $result->errors ) ); 
    397  
    398         // Check for Unopened Parentheses. 
    399         $unopened_parentheses = $basic_css . ' div.background-image { background-image: url "image.jpg" ); } '; 
    400         $result = $this->setting->validate( $unopened_parentheses ); 
    401         $this->assertTrue( array_key_exists( 'imbalanced_parentheses', $result->errors ) ); 
    402  
    403         // A basic Content declaration with no other errors should not throw an error. 
    404         $content_declaration = $basic_css . ' a:before { content: ""; display: block; }'; 
    405         $result = $this->setting->validate( $content_declaration ); 
    406         $this->assertTrue( $result ); 
    407  
    408         // An error, along with a Content declaration will throw two errors. 
    409         // In this case, we're using an extra opening brace. 
    410         $content_declaration = $basic_css . ' a:before { content: "["; display: block; }'; 
    411         $result = $this->setting->validate( $content_declaration ); 
    412         $this->assertTrue( array_key_exists( 'imbalanced_braces', $result->errors ) ); 
    413         $this->assertTrue( array_key_exists( 'possible_false_positive', $result->errors ) ); 
    414  
    415         $css = 'body { background: #f00; } h1.site-title { font-size: 36px; } a:hover { text-decoration: none; } input[type="text"] { padding: 1em; } /* This is a comment */'; 
    416         $this->assertTrue( $this->setting->validate( $css ) ); 
    417  
    418         $validity = $this->setting->validate( $css . ' /* This is another comment.' ); 
    419         $this->assertInstanceOf( 'WP_Error', $validity ); 
    420         $this->assertContains( 'unclosed code comment', join( ' ', $validity->get_error_messages() ) ); 
    421  
    422         $css = '/* This is comment one. */  /* This is comment two. */'; 
    423         $this->assertTrue( $this->setting->validate( $css ) ); 
    424  
    425         $basic_css = 'body { background: #f00; } h1.site-title { font-size: 36px; } a:hover { text-decoration: none; } input[type="text"] { padding: 1em; }'; 
    426         $this->assertTrue( $this->setting->validate( $basic_css ) ); 
    427  
    428         $css = $basic_css . ' .link:before { content: "*"; display: block; }'; 
    429         $this->assertTrue( $this->setting->validate( $css ) ); 
    430  
    431         $css .= ' ( trailing'; 
    432         $validity = $this->setting->validate( $css ); 
    433         $this->assertWPError( $validity ); 
    434         $this->assertNotEmpty( $result->get_error_message( 'possible_false_positive' ) ); 
     361        $this->assertTrue( array_key_exists( 'illegal_markup', $result->errors ) ); 
    435362    } 
    436363} 
  • trunk/tests/phpunit/tests/customize/manager.php

    r41372 r41376  
    23522352        $this->assertNotEmpty( $data ); 
    23532353 
    2354         $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'changeset', 'timeouts' ), array_keys( $data ) ); 
     2354        $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'customCss', 'changeset', 'timeouts' ), array_keys( $data ) ); 
    23552355        $this->assertEquals( $autofocus, $data['autofocus'] ); 
    23562356        $this->assertArrayHasKey( 'save', $data['nonce'] ); 
  • trunk/tests/phpunit/tests/user.php

    r41255 r41376  
    359359        $user_data = array( 
    360360            'ID' => self::$author_id, 'use_ssl' => 1, 'show_admin_bar_front' => 1, 
    361             'rich_editing' => 1, 'first_name' => 'first', 'last_name' => 'last', 
     361            'rich_editing' => 1, 'syntax_highlighting' => 1, 'first_name' => 'first', 'last_name' => 'last', 
    362362            'nickname' => 'nick', 'comment_shortcuts' => 'true', 'admin_color' => 'classic', 
    363363            'description' => 'describe' 
Note: See TracChangeset for help on using the changeset viewer.