public static function do_migration() { Jetpack_Options::update_option('custom_css_4.7_migration', true); Jetpack::log('custom_css_4.7_migration', 'start'); if (!post_type_exists('safecss')) { self::register_legacy_post_type(); } /** This filter is documented in modules/custom-css/custom-css.php */ $preprocessors = apply_filters('jetpack_custom_css_preprocessors', array()); $core_css_post = wp_get_custom_css_post(); $jetpack_css_post = self::get_post(); $revisions = self::get_all_revisions(); // Migrate the settings from revision meta to theme mod. $options = self::get_options($jetpack_css_post->ID); set_theme_mod('jetpack_custom_css', $options); if (empty($revisions) || !is_array($revisions)) { if ($jetpack_css_post instanceof WP_Post) { // Feed in the raw, if the current setting is Sass/LESS, it'll filter it inside. wp_update_custom_css_post($jetpack_css_post->post_content); return 1; } return null; } $revisions = array_reverse($revisions); $themes = Jetpack_Custom_CSS_Enhancements::get_themes(); $migrated = array(); foreach ($revisions as $post_id => $post) { // Jetpack had stored the theme Name, not the stylesheet directory, for ... reasons. // Get the stylesheet. If null, the theme is no longer available. Skip. $stylesheet = isset($themes[$post->post_excerpt]) ? $themes[$post->post_excerpt] : null; if (empty($stylesheet)) { continue; } $migrated[] = $post->ID; $preprocessor = get_post_meta($post->ID, 'custom_css_preprocessor', true); $css = $post->post_content; $pre = ''; // Do a revision by revision parsing. if ($preprocessor && isset($preprocessors[$preprocessor])) { $pre = $css; $css = call_user_func($preprocessors[$preprocessor]['callback'], $pre); } // Do we need to remove any filters here for users without `unfiltered_html` ? wp_update_custom_css_post($css, array('stylesheet' => $stylesheet, 'preprocessed' => $pre)); } // If we've migrated some CSS for the current theme and there was already something there in the Core dataset ... if ($core_css_post && $jetpack_css_post) { $preprocessor = $options['preprocessor']; $css = $core_css_post->post_content; $pre = $core_css_post->post_content_filtered; if ($preprocessor) { if ($pre) { $pre .= "\r\n\r\n/*\r\n\t" . esc_js(__('CSS Migrated from Jetpack:', 'jetpack')) . "\r\n*/\r\n\r\n"; } $pre .= $jetpack_css_post->post_content; $css .= "\r\n\r\n/*\r\n\t" . esc_js(__('CSS Migrated from Jetpack:', 'jetpack')) . "\r\n*/\r\n\r\n"; $css .= call_user_func($preprocessors[$preprocessor]['callback'], $jetpack_css_post->post_content); } else { $css .= "\r\n\r\n/*\r\n\t" . esc_js(__('CSS Migrated from Jetpack:', 'jetpack')) . "\r\n*/\r\n\r\n"; $css .= $jetpack_css_post->post_content; } wp_update_custom_css_post($css, array('preprocessed' => $pre)); } Jetpack::log('custom_css_4.7_migration', sizeof($migrated) . 'revisions migrated'); return sizeof($migrated); }
/** * Store the CSS setting value in the custom_css custom post type for the stylesheet. * * @since 4.7.0 * @access public * * @param string $css The input value. * @return int|false The post ID or false if the value could not be saved. */ public function update($css) { $setting = $this; if (empty($css)) { $css = ''; } $args = array('post_content' => $css, 'post_content_filtered' => ''); /** * Filters the `post_content` and `post_content_filtered` args for a `custom_css` post being updated. * * This filter can be used by plugin that offer CSS pre-processors, to store the original * pre-processed CSS in `post_content_filtered` and then store processed CSS in `post_content`. * When used in this way, the `post_content_filtered` should be supplied as the setting value * instead of `post_content` via a the `customize_value_custom_css` filter, for example: * * <code> * add_filter( 'customize_value_custom_css', function( $value, $setting ) { * $post = wp_get_custom_css_post( $setting->stylesheet ); * if ( $post && ! empty( $post->post_content_filtered ) ) { * $css = $post->post_content_filtered; * } * return $css; * }, 10, 2 ); * </code> * * @since 4.7.0 * @param array $args { * Content post args (unslashed) for `wp_update_post()`/`wp_insert_post()`. * * @type string $post_content CSS. * @type string $post_content_filtered Pre-processed CSS. Normally empty string. * } * @param string $css Original CSS being updated. * @param WP_Customize_Custom_CSS_Setting $setting Custom CSS Setting. */ $args = apply_filters('customize_update_custom_css_post_content_args', $args, $css, $setting); $args = wp_array_slice_assoc($args, array('post_content', 'post_content_filtered')); $args = array_merge($args, array('post_title' => $this->stylesheet, 'post_name' => sanitize_title($this->stylesheet), 'post_type' => 'custom_css', 'post_status' => 'publish')); // Update post if it already exists, otherwise create a new one. $post = wp_get_custom_css_post($this->stylesheet); if ($post) { $args['ID'] = $post->ID; $post_id = wp_update_post(wp_slash($args)); } else { $post_id = wp_insert_post(wp_slash($args)); } if (!$post_id) { return false; } // Cache post ID in theme mod for performance to avoid additional DB query. if ($this->manager->get_stylesheet() === $this->stylesheet) { set_theme_mod('custom_css_post_id', $post_id); } return $post_id; }
/** * Test crud methods on WP_Customize_Custom_CSS_Setting. * * @covers wp_get_custom_css() * @covers WP_Customize_Custom_CSS_Setting::value() * @covers WP_Customize_Custom_CSS_Setting::preview() * @covers WP_Customize_Custom_CSS_Setting::update() */ function test_crud() { $this->setting->default = '/* Hello World */'; $this->assertEquals($this->setting->default, $this->setting->value()); $this->assertNull(wp_get_custom_css_post()); $this->assertNull(wp_get_custom_css_post($this->setting->stylesheet)); $this->assertNull(wp_get_custom_css_post('twentyten')); $original_css = 'body { color: black; }'; $post_id = $this->factory()->post->create(array('post_title' => $this->setting->stylesheet, 'post_name' => $this->setting->stylesheet, 'post_content' => $original_css, 'post_status' => 'publish', 'post_type' => 'custom_css')); $twentyten_css = 'body { color: red; }'; $twentyten_post_id = $this->factory()->post->create(array('post_title' => 'twentyten', 'post_name' => 'twentyten', 'post_content' => $twentyten_css, 'post_status' => 'publish', 'post_type' => 'custom_css')); $twentyten_setting = new WP_Customize_Custom_CSS_Setting($this->wp_customize, 'custom_css[twentyten]'); $this->assertEquals($post_id, wp_get_custom_css_post()->ID); $this->assertEquals($post_id, wp_get_custom_css_post($this->setting->stylesheet)->ID); $this->assertEquals($twentyten_post_id, wp_get_custom_css_post('twentyten')->ID); $this->assertEquals($original_css, wp_get_custom_css($this->setting->stylesheet)); $this->assertEquals($original_css, $this->setting->value()); $this->assertEquals($twentyten_css, wp_get_custom_css('twentyten')); $this->assertEquals($twentyten_css, $twentyten_setting->value()); $updated_css = 'body { color: blue; }'; $this->wp_customize->set_post_value($this->setting->id, $updated_css); $saved = $this->setting->save(); $this->assertTrue(false !== $saved); $this->assertEquals($updated_css, $this->setting->value()); $this->assertEquals($updated_css, wp_get_custom_css($this->setting->stylesheet)); $this->assertEquals($updated_css, get_post($post_id)->post_content); $previewed_css = 'body { color: red; }'; $this->wp_customize->set_post_value($this->setting->id, $previewed_css); $this->setting->preview(); $this->assertEquals($previewed_css, $this->setting->value()); $this->assertEquals($previewed_css, wp_get_custom_css($this->setting->stylesheet)); // Make sure that wp_update_custom_css_post() works as expected for updates. $r = wp_update_custom_css_post('body { color:red; }', array('stylesheet' => $this->setting->stylesheet, 'preprocessed' => "body\n\tcolor:red;")); $this->assertInstanceOf('WP_Post', $r); $this->assertEquals($post_id, $r->ID); $this->assertEquals('body { color:red; }', get_post($r)->post_content); $this->assertEquals("body\n\tcolor:red;", get_post($r)->post_content_filtered); $r = wp_update_custom_css_post('body { content: "\\o/"; }'); $this->assertEquals($this->wp_customize->get_stylesheet(), get_post($r)->post_name); $this->assertEquals('body { content: "\\o/"; }', get_post($r)->post_content); $this->assertEquals('', get_post($r)->post_content_filtered); // Make sure that wp_update_custom_css_post() works as expected for insertion. $r = wp_update_custom_css_post('body { background:black; }', array('stylesheet' => 'other')); $this->assertInstanceOf('WP_Post', $r); $this->assertEquals('other', get_post($r)->post_name); $this->assertEquals('body { background:black; }', get_post($r)->post_content); $this->assertEquals('publish', get_post($r)->post_status); // Test deletion. wp_delete_post($post_id); $this->assertNull(wp_get_custom_css_post()); $this->assertNull(wp_get_custom_css_post(get_stylesheet())); $this->assertEquals($previewed_css, wp_get_custom_css(get_stylesheet()), 'Previewed value remains in spite of deleted post.'); wp_delete_post($twentyten_post_id); $this->assertNull(wp_get_custom_css_post('twentyten')); $this->assertEquals('', wp_get_custom_css('twentyten')); }
/** * Fetch the value of the setting. Will return the previewed value when `preview()` is called. * * @since 4.7.0 * @access public * @see WP_Customize_Setting::value() * * @return string */ public function value() { if ($this->is_previewed) { $post_value = $this->post_value(null); if (null !== $post_value) { return $post_value; } } $id_base = $this->id_data['base']; $value = ''; $post = wp_get_custom_css_post($this->stylesheet); if ($post) { $value = $post->post_content; } if (empty($value)) { $value = $this->default; } /** This filter is documented in wp-includes/class-wp-customize-setting.php */ $value = apply_filters("customize_value_{$id_base}", $value, $this); return $value; }
/** * Fetch the saved Custom CSS content for rendering. * * @since 4.7.0 * @access public * * @param string $stylesheet Optional. A theme object stylesheet name. Defaults to the current theme. * @return string The Custom CSS Post content. */ function wp_get_custom_css($stylesheet = '') { $css = ''; if (empty($stylesheet)) { $stylesheet = get_stylesheet(); } $post = wp_get_custom_css_post($stylesheet); if ($post) { $css = $post->post_content; } /** * Modify the Custom CSS Output into the <head>. * * @since 4.7.0 * * @param string $css CSS pulled in from the Custom CSS CPT. * @param string $stylesheet The theme stylesheet name. */ $css = apply_filters('wp_get_custom_css', $css, $stylesheet); return $css; }
/** * Update the `custom_css` post for a given theme. * * Inserts a `custom_css` post when one doesn't yet exist. * * @since 4.7.0 * @access public * * @param string $css CSS, stored in `post_content`. * @param array $args { * Args. * * @type string $preprocessed Pre-processed CSS, stored in `post_content_filtered`. Normally empty string. Optional. * @type string $stylesheet Stylesheet (child theme) to update. Optional, defaults to current theme/stylesheet. * } * @return WP_Post|WP_Error Post on success, error on failure. */ function wp_update_custom_css_post($css, $args = array()) { $args = wp_parse_args($args, array('preprocessed' => '', 'stylesheet' => get_stylesheet())); $data = array('css' => $css, 'preprocessed' => $args['preprocessed']); /** * Filters the `css` (`post_content`) and `preprocessed` (`post_content_filtered`) args for a `custom_css` post being updated. * * This filter can be used by plugin that offer CSS pre-processors, to store the original * pre-processed CSS in `post_content_filtered` and then store processed CSS in `post_content`. * When used in this way, the `post_content_filtered` should be supplied as the setting value * instead of `post_content` via a the `customize_value_custom_css` filter, for example: * * <code> * add_filter( 'customize_value_custom_css', function( $value, $setting ) { * $post = wp_get_custom_css_post( $setting->stylesheet ); * if ( $post && ! empty( $post->post_content_filtered ) ) { * $css = $post->post_content_filtered; * } * return $css; * }, 10, 2 ); * </code> * * @since 4.7.0 * @param array $data { * Custom CSS data. * * @type string $css CSS stored in `post_content`. * @type string $preprocessed Pre-processed CSS stored in `post_content_filtered`. Normally empty string. * } * @param array $args { * The args passed into `wp_update_custom_css_post()` merged with defaults. * * @type string $css The original CSS passed in to be updated. * @type string $preprocessed The original preprocessed CSS passed in to be updated. * @type string $stylesheet The stylesheet (theme) being updated. * } */ $data = apply_filters('update_custom_css_data', $data, array_merge($args, compact('css'))); $post_data = array('post_title' => $args['stylesheet'], 'post_name' => sanitize_title($args['stylesheet']), 'post_type' => 'custom_css', 'post_status' => 'publish', 'post_content' => $data['css'], 'post_content_filtered' => $data['preprocessed']); // Update post if it already exists, otherwise create a new one. $post = wp_get_custom_css_post($args['stylesheet']); if ($post) { $post_data['ID'] = $post->ID; $r = wp_update_post(wp_slash($post_data), true); } else { $r = wp_insert_post(wp_slash($post_data), true); // Trigger creation of a revision. This should be removed once #30854 is resolved. if (!is_wp_error($r) && 0 === count(wp_get_post_revisions($r))) { wp_save_post_revision($r); } } if (is_wp_error($r)) { return $r; } return get_post($r); }
/** * Add CSS preprocessing to our CSS if it is supported. * * @param mixed $css Value of the setting. * @param WP_Customize_Setting $setting WP_Customize_Setting instance. * * @return string */ public static function customize_value_custom_css($css, $setting) { // Find the current preprocessor. $jetpack_custom_css = get_theme_mod('jetpack_custom_css', array()); if (isset($jetpack_custom_css['preprocessor'])) { $preprocessor = $jetpack_custom_css['preprocessor']; } // If it's not supported, just return. /** This filter is documented in modules/custom-css/custom-css.php */ $preprocessors = apply_filters('jetpack_custom_css_preprocessors', array()); if (!isset($preprocessors[$preprocessor])) { return $css; } // Swap it for the `post_content_filtered` instead. $post = wp_get_custom_css_post($setting->stylesheet); if ($post && !empty($post->post_content_filtered)) { $css = $post->post_content_filtered; } return $css; }
/** * Test crud methods on WP_Customize_Custom_CSS_Setting. * * @covers wp_get_custom_css() * @covers WP_Customize_Custom_CSS_Setting::value() * @covers WP_Customize_Custom_CSS_Setting::preview() * @covers WP_Customize_Custom_CSS_Setting::update() */ function test_crud() { $this->setting->default = '/* Hello World */'; $this->assertEquals($this->setting->default, $this->setting->value()); $this->assertNull(wp_get_custom_css_post()); $this->assertNull(wp_get_custom_css_post($this->setting->stylesheet)); $this->assertNull(wp_get_custom_css_post('twentyten')); $original_css = 'body { color: black; }'; $post_id = $this->factory()->post->create(array('post_title' => $this->setting->stylesheet, 'post_name' => $this->setting->stylesheet, 'post_content' => $original_css, 'post_status' => 'publish', 'post_type' => 'custom_css')); $twentyten_css = 'body { color: red; }'; $twentyten_post_id = $this->factory()->post->create(array('post_title' => 'twentyten', 'post_name' => 'twentyten', 'post_content' => $twentyten_css, 'post_status' => 'publish', 'post_type' => 'custom_css')); $twentyten_setting = new WP_Customize_Custom_CSS_Setting($this->wp_customize, 'custom_css[twentyten]'); $this->assertEquals($post_id, wp_get_custom_css_post()->ID); $this->assertEquals($post_id, wp_get_custom_css_post($this->setting->stylesheet)->ID); $this->assertEquals($twentyten_post_id, wp_get_custom_css_post('twentyten')->ID); $this->assertEquals($original_css, wp_get_custom_css($this->setting->stylesheet)); $this->assertEquals($original_css, $this->setting->value()); $this->assertEquals($twentyten_css, wp_get_custom_css('twentyten')); $this->assertEquals($twentyten_css, $twentyten_setting->value()); $updated_css = 'body { color: blue; }'; $this->wp_customize->set_post_value($this->setting->id, $updated_css); $saved = $this->setting->save(); $this->assertTrue(false !== $saved); $this->assertEquals($updated_css, $this->setting->value()); $this->assertEquals($updated_css, wp_get_custom_css($this->setting->stylesheet)); $previewed_css = 'body { color: red; }'; $this->wp_customize->set_post_value($this->setting->id, $previewed_css); $this->setting->preview(); $this->assertEquals($previewed_css, $this->setting->value()); $this->assertEquals($previewed_css, wp_get_custom_css($this->setting->stylesheet)); wp_delete_post($post_id); $this->assertNull(wp_get_custom_css_post()); $this->assertNull(wp_get_custom_css_post(get_stylesheet())); $this->assertEquals($previewed_css, wp_get_custom_css(get_stylesheet()), 'Previewed value remains in spite of deleted post.'); wp_delete_post($twentyten_post_id); $this->assertNull(wp_get_custom_css_post('twentyten')); $this->assertEquals('', wp_get_custom_css('twentyten')); }