Changeset 2973591
- Timestamp:
- 10/01/2023 07:59:48 PM (18 months ago)
- Location:
- fcp-first-screen-css/trunk
- Files:
-
- 15 added
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
fcp-first-screen-css/trunk/README.md
r2933528 r2973591 1 # F CP First Screen CSS1 # First Screen CSS & Settings 2 2 3 Insert the inline CSS to the head tag of a website, disable existing styles and scripts, defer loading of not-first-screen style, apply to a single post or bulk.3 Insert the inline CSS into your website's head tag. You can choose to inline, defer, or de-register existing styles and scripts. Additionally, you can add custom non-inline styles. Apply everything individually, by post-type, or for archives. 4 4 5 5 ## Features 6 6 7 * Apply to any single post / page / custom post-type8 * Apply to all posts of a particular post-type9 * Apply to the blog or any post-type archive10 * It minifies the CSS before printing11 * Deregister styles and scripts: all or by name12 * A pply the not-first-screen CSS separately13 * Defer the not-first-screen CSS loading7 * Full control over your website's CSS 8 * Apply changes to individual posts/pages/custom post types 9 * Apply changes to all posts of a specific type 10 * Apply changes to the blog or any post-type archive 11 * Inline, defer, or de-register Styles or JS 12 * Automatic CSS minification 13 * Edit CSS easily with the CodeMirror visual editor 14 14 15 15 ## Demo … … 19 19 ## Usage 20 20 21 * Install and activate the plugin 22 * Go to the "First Screen CSS" menu item in the left sidebar of your wp-admin 23 * Add New, insert your CSS 24 * Pick where to apply and other options 21 * Install and activate the plugin. 22 * Navigate to "First Screen CSS" in the left sidebar of your wp-admin. 23 * Click "Add New." 24 * Input your CSS or adjust the settings. 25 * Choose where to apply the changes and publish. -
fcp-first-screen-css/trunk/assets/style.css
r2934071 r2973591 27 27 color: #888; 28 28 } 29 [id^="first-screen-css"] input[type=text]::placeholder { 30 color: #999; 31 } 29 32 30 33 button[name=format], button[name=linewrap], button[name=infinity] { … … 32 35 z-index: 1; 33 36 top: 0; 34 left: - 15px;37 left: -20px; 35 38 font-family: monospace; 39 transform: scale(0.85); 36 40 } 37 41 button[name=linewrap] { … … 41 45 top: 52px; 42 46 } 47 48 #first-screen-css-inline { 49 border-left: 5px solid #B2DFDB; 50 } 51 #first-screen-css-defer { 52 border-left: 5px solid #C5CAE9; 53 } 54 #first-screen-css-deregister { 55 border-left: 5px solid #E1BEE7; 56 } 57 #first-screen-css-inline:not(.closed), 58 #first-screen-css-defer:not(.closed), 59 #first-screen-css-deregister:not(.closed) { 60 padding-bottom: 25px; 61 } -
fcp-first-screen-css/trunk/changelog.txt
r2934071 r2973591 1 = 1.6 = 2 3 * Added the options to inline, defer and de-register CSS and JS 4 * Enhanced the CodeMirror editor for CSS manipulation 5 * Improved performance handling heavy CSS 6 * Enhanced descriptions and added simplified instructions 7 1 8 = 1.5 = 2 9 -
fcp-first-screen-css/trunk/first-screen.php
r2939014 r2973591 1 1 <?php 2 2 /* 3 Plugin Name: F CP First Screen CSS4 Description: Insert inline CSS to the head of the website, so the first screen renders with no jumps, which might improve the CLS web vital. Or for any other reason.5 Version: 1. 5.023 Plugin Name: First Screen CSS & Settings 4 Description: Insert the inline CSS into your website's head tag. You can choose to inline, defer, or de-register existing styles and scripts. Additionally, you can add custom non-inline styles. Apply everything individually, by post-type, or for archives. 5 Version: 1.6 6 6 Requires at least: 5.8 7 Tested up to: 6. 17 Tested up to: 6.3 8 8 Requires PHP: 7.4 9 Author: Firmcatalyst, Vadim Volkov9 Author: Vadim Volkov, Firmcatalyst 10 10 Author URI: https://firmcatalyst.com 11 11 License: GPL v3 or later … … 16 16 defined( 'ABSPATH' ) || exit; 17 17 18 define( 'FCPFSC_DEV', false );18 define( 'FCPFSC_DEV', true ); 19 19 define( 'FCPFSC_VER', get_file_data( __FILE__, [ 'ver' => 'Version' ] )[ 'ver' ] . ( FCPFSC_DEV ? time() : '' ) ); 20 20 21 21 define( 'FCPFSC_SLUG', 'fcpfsc' ); 22 22 define( 'FCPFSC_PREF', FCPFSC_SLUG.'-' ); 23 define( 'FCPFSC_FRONT_ PREF', 'first-screen' );23 define( 'FCPFSC_FRONT_NAME', 'first-screen' ); 24 24 25 define( 'FCPFSC_CM_VER', '5.65.13' ); 25 define( 'FCPFSC_URL', plugin_dir_url( __FILE__ ) ); 26 define( 'FCPFSC_DIR', plugin_dir_path( __FILE__ ) ); 26 27 27 // print the styles 28 add_action( 'wp_enqueue_scripts', function() { 28 define( 'FCPFSC_REST_URL', wp_upload_dir()['baseurl'] . '/' . basename( FCPFSC_DIR ) ); 29 define( 'FCPFSC_REST_DIR', wp_upload_dir()['basedir'] . '/' . basename( FCPFSC_DIR ) ); 29 30 30 // collect css-s to print on the post 31 $csss = []; 31 define( 'FCPFSC_CM_VER', '5.65.13' ); // codemirror version 32 32 33 // get the post type 34 $qo = get_queried_object(); 35 $post_type = ''; 36 if ( is_object( $qo ) ) { 37 if ( get_class( $qo ) === 'WP_Post_Type' ) { 38 $post_type = $qo->name; 39 } 40 if ( get_class( $qo ) === 'WP_Post' ) { 41 $post_type = $qo->post_type; 42 } 33 34 require FCPFSC_DIR . 'inc/functions.php'; 35 require FCPFSC_DIR . 'inc/apply/main.php'; 36 require FCPFSC_DIR . 'inc/admin/main.php'; 37 38 39 // install / uninstall the plugin 40 register_activation_hook( __FILE__, function() use ($meta_close_by_default) { 41 // store the non-first-screen css (rest-css) 42 wp_mkdir_p( FCPFSC_REST_DIR ); 43 44 // close secondary meta boxes by default 45 $admins = get_users(['role' => 'administrator']); 46 foreach ($admins as $admin) { 47 // set the default state for the specified metaboxes 48 update_user_meta($admin->ID, 'closedpostboxes_'.FCPFSC_SLUG, $meta_close_by_default); 43 49 } 44 50 45 if ( is_singular( $post_type ) ) { 46 // get css by post id 47 if ( $css_id = get_post_meta( $qo->ID, FCPFSC_PREF.'id' )[0] ?? null ) { 48 $csss[] = $css_id; 49 } 50 51 // get css by post-type 52 //if ( (int) get_option('page_on_front') !== (int) $qo->ID ) { // exclude the front-page, as they stand out, mostly 53 $csss = array_merge( $csss, get_css_ids( FCPFSC_PREF.'post-types', $post_type ) ); 54 //} 55 } 56 if ( is_home() || is_archive() && ( !$post_type || $post_type === 'page' ) ) { 57 // get css for blog 58 $csss = array_merge( $csss, get_css_ids( FCPFSC_PREF.'post-archives', 'blog' ) ); 59 } 60 if ( is_post_type_archive( $post_type ) ) { 61 // get css for custom post type archive 62 $csss = array_merge( $csss, get_css_ids( FCPFSC_PREF.'post-archives', $post_type ) ); 63 } 64 65 if ( empty( $csss ) ) { return; } 66 67 // filter by post_status, post_type, development-mode 68 $csss = filter_csss( $csss ); 69 70 // filter by exclude 71 if ( $css_exclude = get_post_meta( $qo->ID, FCPFSC_PREF.'id-exclude' )[0] ?? null ) { 72 $csss = array_values( array_diff( $csss, [ $css_exclude ] ) ); 73 } 74 75 if ( empty( $csss ) ) { return; } 76 77 // print styles 78 wp_register_style( FCPFSC_FRONT_PREF, false ); 79 wp_enqueue_style( FCPFSC_FRONT_PREF ); 80 wp_add_inline_style( FCPFSC_FRONT_PREF, get_css_contents_filtered( $csss ) ); 81 82 83 // deregister existing styles 84 $deregister = function() use ( $csss ) { 85 list( $styles, $scripts ) = get_to_deregister( $csss ); 86 87 $deregister = function($list, $ss) { 88 if ( empty( $list ) ) { return; } 89 90 if ( in_array( '*', $list ) ) { // get all registered 91 $global = 'wp_'.$ss.'s'; 92 global $$global; 93 94 $list = array_map( function( $el ) { 95 $name = $el->handle; 96 if ( strpos( $name, FCPFSC_FRONT_PREF ) === 0 ) { return ''; } 97 return $name ?? ''; 98 }, (array) $$global->registered ?? [] ); 99 } 100 101 $func = 'wp_deregister_'.$ss; 102 foreach ( $list as $v ) { 103 $func( $v ); 104 } 105 }; 106 107 $deregister( $styles, 'style' ); 108 $deregister( $scripts, 'script' ); 109 }; 110 add_action( 'wp_enqueue_scripts', $deregister, 100000 ); 111 add_action( 'wp_footer', $deregister, 1 ); 112 add_action( 'wp_footer', $deregister, 11 ); 113 114 115 // enqueue the rest-screen styles 116 add_action( 'wp_enqueue_scripts', function() use ( $csss ) { 117 foreach ( $csss as $id ) { 118 $path = '/' . basename( __DIR__ ) . '/style-'.$id.'.css'; 119 if ( !is_file( wp_upload_dir()['basedir'] . $path ) ) { continue; } 120 wp_enqueue_style( 121 FCPFSC_FRONT_PREF.'-css-rest-' . $id, 122 wp_upload_dir()['baseurl'] . $path, 123 [], 124 filemtime( wp_upload_dir()['basedir'] . $path ), 125 'all' 126 ); 127 } 128 129 // defer loading 130 $defer_csss = get_to_defer( $csss ); 131 add_filter( 'style_loader_tag', function ($tag, $handle) use ($defer_csss) { 132 if ( strpos( $handle, FCPFSC_FRONT_PREF.'-css-rest-' ) === false || !in_array( str_replace( FCPFSC_FRONT_PREF.'-css-rest-', '', $handle ), $defer_csss ) ) { 133 return $tag; 134 } 135 return 136 str_replace( [ 'rel="stylesheet"', "rel='stylesheet'" ], [ 137 'rel="preload" as="style" onload="this.onload=null;this.rel=\'stylesheet\'"', 138 "rel='preload' as='style' onload='this.onload=null;this.rel=\"stylesheet\"'" 139 ], $tag ). 140 '<noscript>'.str_replace( // remove doubling id 141 [ ' id="'.$handle.'-css"', " id='".$handle."-css'" ], 142 [ '', '' ], 143 substr( $tag, 0, -1 ) 144 ).'</noscript>' . "\n" 145 ; 146 }, 10, 2); 147 }, 10 ); 148 149 }, 7 ); 150 151 register_activation_hook( __FILE__, function() { 152 wp_mkdir_p( wp_upload_dir()['basedir'] . '/' . basename( __DIR__ ) ); 51 // 153 52 register_uninstall_hook( __FILE__, 'FCP\FirstScreenCSS\delete_the_plugin' ); 154 53 } ); 155 54 156 // admin post type for css-s 157 add_action( 'init', function() { 158 $shorter = [ 159 'name' => 'First Screen CSS', 160 'plural' => 'First Screen CSS', 161 'public' => false, 162 ]; 163 $labels = [ 164 'name' => $shorter['plural'], 165 'singular_name' => $shorter['name'], 166 'menu_name' => $shorter['plural'], 167 'all_items' => 'View All ' . $shorter['plural'], 168 'archives' => 'All ' . $shorter['plural'], 169 'view_item' => 'View ' . $shorter['name'], 170 'add_new' => 'Add New', 171 'add_new_item' => 'Add New ' . $shorter['name'], 172 'edit_item' => 'Edit ' . $shorter['name'], 173 'update_item' => 'Update ' . $shorter['name'], 174 'search_items' => 'Search ' . $shorter['name'], 175 'not_found' => $shorter['name'] . ' Not Found', 176 'not_found_in_trash' => $shorter['name'] . ' Not found in Trash', 177 ]; 178 $args = [ 179 'label' => $shorter['name'], 180 'description' => 'CSS to print before everything', 181 'labels' => $labels, 182 'supports' => ['title', 'editor'], 183 'hierarchical' => false, 184 'public' => $shorter['public'], 185 'show_in_rest' => false, 186 'show_ui' => true, 187 'show_in_menu' => true, 188 'show_in_nav_menus' => false, 189 'show_in_admin_bar' => true, 190 'menu_position' => 29, 191 'menu_icon' => 'dashicons-money-alt', 192 'can_export' => true, 193 'has_archive' => false, 194 'exclude_from_search' => !$shorter['public'], 195 'publicly_queryable' => $shorter['public'], 196 'capabilities' => [ // only admins 197 'edit_post' => 'switch_themes', 198 'read_post' => 'switch_themes', 199 'delete_post' => 'switch_themes', 200 'edit_posts' => 'switch_themes', 201 'edit_others_posts' => 'switch_themes', 202 'delete_posts' => 'switch_themes', 203 'publish_posts' => 'switch_themes', 204 'read_private_posts' => 'switch_themes' 205 ] 206 ]; 207 register_post_type( FCPFSC_SLUG, $args ); 208 }); 209 210 // admin controls 211 add_action( 'add_meta_boxes', function() { 212 if ( !current_user_can( 'administrator' ) ) { return; } 213 214 add_meta_box( 215 FCPFSC_FRONT_PREF.'-css-bulk', 216 'Bulk apply', 217 'FCP\FirstScreenCSS\fcpfsc_meta_bulk_apply', 218 FCPFSC_SLUG, 219 'normal', 220 'high' 221 ); 222 add_meta_box( 223 FCPFSC_FRONT_PREF.'-css-disable', 224 'Disable enqueued', 225 'FCP\FirstScreenCSS\fcpfsc_meta_disable_styles', 226 FCPFSC_SLUG, 227 'normal', 228 'low' 229 ); 230 add_meta_box( 231 FCPFSC_FRONT_PREF.'-css-rest', 232 'The rest of CSS, which is a not-first-screen', 233 'FCP\FirstScreenCSS\fcpfsc_meta_rest_css', 234 FCPFSC_SLUG, 235 'normal', 236 'low' 237 ); 238 239 list( 'public' => $public_post_types ) = get_all_post_types(); 240 add_meta_box( 241 FCPFSC_FRONT_PREF.'-css', 242 'Select First Screen CSS', 243 'FCP\FirstScreenCSS\anypost_meta_select_fsc', 244 array_keys( $public_post_types ), 245 'side', 246 'low' 247 ); 248 }); 249 250 // disable timymce to textarea 251 add_filter( 'wp_editor_settings', function($settings, $editor_id) { 252 253 if ( $editor_id !== 'content' ) { return $settings; } 254 255 $screen = get_current_screen(); 256 if ( !isset( $screen ) || !is_object( $screen ) || $screen->post_type !== FCPFSC_SLUG ) { return $settings; } 257 258 $settings['tinymce'] = false; 259 $settings['quicktags'] = false; 260 $settings['media_buttons'] = false; 261 262 return $settings; 263 }, 10, 2 ); 264 265 // apply codemirror to the fields for CSS 266 add_action( 'admin_enqueue_scripts', function( $hook ) { 267 268 if ( !in_array( $hook, ['post.php', 'post-new.php'] ) ) { return; } 269 270 $screen = get_current_screen(); 271 if ( !isset( $screen ) || !is_object( $screen ) || $screen->post_type !== FCPFSC_SLUG ) { return; } 272 273 // remove wp-native codemirror 274 wp_deregister_script( 'code-editor' ); 275 wp_deregister_style( 'wp-codemirror' ); 276 277 global $wp_scripts; // remove cm_settings // priority 11 is for this part mostly - default is fine for the rest 278 $jquery_extra_core_data = $wp_scripts->get_data( 'jquery-core', 'data' ); 279 $jquery_extra_core_data = preg_replace( '/var cm_settings(?:.*?);/', '', $jquery_extra_core_data ); 280 $wp_scripts->add_data( 'jquery-core', 'data', $jquery_extra_core_data ); 281 //echo '***'; print_r( $wp_scripts ); exit; 282 283 // codemirror core 284 wp_enqueue_script( 'codemirror', plugin_dir_url(__FILE__) . 'assets/codemirror/codemirror.js', ['jquery'], FCPFSC_CM_VER ); 285 wp_enqueue_style( 'codemirror', plugin_dir_url(__FILE__) . 'assets/codemirror/codemirror.css', [], FCPFSC_CM_VER ); 286 287 // codemirror addons 288 wp_enqueue_script( 'codemirror-mode-css', plugin_dir_url(__FILE__) . 'assets/codemirror/mode/css/css.js', ['codemirror'], FCPFSC_CM_VER ); 289 wp_enqueue_script( 'codemirror-addon-active-line', plugin_dir_url(__FILE__) . 'assets/codemirror/addon/selection/active-line.js', ['codemirror'], FCPFSC_CM_VER ); 290 wp_enqueue_script( 'codemirror-addon-placeholder', plugin_dir_url(__FILE__) . 'assets/codemirror/addon/display/placeholder.js', ['codemirror'], FCPFSC_CM_VER ); 291 wp_enqueue_script( 'codemirror-formatting', plugin_dir_url(__FILE__) . 'assets/codemirror/util/formatting.js', ['codemirror'], '2.38+' ); 292 293 // codemirror init 294 wp_enqueue_script( 'codemirror-init', plugin_dir_url(__FILE__) . '/assets/codemirror/init.js', ['codemirror'], FCPFSC_VER ); 295 296 // overall styling 297 wp_enqueue_style( 'codemirror-style', plugin_dir_url(__FILE__) . 'assets/style.css', ['codemirror'], FCPFSC_VER ); 298 }, 11 ); 299 300 // save meta data 301 add_action( 'save_post', function( $postID ) { 302 303 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } 304 if ( empty( $_POST[ FCPFSC_PREF.'nonce' ] ) || !wp_verify_nonce( $_POST[ FCPFSC_PREF.'nonce' ], FCPFSC_PREF.'nonce' ) ) { return; } 305 if ( !current_user_can( 'administrator' ) ) { return; } 306 307 $post = get_post( $postID ); 308 if ( $post->post_type === 'revision' ) { return; } // update_post_meta fixes the id to the parent, but id can be used before 55 function delete_the_plugin() { 56 return true; 57 // delete the rest-storage // deprecated as it doesn't restore after re-install the plugin 58 /* 59 $dir = FCPFSC_REST_DIR; 60 array_map( 'unlink', glob( FCPFSC_REST_DIR . '/*' ) ); 61 rmdir( FCPFSC_REST_DIR ); 62 //*/ 63 // ++ add the setting to delete all the plugin's leftovers 64 } 309 65 310 66 311 if ( $post->post_type === FCPFSC_SLUG ) { 312 $fields = [ 'post-types', 'post-archives', 'development-mode', 'deregister-style-names', 'deregister-script-names', 'rest-css', 'rest-css-defer' ]; 313 } else { 314 $fields = [ 'id', 'id-exclude' ]; 315 } 316 317 // exception for the rest-css ++ improve when having different function for processing values 318 $file = wp_upload_dir()['basedir'] . '/' . basename( __DIR__ ) . '/style-'.$postID.'.css'; 319 @unlink( $file ); 320 321 foreach ( $fields as $f ) { 322 $f = FCPFSC_PREF . $f; 323 if ( empty( $_POST[ $f ] ) || empty( $new_value = sanitize_meta( $_POST[ $f ], $f, $postID ) ) ) { 324 delete_post_meta( $postID, $f ); 325 continue; 326 } 327 update_post_meta( $postID, $f, $new_value ); 328 } 329 }); 330 331 // filter css 332 add_filter( 'wp_insert_post_data', function($data, $postarr) { 333 // ++ if can admin? 334 if ( $data['post_type'] !== FCPFSC_SLUG ) { return $data; } 335 clear_errors( $postarr['ID'] ); 336 337 // empty is not an error 338 if ( trim( $data['post_content'] ) === '' ) { 339 $data['post_content_filtered'] = ''; 340 return $data; 341 } 342 343 $errors = []; 344 345 list( $errors, $filtered ) = sanitize_css( wp_unslash( $data['post_content'] ) ); 346 347 // right 348 if ( empty( $errors ) ) { 349 $data['post_content_filtered'] = wp_slash( css_minify( $filtered ) ); // slashes are stripped again right after 350 return $data; 351 } 352 353 // wrong 354 $data['post_content_filtered'] = ''; 355 save_errors( $errors, $postarr['ID'], '#postdivrich' ); // ++set to draft on any error? 356 return $data; 357 358 }, 10, 2 ); 359 360 // message on errors in css 361 add_action( 'admin_notices', function () { 362 363 $screen = get_current_screen(); 364 if ( !isset( $screen ) || !is_object( $screen ) || $screen->post_type !== FCPFSC_SLUG || $screen->base !== 'post' ) { return; } 365 366 global $post; 367 if ( empty( $errors = get_post_meta( $post->ID, FCPFSC_PREF.'_post_errors' )[0] ?? '' ) ) { return; } 368 369 array_unshift( $errors['errors'], '<strong>This CSS-post can not be published due to the following errors:</strong>' ); 370 ?> 371 372 <div class="notice notice-error"><ul> 373 <?php array_walk( $errors['errors'], function($a) { 374 ?> 375 <li><?php echo wp_kses( $a, ['strong' => [], 'em' => []] ) ?></li> 376 <?php }) ?> 377 </ul></div> 378 379 <style type="text/css"><?php echo implode( ', ', $errors['selectors']) ?>{box-shadow:-3px 0px 0px 0px #d63638}</style> 380 381 <?php 382 }); 383 384 385 // functions ------------------------------------- 386 387 function sanitize_meta( $value, $field, $postID ) { 388 389 $field = ( strpos( $field, FCPFSC_PREF ) === 0 ) ? substr( $field, strlen( FCPFSC_PREF ) ) : $field; 390 391 $onoff = function($value) { 392 return $value[0] === 'on' ? ['on'] : []; 393 }; 394 395 switch( $field ) { 396 case ( 'post-types' ): 397 return array_intersect( $value, array_keys( get_all_post_types()['public'] ) ); 398 break; 399 case ( 'post-archives' ): 400 return array_intersect( $value, array_keys( get_all_post_types()['archive'] ) ); 401 break; 402 case ( 'development-mode' ): 403 return $onoff( $value ); 404 break; 405 case ( 'deregister-style-names' ): 406 return $value; // ++preg_replace not letters ,space-_, lowercase?, 407 break; 408 case ( 'deregister-script-names' ): 409 return $value; // ++preg_replace not letters ,space-_, lowercase?, 410 break; 411 case ( 'rest-css' ): 412 413 list( $errors, $filtered ) = sanitize_css( wp_unslash( $value ) ); //++ move it all to a separate filter / actions, organize better with errors!! 414 $file = wp_upload_dir()['basedir'] . '/' . basename( __DIR__ ) . '/style-'.$postID.'.css'; 415 416 // correct 417 if ( empty( $errors ) ) { 418 file_put_contents( $file, css_minify( $filtered ) ); //++ add the permission error 419 return $value; 420 } 421 // wrong 422 unlink( $file ); 423 save_errors( $errors, $postID, '#first-screen-css-rest > .inside' ); 424 return $value; 425 break; 426 case ( 'rest-css-defer' ): 427 return $onoff( $value ); 428 break; 429 case ( 'id' ): 430 if ( !is_numeric( $value ) ) { return ''; } // ++to a function 431 if ( !( $post = get_post( $value ) ) || $post->post_type !== FCPFSC_SLUG ) { return ''; } 432 return $value; 433 break; 434 case ( 'id-exclude' ): 435 if ( !is_numeric( $value ) ) { return ''; } 436 if ( !( $post = get_post( $value ) ) || $post->post_type !== FCPFSC_SLUG ) { return ''; } 437 return $value; 438 break; 439 } 440 441 return ''; 442 } 443 444 function sanitize_css($css) { 445 446 $errors = []; 447 448 // try to escape tags inside svg with url-encoding 449 if ( strpos( $css, '<' ) !== false && preg_match( '/<\/?\w+/', $css ) ) { 450 // the idea is taken from https://github.com/yoksel/url-encoder/ 451 $svg_sanitized = preg_replace_callback( '/url\(\s*(["\']*)\s*data:\s*image\/svg\+xml(.*)\\1\s*\)/', function($m) { 452 return 'url('.$m[1].'data:image/svg+xml' 453 .preg_replace_callback( '/[\r\n%#\(\)<>\?\[\]\\\\^\`\{\}\|]+/', function($m) { 454 return urlencode( $m[0] ); 455 }, urldecode( $m[2] ) ) 456 .$m[1].')'; 457 }, $css ); 458 459 if ( $svg_sanitized !== null ) { 460 $css = $svg_sanitized; 461 } 462 } 463 // if tags still exist, forbid that 464 // the idea is taken from WP_Customize_Custom_CSS_Setting::validate as well as the translation 465 if ( strpos( $css, '<' ) !== false && preg_match( '/<\/?\w+/', $css ) ) { 466 $errors['tags'] = 'HTML ' . __( 'Markup is not allowed in CSS.' ); 467 } 468 469 // ++strip <?php, <!--?? 470 // ++maybe add parser sometime later 471 // ++safecss_filter_attr($css)?? 472 473 return [$errors, $css]; 474 } 475 476 function save_errors($errors, $postID, $selector = '') { 477 static $errors_list = [ 'errors' => [], 'selectors' => [] ]; 478 479 $errors = (array) $errors; 480 481 $errors_list['errors'] = array_merge( $errors_list['errors'], $errors ); 482 $errors_list['selectors'][] = $selector; // errors override by associative key, numeric add, but selectors only add for now 483 update_post_meta( $postID, FCPFSC_PREF.'_post_errors', $errors_list ); 484 } 485 function clear_errors($postID) { 486 delete_post_meta( $postID, FCPFSC_PREF.'_post_errors' ); 487 } 488 489 function checkboxes($a) { 490 ?> 491 <fieldset 492 id="<?php echo esc_attr( FCPFSC_PREF . $a->name ) ?>" 493 class="<?php echo isset( $a->className ) ? esc_attr( $a->className ) : '' ?>"><?php 494 495 foreach ( (array) $a->options as $k => $v ) { 496 $checked = is_array( $a->value ) && in_array( $k, $a->value ); 497 ?><label> 498 <input type="checkbox" 499 name="<?php echo esc_attr( FCPFSC_PREF . $a->name ) ?>[]" 500 value="<?php echo esc_attr( $k ) ?>" 501 <?php echo $checked ? 'checked' : '' ?> 502 > 503 <span><?php echo esc_html( $v ) ?></span> 504 </label><?php } ?> 505 </fieldset> 506 <?php 507 } 508 509 function select($a) { 510 ?> 511 <select 512 name="<?php echo esc_attr( FCPFSC_PREF . $a->name ) ?>" 513 id="<?php echo esc_attr( FCPFSC_PREF . $a->name ) ?>" 514 class="<?php echo isset( $a->className ) ? esc_attr( $a->className ) : '' ?>"><?php 515 516 if ( isset( $a->placeholder ) ) { ?> 517 <option value=""><?php echo esc_html( $a->placeholder ) ?></option> 518 <?php } ?> 519 520 <?php foreach ( $a->options as $k => $v ) { ?> 521 <option 522 value="<?php echo esc_attr( $k ) ?>" 523 <?php echo isset( $a->value ) && $a->value == $k ? 'selected' : '' ?> 524 ><?php echo esc_html( $v ) ?></option> 525 <?php } ?> 526 </select> 527 <?php 528 } 529 530 function input($a) { 531 ?> 532 <input type="text" 533 name="<?php echo esc_attr( FCPFSC_PREF . $a->name ) ?>" 534 id="<?php echo esc_attr( FCPFSC_PREF . $a->name ) ?>" 535 placeholder="<?php echo isset( $a->placeholder ) ? esc_attr( $a->placeholder ) : '' ?>" 536 value="<?php echo isset( $a->value ) ? esc_attr( $a->value ) : '' ?>" 537 class="<?php echo isset( $a->className ) ? esc_attr( $a->className ) : '' ?>" 538 /> 539 <?php 540 } 541 542 function textarea($a) { 543 ?> 544 <textarea 545 name="<?php echo esc_attr( FCPFSC_PREF . $a->name ) ?>" 546 id="<?php echo esc_attr( FCPFSC_PREF . $a->name ) ?>" 547 rows="<?php echo isset( $a->rows ) ? esc_attr( $a->rows ) : '10' ?>" cols="<?php echo isset( $a->cols ) ? esc_attr( $a->cols ) : '50' ?>" 548 placeholder="<?php echo isset( $a->placeholder ) ? esc_attr( $a->placeholder ) : '' ?>" 549 class="<?php echo isset( $a->className ) ? esc_attr( $a->className ) : '' ?>" 550 style="<?php echo isset( $a->style ) ? esc_attr( $a->style ) : '' ?>" 551 ><?php 552 echo esc_textarea( isset( $a->value ) ? $a->value : '' ) 553 ?></textarea> 554 <?php 555 } 556 557 function filter_csss( $ids ) { 558 559 if ( empty( $ids ) ) { return []; } 560 561 global $wpdb; 562 563 // filter by post_status & post_type 564 $filtered_ids = $wpdb->get_col( $wpdb->prepare(' 565 566 SELECT `ID` 567 FROM `'.$wpdb->posts.'` 568 WHERE `post_status` = %s AND `post_type` = %s AND `ID` IN ( '.implode( ',', array_fill( 0, count( $ids ), '%s' ), ).' ) 569 570 ', array_merge( [ 'publish', FCPFSC_SLUG ], $ids ) ) ); 571 572 // filter by development mode 573 if ( current_user_can( 'administrator' ) ) { return $filtered_ids; } 574 575 $dev_mode = $wpdb->get_col( $wpdb->remove_placeholder_escape( $wpdb->prepare(' 576 577 SELECT `post_id` 578 FROM `'.$wpdb->postmeta.'` 579 WHERE `meta_key` = %s AND `meta_value` = %s AND `post_id` IN ( '.implode( ',', array_fill( 0, count( $ids ), '%s' ), ).' ) 580 581 ', array_merge( [ FCPFSC_PREF.'development-mode', serialize(['on']) ], $ids ) ) ) ); 582 583 584 return array_values( array_diff( $filtered_ids, $dev_mode ) ); 585 } 586 587 function get_css_ids( $key, $type = 'post' ) { 588 589 global $wpdb; 590 591 $ids = $wpdb->get_col( $wpdb->remove_placeholder_escape( $wpdb->prepare(' 592 593 SELECT `post_id` 594 FROM `'.$wpdb->postmeta.'` 595 WHERE `meta_key` = %s AND `meta_value` LIKE %s 596 597 ', $key, $wpdb->add_placeholder_escape( '%"'.$type.'"%' ) ) ) ); 598 599 return $ids; 600 } 601 602 function get_to_defer( $ids ) { 603 604 global $wpdb; 605 606 $defer_ids = $wpdb->get_col( $wpdb->remove_placeholder_escape( $wpdb->prepare(' 607 608 SELECT `post_id` 609 FROM `'.$wpdb->postmeta.'` 610 WHERE `meta_key` = %s AND `meta_value` = %s AND `post_id` IN ( '.implode( ',', array_fill( 0, count( $ids ), '%s' ), ).' ) 611 612 ', array_merge( [ FCPFSC_PREF.'rest-css-defer', serialize(['on']) ], $ids ) ) ) ); 613 614 615 return $defer_ids; 616 } 617 618 function get_css_contents_filtered( $ids ) { // ++add proper ordering 619 620 if ( empty( $ids ) ) { return; } 621 622 global $wpdb; 623 624 $metas = $wpdb->get_col( $wpdb->prepare(' 625 626 SELECT `post_content_filtered` 627 FROM `'.$wpdb->posts.'` 628 WHERE `ID` IN ( '.implode( ',', array_fill( 0, count( $ids ), '%s' ), ).' ) 629 630 ', $ids ) ); 631 632 return implode( '', $metas ); 633 } 634 635 function get_to_deregister( $ids ) { 636 637 if ( empty( $ids ) ) { return []; } 638 639 global $wpdb; 640 641 $wpdb->query( $wpdb->remove_placeholder_escape( $wpdb->prepare(' 642 643 SELECT 644 IF ( STRCMP( `meta_key`, %s ) = 0, `meta_value`, "" ) AS "styles", 645 IF ( STRCMP( `meta_key`, %s ) = 0, `meta_value`, "" ) AS "scripts" 646 FROM `'.$wpdb->postmeta.'` 647 WHERE ( `meta_key` = %s OR `meta_key` = %s ) AND `post_id` IN ( '.implode( ',', array_fill( 0, count( $ids ), '%s' ), ).' ) 648 649 ', array_merge( 650 [ FCPFSC_PREF.'deregister-style-names', FCPFSC_PREF.'deregister-script-names', FCPFSC_PREF.'deregister-style-names', FCPFSC_PREF.'deregister-script-names' ], 651 $ids 652 ) ) ) ); 653 654 $clear = function($a) { return array_values( array_unique( array_filter( array_map( 'trim', explode( ',', implode( ', ', $a ) ) ) ) ) ); }; 655 656 $styles = $clear( $wpdb->get_col( null, 0 ) ); 657 $scripts = $clear( $wpdb->get_col( null, 1 ) ); 658 659 return [ $styles, $scripts ]; 660 } 661 662 function get_all_post_types() { 663 static $all = [], $public = [], $archive = []; 664 665 if ( !empty( $public ) ) { return [ 'all' => $all, 'public' => $public, 'archive' => $archive ]; } 666 667 $all = get_post_types( [], 'objects' ); 668 $public = []; 669 $archive = []; 670 $archive[ 'blog' ] = 'Blog'; 671 usort( $all, function($a,$b) { return strcasecmp( $a->label, $b->label ); }); 672 foreach ( $all as $type ) { 673 $type->name = isset( $type->rewrite->slug ) ? $type->rewrite->slug : $type->name; 674 if ( $type->has_archive ) { 675 $archive[ $type->name ] = $type->label; 676 } 677 if ( $type->public ) { 678 //if ( $type->name === 'page' ) { $type->label .= ' (except Front Page)'; } 679 $public[ $type->name ] = $type->label; 680 } 681 } 682 683 return [ 'all' => $all, 'public' => $public, 'archive' => $archive ]; 684 685 } 686 687 function css_minify($css) { 688 $preg_replace = function($regexp, $replace, $string) { // avoid null result so that css still works even though not fully minified 689 return preg_replace( $regexp, $replace, $string ) ?: $string . '/* --- failed '.$regexp.', '.$replace.' */'; 690 }; 691 $css = $preg_replace( '/\s+/', ' ', $css ); // one-line & only single speces 692 $css = $preg_replace( '/ ?\/\*(?:.*?)\*\/ ?/', '', $css ); // remove comments 693 $css = $preg_replace( '/ ?([\{\};:\>\~\+]) ?/', '$1', $css ); // remove spaces 694 $css = $preg_replace( '/\+(\d)/', ' + $1', $css ); // restore spaces in functions 695 $css = $preg_replace( '/(?:[^\}]*)\{\}/', '', $css ); // remove empty properties 696 $css = str_replace( [';}', '( ', ' )'], ['}', '(', ')'], $css ); // remove last ; and spaces 697 // ++ should also remove 0 from 0.5, but not from svg-s? 698 // ++ try replacing ', ' with ',' 699 // ++ remove space between %3E %3C and before %3E and /%3E 700 return trim( $css ); 701 }; 702 703 // meta boxes 704 function fcpfsc_meta_bulk_apply() { 705 global $post; 706 707 // get post types to print options 708 list( 'public' => $public_post_types, 'archive' => $archives_post_types ) = get_all_post_types(); 709 710 ?><p><strong>Apply to the following post types</strong></p><?php 711 712 checkboxes( (object) [ 713 'name' => 'post-types', 714 'options' => $public_post_types, 715 'value' => get_post_meta( $post->ID, FCPFSC_PREF.'post-types' )[0] ?? '', 716 ]); 717 718 ?><p><strong>Apply to the Archive pages of the following post types</strong></p><?php 719 720 checkboxes( (object) [ 721 'name' => 'post-archives', 722 'options' => $archives_post_types, 723 'value' => get_post_meta( $post->ID, FCPFSC_PREF.'post-archives' )[0] ?? '', 724 ]); 725 726 ?> 727 <p>You can apply this styling to a separate post. Every public post type has a special select box in the right sidebar to pick this or any other first-screen-css.</p> 728 <p>You can grab the first screen css of a page with the script: <a href="https://github.com/VVolkov833/first-screen-css-grabber" target="_blank" rel="noopener">github.com/VVolkov833/first-screen-css-grabber</a></p> 729 <?php 730 731 checkboxes( (object) [ 732 'name' => 'development-mode', 733 'options' => ['on' => 'Development mode (apply only if the post is visited as the admin)'], 734 'value' => get_post_meta( $post->ID, FCPFSC_PREF.'development-mode' )[0] ?? '', 735 ]); 736 737 ?> 738 <input type="hidden" name="<?php echo esc_attr( FCPFSC_PREF ) ?>nonce" value="<?= esc_attr( wp_create_nonce( FCPFSC_PREF.'nonce' ) ) ?>"> 739 <?php 740 } 741 742 function fcpfsc_meta_disable_styles() { 743 global $post; 744 745 ?><p><strong>List the names of STYLES to deregister</strong></p><?php 746 747 input( (object) [ 748 'name' => 'deregister-style-names', 749 'placeholder' => 'my-theme-style, some-plugin-style', 750 'value' => get_post_meta( $post->ID, FCPFSC_PREF.'deregister-style-names' )[0] ?? '', 751 ]); 752 ?>Separate names by comma. To deregister all styles set *<?php 753 754 ?><p><strong>List the names of SCRIPTS to deregister</strong></p><?php 755 756 input( (object) [ 757 'name' => 'deregister-script-names', 758 'placeholder' => 'my-theme-script, some-plugin-script', 759 'value' => get_post_meta( $post->ID, FCPFSC_PREF.'deregister-script-names' )[0] ?? '', 760 ]); 761 ?>Separate names by comma. To deregister all scripts set *<?php 762 763 } 764 765 function fcpfsc_meta_rest_css() { 766 global $post; 767 768 textarea( (object) [ 769 'name' => 'rest-css', 770 'value' => get_post_meta( $post->ID, FCPFSC_PREF.'rest-css' )[0] ?? '', 771 'style' => 'height:300px', 772 ]); 773 774 checkboxes( (object) [ 775 'name' => 'rest-css-defer', 776 'options' => ['on' => 'Defer the not-first-screen CSS (avoid render-blicking)'], 777 'value' => get_post_meta( $post->ID, FCPFSC_PREF.'rest-css-defer' )[0] ?? '', 778 ]); 779 } 780 781 function anypost_meta_select_fsc() { 782 global $post; 783 784 // get css post types 785 $css_posts0 = get_posts([ 786 'post_type' => FCPFSC_SLUG, 787 'orderby' => 'post_title', 788 'order' => 'ASC', 789 'post_status' => ['any', 'active'], 790 'posts_per_page' => -1, 791 ]); 792 $css_posts = []; 793 foreach( $css_posts0 as $v ){ 794 $css_posts[ $v->ID ] = $v->post_title ? $v->post_title : __( '(no title)' ); 795 } 796 797 select( (object) [ 798 'name' => 'id', 799 'placeholder' => '------', 800 'options' => $css_posts, 801 'value' => get_post_meta( $post->ID, FCPFSC_PREF.'id' )[0] ?? '', 802 ]); 803 804 ?><p> </p><p><strong>Exclude CSS</strong></p><?php 805 select( (object) [ 806 'name' => 'id-exclude', 807 'placeholder' => '------', 808 'options' => $css_posts, 809 'value' => get_post_meta( $post->ID, FCPFSC_PREF.'id-exclude' )[0] ?? '', 810 ]); 811 812 ?> 813 <input type="hidden" name="<?php echo esc_attr( FCPFSC_PREF ) ?>nonce" value="<?= esc_attr( wp_create_nonce( FCPFSC_PREF.'nonce' ) ) ?>"> 814 <?php 815 } 816 817 function delete_the_plugin() { 818 $dir = wp_upload_dir()['basedir'] . '/' . basename( __DIR__ ); 819 array_map( 'unlink', glob( $dir . '/*' ) ); 820 rmdir( $dir ); 821 } 822 823 // new version set 824 // svn upload 825 826 // ++refactor - split in files 827 // ++add the @bigger height@ button and save new height in local storage 67 // ++add the option to switch to inline 68 // ++add the option to defer loading 69 // ++autopick the names by url or the instruction how to 70 // ++add the @bigger height@ button and save new height in local storage or user settings? 828 71 // ++switch selects to checkboxes or multiples 829 72 // ++maybe limit the id-exclude to the fitting post types -
fcp-first-screen-css/trunk/readme.txt
r2939014 r2973591 3 3 Tags: inline, css, firstscreen, style, web vitals, cls, fcp, defer, dequeue, deregister 4 4 Requires at least: 5.8 5 Tested up to: 6. 25 Tested up to: 6.3 6 6 Requires PHP: 7.4 7 Stable tag: 1. 5.028 Author: Firmcatalyst, Vadim Volkov7 Stable tag: 1.6 8 Author: Vadim Volkov, Firmcatalyst 9 9 Author URI: https://firmcatalyst.com 10 10 License: GPL v3 or later 11 11 License URI: http://www.gnu.org/licenses/gpl-3.0.html 12 12 13 F CP First Screen CSS inline13 First Screen CSS & Settings 14 14 15 15 == Description == 16 16 17 Insert the inline CSS to the head tag of a website, disable existing styles and scripts, defer loading of not-first-screen style, apply to a single post or bulk.17 Insert the inline CSS into your website's head tag. You can choose to inline, defer, or de-register existing styles and scripts. Additionally, you can add custom non-inline styles. Apply everything individually, by post-type, or for archives. 18 18 19 19 = Features = 20 20 21 * Apply to any single post / page / custom post-type22 * Apply to all posts of a particular post-type23 * Apply to the blog or any post-type archive24 * It minifies the CSS before printing25 * Deregister styles and scripts: all or by name26 * A pply the not-first-screen CSS separately27 * Defer the not-first-screen CSS loading21 * Full control over your website's CSS 22 * Apply changes to individual posts/pages/custom post types 23 * Apply changes to all posts of a specific type 24 * Apply changes to the blog or any post-type archive 25 * Inline, defer, or de-register Styles or JS 26 * Automatic CSS minification 27 * Edit CSS easily with the CodeMirror visual editor 28 28 29 29 = Demo = … … 33 33 = Usage = 34 34 35 * Install and activate the plugin 36 * Go to the "First Screen CSS" menu item in the left sidebar of your wp-admin 37 * Add New, insert your CSS 38 * Pick where to apply and other options 35 * Install and activate the plugin. 36 * Navigate to "First Screen CSS" in the left sidebar of your wp-admin. 37 * Click "Add New." 38 * Input your CSS or adjust the settings. 39 * Choose where to apply the changes and publish. 39 40 40 41 == Installation == … … 45 46 == Development == 46 47 47 You can modify the code for your needs, or suggest improvemens on [GitHub](https://github.com/VVolkov833/first-screen-css). It is pretty transparent and well-commented.48 You can customize the code to suit your requirements or suggest improvements on [GitHub](https://github.com/VVolkov833/first-screen-css). The code is transparent and easy to understand. 48 49 49 50 == Frequently Asked Questions == 50 51 51 Waiting for your questions, which you can ask [here](https://firmcatalyst.com/contact/) or via GitHub.52 Feel free to ask your questions [here](https://firmcatalyst.com/contact/) or through GitHub.. 52 53 53 54 == Upgrade Notice == 55 56 = 1.6 = 57 58 * Added the options to inline, defer and de-register CSS and JS 59 * Enhanced the CodeMirror editor for CSS manipulation 60 * Improved performance handling heavy CSS 61 * Enhanced descriptions and added simplified instructions 54 62 55 63 = 1.5 =
Note: See TracChangeset
for help on using the changeset viewer.