hook = (string) add_submenu_page( 'themes.php', __( 'Custom CSS', 'adminkeep' ), __( 'Custom CSS', 'adminkeep' ), 'edit_theme_options', self::PAGE, array( $this, 'render' ) ); } /** * Load WordPress's own code editor and the save script on this screen only. * * When the user has switched syntax highlighting off in their profile, * wp_enqueue_code_editor() returns false and the plain textarea is the editor. The save * script loads either way: it reads whichever of the two is there. * * @param string $hook_suffix Current admin page. * @return void */ public function enqueue_editor( $hook_suffix ) { if ( '' === $this->hook || $hook_suffix !== $this->hook ) { return; } // The layout rules ride on a handle of their own, so they load even when syntax // highlighting is off and the code editor's stylesheet is not. wp_register_style( 'adminkeep-custom-css-screen', false, array(), ADMINKEEP_VERSION ); wp_enqueue_style( 'adminkeep-custom-css-screen' ); wp_add_inline_style( 'adminkeep-custom-css-screen', '.adminkeep-css-layout{display:flex;align-items:flex-start;gap:20px;}' . '.adminkeep-css-main{flex:1 1 auto;min-width:0;}' . '.adminkeep-css-side{flex:0 0 260px;box-sizing:border-box;padding:4px 16px 12px;background:#fff;border:1px solid #c3c4c7;}' . '.adminkeep-css-side h2{font-size:14px;margin:1em 0 .5em;}' . '@media (max-width:1100px){.adminkeep-css-layout{flex-direction:column;}.adminkeep-css-main,.adminkeep-css-side{flex:none;width:100%;}}' ); CodeScreenSave::enqueue( array( 'route' => esc_url_raw( rest_url( RestController::NAMESPACE_V1 . '/custom-css' ) ), 'form' => self::FORM_ID, 'fields' => array( 'css' => self::EDITOR_ID ), 'i18n' => array( 'saved' => __( 'CSS saved.', 'adminkeep' ) ), ) ); $settings = wp_enqueue_code_editor( array( 'type' => 'text/css' ) ); if ( false === $settings ) { return; } wp_add_inline_script( 'code-editor', sprintf( 'jQuery( function() { wp.codeEditor.initialize( %s, %s ); } );', wp_json_encode( self::EDITOR_ID ), wp_json_encode( $settings ) ) ); wp_add_inline_style( 'code-editor', '.adminkeep-css-screen .CodeMirror{height:60vh;border:1px solid #c3c4c7;}' ); } /** * Save a submission. * * @return void */ public function handle_save() { if ( empty( $_POST[ self::SAVE_FIELD ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Checked below, once this is known to be our form. return; } if ( ! self::can_edit() ) { wp_die( esc_html__( 'You are not allowed to edit CSS on this site.', 'adminkeep' ), '', 403 ); } check_admin_referer( self::NONCE ); // Not sanitized as text: CSS legitimately holds >, quotes and backslashes. It is validated // for markup instead, and tags are stripped again on output. // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $css = isset( $_POST[ self::CSS_FIELD ] ) && is_string( $_POST[ self::CSS_FIELD ] ) ? wp_unslash( $_POST[ self::CSS_FIELD ] ) : ''; $saved = CustomCss::save( $css ); if ( is_wp_error( $saved ) ) { $this->refused = array( 'css' => str_replace( "\r\n", "\n", $css ), 'message' => $saved->get_error_message(), ); return; } wp_safe_redirect( add_query_arg( 'updated', '1', self::url() ) ); exit; } /** * Render the screen. * * @return void */ public function render() { if ( ! self::can_edit() ) { return; } $css = null === $this->refused ? CustomCss::css() : $this->refused['css']; echo '
'; echo '

' . Brand::heading_mark( 'custom_css' ) . esc_html__( 'Custom CSS', 'adminkeep' ) . '

'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The mark is escaped where it is built. if ( null !== $this->refused ) { CodeScreenSave::print_notices( 'error', $this->refused['message'] ); } elseif ( isset( $_GET['updated'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only flag choosing a static notice. CodeScreenSave::print_notices( 'success', __( 'CSS saved.', 'adminkeep' ) ); } else { CodeScreenSave::print_notices(); } printf( '

%s

', esc_html__( 'This CSS applies to the front end of your site on every theme. Your theme\'s own Additional CSS is separate and is not changed here.', 'adminkeep' ) ); // Beside the editor rather than above it: above, it read as the way to use this screen. echo '
'; echo '
'; wp_nonce_field( self::NONCE ); printf( '', esc_attr( self::SAVE_FIELD ) ); // The newline after the opening tag is for the browser, which drops one there; without it, // CSS starting with a newline would lose it on every save. printf( '', esc_attr( self::EDITOR_ID ), esc_html__( 'Custom CSS', 'adminkeep' ), esc_attr( self::CSS_FIELD ), esc_textarea( $css ) ); submit_button( __( 'Save CSS', 'adminkeep' ) ); echo '
'; echo '
'; } }