Plugin Directory

Changeset 2973591


Ignore:
Timestamp:
10/01/2023 07:59:48 PM (18 months ago)
Author:
firmcatalyst
Message:

Version 1.6 ready

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 # FCP First Screen CSS
     1# First Screen CSS & Settings
    22
    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.
     3Insert 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.
    44
    55## Features
    66
    7 * Apply to any single post / page / custom post-type
    8 * Apply to all posts of a particular post-type
    9 * Apply to the blog or any post-type archive
    10 * It minifies the CSS before printing
    11 * Deregister styles and scripts: all or by name
    12 * Apply the not-first-screen CSS separately
    13 * Defer the not-first-screen CSS loading
     7* 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
    1414
    1515## Demo
     
    1919## Usage
    2020
    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  
    2727    color: #888;
    2828}
     29[id^="first-screen-css"] input[type=text]::placeholder {
     30    color: #999;
     31}
    2932
    3033button[name=format], button[name=linewrap], button[name=infinity] {
     
    3235    z-index: 1;
    3336    top: 0;
    34     left: -15px;
     37    left: -20px;
    3538    font-family: monospace;
     39    transform: scale(0.85);
    3640}
    3741button[name=linewrap] {
     
    4145    top: 52px;
    4246}
     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
    18= 1.5 =
    29
  • fcp-first-screen-css/trunk/first-screen.php

    r2939014 r2973591  
    11<?php
    22/*
    3 Plugin Name: FCP First Screen CSS
    4 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.02
     3Plugin Name: First Screen CSS & Settings
     4Description: 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.
     5Version: 1.6
    66Requires at least: 5.8
    7 Tested up to: 6.1
     7Tested up to: 6.3
    88Requires PHP: 7.4
    9 Author: Firmcatalyst, Vadim Volkov
     9Author: Vadim Volkov, Firmcatalyst
    1010Author URI: https://firmcatalyst.com
    1111License: GPL v3 or later
     
    1616defined( 'ABSPATH' ) || exit;
    1717
    18 define( 'FCPFSC_DEV', false );
     18define( 'FCPFSC_DEV', true );
    1919define( 'FCPFSC_VER', get_file_data( __FILE__, [ 'ver' => 'Version' ] )[ 'ver' ] . ( FCPFSC_DEV ? time() : '' ) );
    2020
    2121define( 'FCPFSC_SLUG', 'fcpfsc' );
    2222define( 'FCPFSC_PREF', FCPFSC_SLUG.'-' );
    23 define( 'FCPFSC_FRONT_PREF', 'first-screen' );
     23define( 'FCPFSC_FRONT_NAME', 'first-screen' );
    2424
    25 define( 'FCPFSC_CM_VER', '5.65.13' );
     25define( 'FCPFSC_URL', plugin_dir_url( __FILE__ ) );
     26define( 'FCPFSC_DIR', plugin_dir_path( __FILE__ ) );
    2627
    27 // print the styles
    28 add_action( 'wp_enqueue_scripts', function() {
     28define( 'FCPFSC_REST_URL', wp_upload_dir()['baseurl'] . '/' . basename( FCPFSC_DIR ) );
     29define( 'FCPFSC_REST_DIR', wp_upload_dir()['basedir'] . '/' . basename( FCPFSC_DIR ) );
    2930
    30     // collect css-s to print on the post
    31     $csss = [];
     31define( 'FCPFSC_CM_VER', '5.65.13' ); // codemirror version
    3232
    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
     34require FCPFSC_DIR . 'inc/functions.php';
     35require FCPFSC_DIR . 'inc/apply/main.php';
     36require FCPFSC_DIR . 'inc/admin/main.php';
     37
     38
     39// install / uninstall the plugin
     40register_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);
    4349    }
    4450
    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    //
    15352    register_uninstall_hook( __FILE__, 'FCP\FirstScreenCSS\delete_the_plugin' );
    15453} );
    15554
    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
     55function 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}
    30965
    31066
    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>&nbsp;</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?
    82871// ++switch selects to checkboxes or multiples
    82972// ++maybe limit the id-exclude to the fitting post types
  • fcp-first-screen-css/trunk/readme.txt

    r2939014 r2973591  
    33Tags: inline, css, firstscreen, style, web vitals, cls, fcp, defer, dequeue, deregister
    44Requires at least: 5.8
    5 Tested up to: 6.2
     5Tested up to: 6.3
    66Requires PHP: 7.4
    7 Stable tag: 1.5.02
    8 Author: Firmcatalyst, Vadim Volkov
     7Stable tag: 1.6
     8Author: Vadim Volkov, Firmcatalyst
    99Author URI: https://firmcatalyst.com
    1010License: GPL v3 or later
    1111License URI: http://www.gnu.org/licenses/gpl-3.0.html
    1212
    13 FCP First Screen CSS inline
     13First Screen CSS & Settings
    1414
    1515== Description ==
    1616
    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.
     17Insert 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.
    1818
    1919= Features =
    2020
    21 * Apply to any single post / page / custom post-type
    22 * Apply to all posts of a particular post-type
    23 * Apply to the blog or any post-type archive
    24 * It minifies the CSS before printing
    25 * Deregister styles and scripts: all or by name
    26 * Apply the not-first-screen CSS separately
    27 * Defer the not-first-screen CSS loading
     21* 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
    2828
    2929= Demo =
     
    3333= Usage =
    3434
    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.
    3940
    4041== Installation ==
     
    4546== Development ==
    4647
    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.
     48You 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.
    4849
    4950== Frequently Asked Questions ==
    5051
    51 Waiting for your questions, which you can ask [here](https://firmcatalyst.com/contact/) or via GitHub.
     52Feel free to ask your questions [here](https://firmcatalyst.com/contact/) or through GitHub..
    5253
    5354== 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
    5462
    5563= 1.5 =
Note: See TracChangeset for help on using the changeset viewer.