array( 'type' => 'bool', 'default' => true, ), ); } /** * Field labels. * * @param string $key Field key. * @return string */ public function field_label( $key ) { switch ( $key ) { case 'admin_bar': return __( 'Show Edit CSS in the admin bar', 'adminkeep' ); default: return ''; } } /** * Settings with their defaults filled in. * * @param array $settings Stored settings. * @return array{admin_bar: bool} */ public static function resolve( array $settings ) { return array( 'admin_bar' => ! isset( $settings['admin_bar'] ) || (bool) $settings['admin_bar'], ); } /** * What switching this on actually does. * * @return string[] */ public function how_it_works() { return array( __( 'Adds Appearance → Custom CSS, with the same code editor WordPress uses for its own CSS box.', 'adminkeep' ), __( 'Adds Edit CSS to the admin bar on your site: a code panel beside the page that restyles it as you type. You can hide the admin bar item and open the panel from Appearance → Custom CSS instead.', 'adminkeep' ), __( 'Prints your CSS on the front end of the site, on whichever theme is active. Switching themes does not lose it.', 'adminkeep' ), __( 'Your theme\'s own Additional CSS is left alone. Both can be used; this one prints second, so it wins when the two disagree.', 'adminkeep' ), __( 'Anything that looks like HTML is refused when you save, so a pasted snippet cannot break out of the style tag.', 'adminkeep' ), __( 'Switching this off stops the CSS printing and removes the menu item. The CSS itself is kept unless you delete the plugin with "Delete all Adminkeep data" switched on.', 'adminkeep' ), ); } /** * Where the settings panel sends you to do the actual work. * * @return array{url?: string, label?: string} Empty when this user could not open the screen. */ public function panel_link() { if ( ! CustomCssScreen::can_edit() ) { return array(); } return array( 'url' => CustomCssScreen::url(), 'label' => __( 'Edit CSS', 'adminkeep' ), ); } /** * The editing screen. * * @return CustomCssScreen */ public function screen() { if ( null === $this->screen ) { $this->screen = new CustomCssScreen(); } return $this->screen; } /** * The front-end editing panel. * * @return LiveCssPanel */ public function panel() { if ( null === $this->panel ) { $this->panel = new LiveCssPanel(); } return $this->panel; } /** * Register hooks. * * @param array $settings Stored settings. * @return void */ public function boot( array $settings ) { $resolved = self::resolve( $settings ); add_action( 'wp_head', array( $this, 'print_css' ), self::HEAD_PRIORITY ); add_action( 'rest_api_init', array( $this, 'register_rest_route' ) ); if ( is_admin() ) { $this->screen()->boot(); } else { $this->panel()->boot( $resolved['admin_bar'] ); } } /** * The stored CSS. * * @return string */ public static function css() { $css = get_option( self::OPTION, '' ); return is_string( $css ) ? $css : ''; } /** * Check CSS the way core checks its own Additional CSS: no markup. * * @param string $css Raw CSS. * @return true|\WP_Error */ public static function validate( $css ) { if ( preg_match( '# \WP_REST_Server::READABLE, 'callback' => array( $this, 'rest_get' ), 'permission_callback' => array( $this, 'rest_can_edit' ), ), array( 'methods' => \WP_REST_Server::EDITABLE, 'callback' => array( $this, 'rest_save' ), 'permission_callback' => array( $this, 'rest_can_edit' ), 'args' => array( 'css' => array( 'type' => 'string', 'required' => true, // Named explicitly: in a hand-written args array the type alone is not // enforced, and an array or number would otherwise reach the callback. 'validate_callback' => 'rest_validate_request_arg', ), ), ), ) ); } /** * The screen's gate: edit_theme_options and edit_css together. * * @return bool|\WP_Error */ public function rest_can_edit() { if ( CustomCssScreen::can_edit() ) { return true; } return new \WP_Error( 'adminkeep_css_forbidden', __( 'You are not allowed to edit CSS on this site.', 'adminkeep' ), array( 'status' => rest_authorization_required_code() ) ); } /** * GET: the stored CSS, byte for byte. * * @return \WP_REST_Response */ public function rest_get() { return rest_ensure_response( array( 'css' => self::css() ) ); } /** * POST: replace the stored CSS. * * Refused CSS is a 400 carrying the same message the screen shows, and the stored CSS is left * as it was. * * @param \WP_REST_Request $request Request. * @return \WP_REST_Response|\WP_Error */ public function rest_save( $request ) { $saved = self::save( (string) $request->get_param( 'css' ) ); if ( is_wp_error( $saved ) ) { $saved->add_data( array( 'status' => 400 ) ); return $saved; } return rest_ensure_response( array( 'css' => $saved ) ); } /** * Print the CSS into the head. * * @return void */ public function print_css() { $css = trim( self::css() ); // With the panel open the tag is printed even when empty: it is what the preview writes // into, and keeping it here keeps the preview at the same place in the cascade. if ( '' === $css && ! LiveCssPanel::is_open() ) { return; } // CSS cannot be HTML-escaped without corrupting it (child combinators, quoted strings). // Stripping tags is what core does for Additional CSS; markup was already refused on save. echo '' . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } }