true, * 'public' => false, * 'publicly_queryable' => false, * 'show_ui' => false, */ 'supports' => array( 'revisions' ), 'label' => 'Custom CSS', 'can_export' => false, 'rewrite' => false, 'capabilities' => array( 'edit_post' => 'edit_theme_options', 'read_post' => 'read', 'delete_post' => 'edit_theme_options', 'edit_posts' => 'edit_theme_options', 'edit_others_posts' => 'edit_theme_options', 'publish_posts' => 'edit_theme_options', 'read_private_posts' => 'read', ), ) ); // Short-circuit WP if this is a CSS stylesheet request if ( isset( $_GET['custom-css'] ) ) { header( 'Content-Type: text/css', true, 200 ); header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + 31536000 ) . ' GMT' ); // 1 year self::print_css(); exit; } add_action( 'admin_enqueue_scripts', array( 'Jetpack_Custom_CSS', 'enqueue_scripts' ) ); if ( isset( $_GET['page'] ) && 'editcss' === $_GET['page'] && is_admin() ) { // Do migration routine if necessary self::upgrade(); /** * Allows additional work when migrating safecss from wp_options to wp_post. * * @module custom-css * * @since 1.7.0 */ do_action( 'safecss_migrate_post' ); } /** * Never embed the style in the head on wpcom. * Yes, this filter should be added to an unsynced file on wpcom, but * there is no good syntactically-correct location to put it yet. * * @link https://github.com/Automattic/jetpack/commit/a1be114e9179f64d147124727a58e2cf76c7e5a1#commitcomment-7763921 */ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { add_filter( 'safecss_embed_style', '__return_false' ); } else { add_filter( 'safecss_embed_style', array( 'Jetpack_Custom_CSS', 'should_we_inline_custom_css' ), 10, 2 ); } add_action( 'wp_head', array( 'Jetpack_Custom_CSS', 'link_tag' ), 101 ); add_filter( 'jetpack_content_width', array( 'Jetpack_Custom_CSS', 'jetpack_content_width' ) ); add_filter( 'editor_max_image_size', array( 'Jetpack_Custom_CSS', 'editor_max_image_size' ), 10, 3 ); if ( ! current_user_can( 'switch_themes' ) && ! is_super_admin() ) { return; } add_action( 'admin_menu', array( 'Jetpack_Custom_CSS', 'menu' ) ); if ( isset( $_POST['safecss'] ) && ( ! isset( $_SERVER['REQUEST_URI'] ) || false === strstr( filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ), 'options.php' ) ) ) { check_admin_referer( 'safecss' ); $save_result = self::save( array( 'css' => filter_var( wp_unslash( $_POST['safecss'] ) ), 'is_preview' => isset( $_POST['action'] ) && $_POST['action'] === 'preview', 'preprocessor' => isset( $_POST['custom_css_preprocessor'] ) ? sanitize_key( $_POST['custom_css_preprocessor'] ) : '', 'add_to_existing' => isset( $_POST['add_to_existing'] ) ? $_POST['add_to_existing'] === 'true' : true, 'content_width' => isset( $_POST['custom_content_width'] ) ? intval( $_POST['custom_content_width'] ) : false, ) ); if ( $_POST['action'] === 'preview' ) { wp_safe_redirect( add_query_arg( 'csspreview', 'true', get_option( 'home' ) ) ); exit; } if ( $save_result ) { add_action( 'admin_notices', array( 'Jetpack_Custom_CSS', 'saved_message' ) ); } } // Modify all internal links so that preview state persists if ( self::is_preview() ) { ob_start( array( 'Jetpack_Custom_CSS', 'buffer' ) ); } } /** * Save new custom CSS. This should be the entry point for any third-party code using Jetpack_Custom_CSS * to save CSS. * * @param array $args Array of arguments: * string $css The CSS (or LESS or Sass) * bool $is_preview Whether this CSS is preview or published * string preprocessor Which CSS preprocessor to use * bool $add_to_existing Whether this CSS replaces the theme's CSS or supplements it. * int $content_width A custom $content_width to go along with this CSS. * @return int The post ID of the saved Custom CSS post. */ public static function save( $args = array() ) { $defaults = array( 'css' => '', 'is_preview' => false, 'preprocessor' => '', 'add_to_existing' => true, 'content_width' => false, ); $args = wp_parse_args( $args, $defaults ); if ( $args['content_width'] && (int) $args['content_width'] > 0 && ( ! isset( $GLOBALS['content_width'] ) || $args['content_width'] !== $GLOBALS['content_width'] ) ) { $args['content_width'] = (int) $args['content_width']; } else { $args['content_width'] = false; } /** * Fires prior to saving custom css values. Necessitated because the * core WordPress save_pre filters were removed: * - content_save_pre * - content_filtered_save_pre * * @module custom-css * * @since 1.7.0 * * @param array $args { * Array of custom CSS arguments. * @type string $css The CSS (or LESS or Sass). * @type bool $is_preview Whether this CSS is preview or published. * @type string preprocessor Which CSS preprocessor to use. * @type bool $add_to_existing Whether this CSS replaces the theme's CSS or supplements it. * @type int $content_width A custom $content_width to go along with this CSS. * } */ do_action( 'safecss_save_pre', $args ); $warnings = array(); safecss_class(); $csstidy = new csstidy(); $csstidy->optimise = new safecss( $csstidy ); $csstidy->set_cfg( 'remove_bslash', false ); $csstidy->set_cfg( 'compress_colors', false ); $csstidy->set_cfg( 'compress_font-weight', false ); $csstidy->set_cfg( 'optimise_shorthands', 0 ); $csstidy->set_cfg( 'remove_last_;', false ); $csstidy->set_cfg( 'case_properties', false ); $csstidy->set_cfg( 'discard_invalid_properties', true ); $csstidy->set_cfg( 'css_level', 'CSS3.0' ); $csstidy->set_cfg( 'preserve_css', true ); $csstidy->set_cfg( 'template', __DIR__ . '/csstidy/wordpress-standard.tpl' ); $prev = $args['css']; $css = preg_replace( '/\\\\([0-9a-fA-F]{4})/', '\\\\\\\\$1', $prev ); // prevent content: '\3434' from turning into '\\3434' $css = str_replace( array( '\'\\\\', '"\\\\' ), array( '\'\\', '"\\' ), $css ); if ( $css !== $prev ) { $warnings[] = 'preg_replace found stuff'; } // Some people put weird stuff in their CSS, KSES tends to be greedy $css = str_replace( '<=', '<=', $css ); // Why KSES instead of strip_tags? Who knows? $prev = $css; $css = wp_kses_split( $prev, array(), array() ); $css = str_replace( '>', '>', $css ); // kses replaces lone '>' with > // Why both KSES and strip_tags? Because we just added some '>'. $css = strip_tags( $css ); // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags if ( $css !== $prev ) { $warnings[] = 'kses found stuff'; } // if we're not using a preprocessor if ( ! $args['preprocessor'] ) { /** * Fires before parsing the css with CSSTidy, but only if * the preprocessor is not configured for use. * * @module custom-css * * @since 1.7.0 * * @param obj $csstidy The csstidy object. * @param string $css Custom CSS. * @param array $args Array of custom CSS arguments. */ do_action( 'safecss_parse_pre', $csstidy, $css, $args ); $csstidy->parse( $css ); /** * Fires after parsing the css with CSSTidy, but only if * the preprocessor is not cinfigured for use. * * @module custom-css * * @since 1.7.0 * * @param obj $csstidy The csstidy object. * @param array $warnings Array of warnings. * @param array $args Array of custom CSS arguments. */ do_action( 'safecss_parse_post', $csstidy, $warnings, $args ); $css = $csstidy->print->plain(); } if ( $args['add_to_existing'] ) { $add_to_existing = 'yes'; } else { $add_to_existing = 'no'; } if ( $args['is_preview'] || self::is_freetrial() ) { // Save the CSS $safecss_revision_id = self::save_revision( $css, true, $args['preprocessor'] ); // Cache Buster update_option( 'safecss_preview_rev', (int) get_option( 'safecss_preview_rev' ) + 1 ); update_metadata( 'post', $safecss_revision_id, 'custom_css_add', $add_to_existing ); update_metadata( 'post', $safecss_revision_id, 'content_width', $args['content_width'] ); update_metadata( 'post', $safecss_revision_id, 'custom_css_preprocessor', $args['preprocessor'] ); delete_option( 'safecss_add' ); delete_option( 'safecss_content_width' ); if ( $args['is_preview'] ) { return $safecss_revision_id; } /** * Fires after saving Custom CSS. * * @module custom-css * * @since 1.7.0 */ do_action( 'safecss_save_preview_post' ); } // Save the CSS $safecss_post_id = self::save_revision( $css, false, $args['preprocessor'] ); $safecss_post_revision = self::get_current_revision(); update_option( 'safecss_rev', (int) get_option( 'safecss_rev' ) + 1 ); update_post_meta( $safecss_post_id, 'custom_css_add', $add_to_existing ); update_post_meta( $safecss_post_id, 'content_width', $args['content_width'] ); update_post_meta( $safecss_post_id, 'custom_css_preprocessor', $args['preprocessor'] ); delete_option( 'safecss_add' ); delete_option( 'safecss_content_width' ); update_metadata( 'post', $safecss_post_revision['ID'], 'custom_css_add', $add_to_existing ); update_metadata( 'post', $safecss_post_revision['ID'], 'content_width', $args['content_width'] ); update_metadata( 'post', $safecss_post_revision['ID'], 'custom_css_preprocessor', $args['preprocessor'] ); delete_option( 'safecss_preview_add' ); return $safecss_post_id; } /** * Get the published custom CSS post. * * @return array */ public static function get_post() { $custom_css_post_id = self::post_id(); if ( $custom_css_post_id ) { return get_post( $custom_css_post_id, ARRAY_A ); } return array(); } /** * Get the post ID of the published custom CSS post. * * @return int|bool The post ID if it exists; false otherwise. */ public static function post_id() { /** * Filter the ID of the post where Custom CSS is stored, before the ID is retrieved. * * If the callback function returns a non-null value, then post_id() will immediately * return that value, instead of retrieving the normal post ID. * * @module custom-css * * @since 3.8.1 * * @param null null The ID to return instead of the normal ID. */ $custom_css_post_id = apply_filters( 'jetpack_custom_css_pre_post_id', null ); if ( $custom_css_post_id !== null ) { return $custom_css_post_id; } $custom_css_post_id = wp_cache_get( 'custom_css_post_id' ); if ( false === $custom_css_post_id ) { $custom_css_posts = get_posts( array( 'posts_per_page' => 1, 'post_type' => 'safecss', 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', ) ); if ( count( $custom_css_posts ) > 0 ) { $custom_css_post_id = $custom_css_posts[0]->ID; } else { $custom_css_post_id = 0; } // Save post_id=0 to note that no safecss post exists. wp_cache_set( 'custom_css_post_id', $custom_css_post_id ); } if ( ! $custom_css_post_id ) { return false; } return $custom_css_post_id; } /** * Get the current revision of the original safecss record * * @return object */ public static function get_current_revision() { $safecss_post = self::get_post(); if ( empty( $safecss_post ) ) { return false; } $revisions = wp_get_post_revisions( $safecss_post['ID'], array( 'posts_per_page' => 1, 'orderby' => 'date', 'order' => 'DESC', ) ); // Empty array if no revisions exist if ( empty( $revisions ) ) { // Return original post return $safecss_post; } else { // Return the first entry in $revisions, this will be the current revision $current_revision = get_object_vars( array_shift( $revisions ) ); return $current_revision; } } /** * Save new revision of CSS * Checks to see if content was modified before really saving * * @param string $css - the CSS. * @param bool $is_preview - if we're in preview mode. * @param string $preprocessor - what preprocessor we're using. * * @return bool|int If nothing was saved, returns false. If a post * or revision was saved, returns the post ID. */ public static function save_revision( $css, $is_preview = false, $preprocessor = '' ) { $safecss_post = self::get_post(); $compressed_css = self::minify( $css, $preprocessor ); // If null, there was no original safecss record, so create one if ( ! $safecss_post ) { if ( ! $css ) { return false; } $post = array(); $post['post_content'] = wp_slash( $css ); $post['post_title'] = 'safecss'; $post['post_status'] = 'publish'; $post['post_type'] = 'safecss'; $post['post_content_filtered'] = wp_slash( $compressed_css ); // Set excerpt to current theme, for display in revisions list $current_theme = wp_get_theme(); $post['post_excerpt'] = $current_theme->Name; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase add_filter( 'wp_insert_post_data', array( __CLASS__, 'restore_unsafe_postcss_content' ), 9, 3 ); // Insert the CSS into wp_posts $post_id = wp_insert_post( $post ); remove_filter( 'wp_insert_post_data', array( __CLASS__, 'restore_unsafe_postcss_content' ), 9 ); wp_cache_set( 'custom_css_post_id', $post_id ); return $post_id; } // Update CSS in post array with new value passed to this function $safecss_post['post_content'] = $css; $safecss_post['post_content_filtered'] = $compressed_css; // Set excerpt to current theme, for display in revisions list $current_theme = wp_get_theme(); $safecss_post['post_excerpt'] = $current_theme->Name; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase // Don't carry over last revision's timestamps, otherwise revisions all have matching timestamps unset( $safecss_post['post_date'] ); unset( $safecss_post['post_date_gmt'] ); unset( $safecss_post['post_modified'] ); unset( $safecss_post['post_modified_gmt'] ); // Do not update post if we are only saving a preview if ( false === $is_preview ) { $safecss_post['post_content'] = wp_slash( $safecss_post['post_content'] ); $safecss_post['post_content_filtered'] = wp_slash( $safecss_post['post_content_filtered'] ); add_filter( 'wp_insert_post_data', array( __CLASS__, 'restore_unsafe_postcss_content' ), 9, 3 ); $post_id = wp_update_post( $safecss_post ); remove_filter( 'wp_insert_post_data', array( __CLASS__, 'restore_unsafe_postcss_content' ), 9 ); wp_cache_set( 'custom_css_post_id', $post_id ); return $post_id; } elseif ( ! defined( 'DOING_MIGRATE' ) ) { add_filter( 'wp_insert_post_data', array( __CLASS__, 'restore_unsafe_postcss_content' ), 9, 3 ); $revision = _wp_put_post_revision( $safecss_post ); remove_filter( 'wp_insert_post_data', array( __CLASS__, 'restore_unsafe_postcss_content' ), 9 ); return $revision; } } /** * Restore Unsafe Post CSS Content. * * @param array $data The post data being filtered. * @param array $postarray Unused. * @param array $unsanitized The unsanitized post data. * * @return array Post data. */ public static function restore_unsafe_postcss_content( $data, $postarray, $unsanitized ) { $replace_content = isset( $data['post_type'] ) && isset( $unsanitized['post_content'] ) && ( 'safecss' === $data['post_type'] || ( 'revision' === $data['post_type'] && ! empty( $data['post_parent'] ) && 'safecss' === get_post_type( $data['post_parent'] ) ) ); if ( $replace_content ) { $data['post_content'] = $unsanitized['post_content']; } return $data; } /** * Prevent the stylesheet from being enqued. * * @return bool */ public static function skip_stylesheet() { /** * Prevent the Custom CSS stylesheet from being enqueued. * * @module custom-css * * @since 2.2.1 * * @param null Should the stylesheet be skipped. Default to null. Anything else will force the stylesheet to be skipped. */ $skip_stylesheet = apply_filters( 'safecss_skip_stylesheet', null ); if ( null !== $skip_stylesheet ) { return $skip_stylesheet; } elseif ( self::is_customizer_preview() ) { return false; } else { if ( self::is_preview() ) { $safecss_post = self::get_current_revision(); if ( $safecss_post ) { return (bool) ( get_post_meta( $safecss_post['ID'], 'custom_css_add', true ) === 'no' ); } else { return (bool) ( get_option( 'safecss_preview_add' ) === 'no' ); } } else { $custom_css_post_id = self::post_id(); if ( $custom_css_post_id ) { $custom_css_add = get_post_meta( $custom_css_post_id, 'custom_css_add', true ); // It is possible for the CSS to be stored in a post but for the safecss_add option // to have not been upgraded yet if the user hasn't opened their Custom CSS editor // since October 2012. if ( ! empty( $custom_css_add ) ) { return (bool) ( $custom_css_add === 'no' ); } } return (bool) ( Jetpack_Options::get_option_and_ensure_autoload( 'safecss_add', '' ) === 'no' ); } } } /** * Checks if we're in a preview mode. * * @return bool */ public static function is_preview() { return isset( $_GET['csspreview'] ) && $_GET['csspreview'] === 'true'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no changes made to the site. } /** * Currently this filter function gets called on * 'template_redirect' action and * 'admin_init' action */ public static function set_content_width() { // Don't apply this filter on the Edit CSS page if ( isset( $_GET ) && isset( $_GET['page'] ) && 'editcss' === $_GET['page'] && is_admin() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nothing changing on the site, it's not applying a filter if set. return; } $GLOBALS['content_width'] = Jetpack::get_content_width(); } /** * False when the site has the Custom Design upgrade. * Used only on WordPress.com. * * @return bool * @todo see if we can remove this, I don't believe WordPress.com uses free trials anymore. */ public static function is_freetrial() { /** * Determine if a WordPress.com site uses a Free trial of the Custom Design Upgrade. * Used only on WordPress.com. * * @module custom-css * * @since 1.7.0 * * @param bool false Does the site use a Free trial of the Custom Design Upgrade. Default to false. */ return apply_filters( 'safecss_is_freetrial', false ); } /** * Get the preprocessor key. * * @return string|false */ public static function get_preprocessor_key() { $safecss_post = self::get_current_revision(); return get_post_meta( $safecss_post['ID'], 'custom_css_preprocessor', true ); } /** * Get the prepocessor. * * @return string|null */ public static function get_preprocessor() { /** This filter is documented in modules/custom-css/custom-css.php */ $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() ); $selected_preprocessor_key = self::get_preprocessor_key(); $selected_preprocessor = isset( $preprocessors[ $selected_preprocessor_key ] ) ? $preprocessors[ $selected_preprocessor_key ] : null; return $selected_preprocessor; } /** * Get the CSS. * * @param boolean $compressed - if the CSS is compressed. * * @return string */ public static function get_css( $compressed = false ) { /** * Filter the Custom CSS returned. * Can be used to return an error, or no CSS at all. * * @module custom-css * * @since 1.7.0 * * @param bool false Should we return an error instead of the Custom CSS. Default to false. */ $default_css = apply_filters( 'safecss_get_css_error', false ); if ( $default_css !== false ) { return $default_css; } $option = ( self::is_preview() || self::is_freetrial() ) ? 'safecss_preview' : 'safecss'; $css = ''; if ( 'safecss' === $option ) { // Don't bother checking for a migrated 'safecss' option if it never existed. if ( false === get_option( 'safecss' ) || get_option( 'safecss_revision_migrated' ) ) { $safecss_post = self::get_post(); if ( ! empty( $safecss_post ) ) { $css = ( $compressed && $safecss_post['post_content_filtered'] ) ? $safecss_post['post_content_filtered'] : $safecss_post['post_content']; } } else { $current_revision = self::get_current_revision(); if ( false === $current_revision ) { $css = ''; } else { $css = ( $compressed && $current_revision['post_content_filtered'] ) ? $current_revision['post_content_filtered'] : $current_revision['post_content']; } } // Fix for un-migrated Custom CSS if ( empty( $safecss_post ) ) { $_css = get_option( 'safecss' ); if ( ! empty( $_css ) ) { $css = $_css; } } } elseif ( 'safecss_preview' === $option ) { $safecss_post = self::get_current_revision(); $css = $safecss_post['post_content']; $css = self::minify( $css, get_post_meta( $safecss_post['ID'], 'custom_css_preprocessor', true ) ); } $css = str_replace( array( '\\\00BB \\\0020', '\0BB \020', '0BB 020' ), '\00BB \0020', $css ); /** * Filter the Custom CSS returned from the editor. * * @module custom-css * * @since 1.7.0 * * @param string $css Custom CSS. */ $css = apply_filters( 'safecss_css', $css ); return $css; } /** * Replace insecure URLs. * * @param string $css - the CSS. * * @return string */ public static function replace_insecure_urls( $css ) { if ( ! function_exists( '_sa_get_frontend_https_url_replacement_map' ) ) { return $css; } list( $http_urls, $secure_urls ) = _sa_get_frontend_https_url_replacement_map(); return str_replace( $http_urls, $secure_urls, $css ); } /** * Print the CSS. */ public static function print_css() { /** * Fires right before printing the custom CSS inside the
element. * * @module custom-css * * @since 1.7.0 */ do_action( 'safecss_print_pre' ); $css = self::get_css( true ); echo self::replace_insecure_urls( $css ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * If the CSS is less than 2,000 characters, inline it! otherwise return what was passed in. * * @param bool $should_we if we should inline the CSS. * @param string $css - the CSS object. * * @return bool */ public static function should_we_inline_custom_css( $should_we, $css ) { return ( strlen( $css ) < 2000 ) ? true : $should_we; } /** * Add the link tag to inline CSS. */ public static function link_tag() { global $blog_id, $current_blog; if ( /** * Do not include any CSS on the page if the CSS includes an error. * Setting this filter to true stops any Custom CSS from being enqueued. * * @module custom-css * * @since 1.7.0 * * @param bool false Does the CSS include an error. Default to false. */ apply_filters( 'safecss_style_error', false ) ) { return; } if ( ! is_super_admin() && isset( $current_blog ) && ( $current_blog->spam || $current_blog->deleted ) ) { return; } if ( self::is_customizer_preview() ) { return; } $css = ''; $option = self::is_preview() ? 'safecss_preview' : 'safecss'; if ( 'safecss' === $option ) { if ( Jetpack_Options::get_option_and_ensure_autoload( 'safecss_revision_migrated', '0' ) ) { $safecss_post = self::get_post(); if ( ! empty( $safecss_post['post_content'] ) ) { $css = $safecss_post['post_content']; } } else { $current_revision = self::get_current_revision(); if ( ! empty( $current_revision['post_content'] ) ) { $css = $current_revision['post_content']; } } // Fix for un-migrated Custom CSS if ( empty( $safecss_post ) ) { $_css = Jetpack_Options::get_option_and_ensure_autoload( 'safecss', '' ); if ( ! empty( $_css ) ) { $css = $_css; } } } if ( 'safecss_preview' === $option ) { $safecss_post = self::get_current_revision(); if ( ! empty( $safecss_post['post_content'] ) ) { $css = $safecss_post['post_content']; } } $css = str_replace( array( '\\\00BB \\\0020', '\0BB \020', '0BB 020' ), '\00BB \0020', $css ); if ( $css === '' ) { return; } if ( /** * Allow inserting CSS inline instead of through a separate file. * * @module custom-css * * @since 3.4.0 * * @param bool false Should the CSS be added inline instead of through a separate file. Default to false. * @param string $css Custom CSS. */ apply_filters( 'safecss_embed_style', false, $css ) ) { echo "\r\n" . '\r\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } else { $href = home_url( '/' ); $href = add_query_arg( 'custom-css', 1, $href ); $href = add_query_arg( 'csblog', $blog_id, $href ); $href = add_query_arg( 'cscache', 6, $href ); $href = add_query_arg( 'csrev', (int) get_option( $option . '_rev' ), $href ); /** * Filter the Custom CSS link enqueued in the head. * * @module custom-css * * @since 1.7.0 * * @param string $href Custom CSS link enqueued in the head. * @param string $blog_id Blog ID. */ $href = apply_filters( 'safecss_href', $href, $blog_id ); if ( self::is_preview() ) { $href = add_query_arg( 'csspreview', 'true', $href ); } ?> in the element for the custom css stylesheet. * * @module custom-css * * @since 2.2.2 */ do_action( 'safecss_link_tag_post' ); } /** * Filter the default blank Custom CSS URL. * * @param string $current - the current CSS. * * @return string */ public static function style_filter( $current ) { if ( self::is_freetrial() && ( ! self::is_preview() || ! current_user_can( 'switch_themes' ) ) ) { return $current; } elseif ( self::skip_stylesheet() ) { /** * Filter the default blank Custom CSS URL. * * @module custom-css * * @since 2.2.1 * * @param string $url Default blank Custom CSS URL. */ return apply_filters( 'safecss_style_filter_url', plugins_url( 'custom-css/css/blank.css', __FILE__ ) ); } return $current; } /** * Buffer the HTML. * * @param string $html - the HTML. * * @return string */ public static function buffer( $html ) { $html = str_replace( '', self::preview_flag(), $html ); return preg_replace_callback( '!href=([\'"])(.*?)\\1!', array( 'Jetpack_Custom_CSS', 'preview_links' ), $html ); } /** * Preview links. * * @param array $matches - the matches. * * @return string */ public static function preview_links( $matches ) { if ( 0 !== strpos( $matches[2], get_option( 'home' ) ) ) { return $matches[0]; } $link = wp_specialchars_decode( $matches[2] ); $link = add_query_arg( 'csspreview', 'true', $link ); $link = esc_url( $link ); return "href={$matches[1]}$link{$matches[1]}"; } /** * Places a black bar above every preview page */ public static function preview_flag() { if ( is_admin() ) { return; } $message = esc_html__( 'Preview: changes must be saved or they will be lost', 'jetpack' ); /** * Filter the Preview message displayed on the site when previewing custom CSS, before to save it. * * @module custom-css * * @since 1.7.0 * * @param string $message Custom CSS preview message. */ $message = apply_filters( 'safecss_preview_message', $message ); $preview_flag_js = "var flag = document.createElement('div'); flag.innerHTML = " . wp_json_encode( $message ) . "; flag.style.background = '#FF6600'; flag.style.color = 'white'; flag.style.textAlign = 'center'; flag.style.fontSize = '15px'; flag.style.padding = '2px'; flag.style.fontFamily = 'sans-serif'; document.body.style.paddingTop = '0px'; document.body.insertBefore(flag, document.body.childNodes[0]); "; /** * Filter the Custom CSS preview message JS styling. * * @module custom-css * * @since 1.7.0 * * @param string $preview_flag_js Custom CSS preview message JS styling. */ $preview_flag_js = apply_filters( 'safecss_preview_flag_js', $preview_flag_js ); if ( $preview_flag_js ) { $preview_flag_js = ''; } return $preview_flag_js; } /** * Add the additional CSS menu. */ public static function menu() { $title = __( 'Additional CSS', 'jetpack' ); $hook = add_theme_page( $title, $title, 'edit_theme_options', 'editcss', array( 'Jetpack_Custom_CSS', 'admin' ) ); add_action( 'load-revision.php', array( 'Jetpack_Custom_CSS', 'prettify_post_revisions' ) ); add_action( "load-$hook", array( 'Jetpack_Custom_CSS', 'update_title' ) ); } /** * Adds a menu item in the appearance section for this plugin's administration * page. Also adds hooks to enqueue the CSS and JS for the admin page. */ public static function update_title() { global $title; $title = __( 'CSS', 'jetpack' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited } /** * Prettify the post revision. */ public static function prettify_post_revisions() { add_filter( 'the_title', array( 'Jetpack_Custom_CSS', 'post_title' ), 10, 2 ); } /** * Get the post title. * * @param string $title - the post title. * @param int $post_id - the post ID. * * @return string */ public static function post_title( $title, $post_id ) { $post_id = (int) $post_id; if ( ! $post_id ) { return $title; } $post = get_post( $post_id ); if ( ! $post ) { return $title; } if ( 'safecss' !== $post->post_type ) { return $title; } return __( 'Custom CSS Stylesheet', 'jetpack' ); } /** * Enqueue scripts. * * @param string $hook - the hook. */ public static function enqueue_scripts( $hook ) { if ( 'appearance_page_editcss' !== $hook ) { return; } wp_enqueue_script( 'postbox' ); wp_enqueue_script( 'custom-css-editor', Assets::get_file_url_for_environment( '_inc/build/custom-css/custom-css/js/css-editor.min.js', 'modules/custom-css/custom-css/js/css-editor.js' ), 'jquery', '20130325', true ); wp_enqueue_style( 'custom-css-editor', plugins_url( 'custom-css/css/css-editor.css', __FILE__ ) ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion if ( defined( 'SAFECSS_USE_ACE' ) && SAFECSS_USE_ACE ) { wp_register_style( 'jetpack-css-codemirror', plugins_url( 'custom-css/css/codemirror.css', __FILE__ ), array(), '20120905' ); wp_enqueue_style( 'jetpack-css-use-codemirror', plugins_url( 'custom-css/css/use-codemirror.css', __FILE__ ), array( 'jetpack-css-codemirror' ), '20120905' ); wp_register_script( 'jetpack-css-codemirror', plugins_url( 'custom-css/js/codemirror.min.js', __FILE__ ), array(), '3.16', true ); wp_enqueue_script( 'jetpack-css-use-codemirror', Assets::get_file_url_for_environment( '_inc/build/custom-css/custom-css/js/use-codemirror.min.js', 'modules/custom-css/custom-css/js/use-codemirror.js' ), array( 'jquery', 'underscore', 'jetpack-css-codemirror' ), '20131009', true ); } } /** * Render the saved message. */ public static function saved_message() { echo '' . esc_html__( 'Stylesheet saved.', 'jetpack' ) . '