Plugin Directory

Changeset 3672525


Ignore:
Timestamp:
08/30/2026 12:44:07 PM (8 weeks ago)
Author:
nazemi99
Message:

pembaruan versi 1.2 MasterSpeed Cache

Location:
masterspeed-cache/trunk
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • masterspeed-cache/trunk/languages/masterspeed-cache-en_US.po

    r3670502 r3672525  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: MasterSpeed Cache 1.1\n"
     5"Project-Id-Version: MasterSpeed Cache 1.2\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/masterspeed-cache\n"
    77"POT-Creation-Date: 2026-08-28T00:00:00+00:00\n"
  • masterspeed-cache/trunk/languages/masterspeed-cache.pot

    r3670502 r3672525  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: MasterSpeed Cache 1.1\n"
     5"Project-Id-Version: MasterSpeed Cache 1.2\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/masterspeed-cache\n"
    77"POT-Creation-Date: 2026-08-28T00:00:00+00:00\n"
  • masterspeed-cache/trunk/masterspeed-cache.php

    r3671949 r3672525  
    2424// KONSTANTA PLUGIN
    2525// ============================================================================
    26 define( 'NAZEMIWPSC_VERSION',        '1.1' );
     26define( 'NAZEMIWPSC_VERSION',        '1.2' );
    2727define( 'NAZEMIWPSC_PLUGIN_FILE',    __FILE__ );
    2828define( 'NAZEMIWPSC_PLUGIN_DIR',     plugin_dir_path( __FILE__ ) );
  • masterspeed-cache/trunk/modules/class-wpsc-image-optimizer.php

    r3670502 r3672525  
    12191219                $placeholders = implode( ',', array_fill( 0, count( self::SUPPORTED_MIMES ), '%s' ) );
    12201220
    1221                 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $placeholders murni string '%s' berulang sebanyak count(SUPPORTED_MIMES) (konstanta tetap di kelas ini, bukan input pengguna); semua nilai sesungguhnya tetap lewat larik kedua $wpdb->prepare() di bawah.
     1221                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $placeholders murni string '%s' berulang sebanyak count(SUPPORTED_MIMES) (konstanta tetap di kelas ini, bukan input pengguna); semua nilai sesungguhnya tetap lewat larik kedua $wpdb->prepare() di bawah. Dipakai gaya blok (disable/enable), bukan ignore satu baris, karena statement-nya berjalan lintas beberapa baris (baris query sesungguhnya beberapa baris di bawah baris ini) dan ignore satu baris tidak menjangkau sejauh itu.
    12221222                $ids = $wpdb->get_col(
    12231223                        $wpdb->prepare(
    … …  
    12331233                        )
    12341234                );
     1235                // phpcs:enable
    12351236
    12361237                return array_map( 'absint', $ids );
    … …  
    12451246                global $wpdb;
    12461247                $placeholders = implode( ',', array_fill( 0, count( self::SUPPORTED_MIMES ), '%s' ) );
    1247                 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $placeholders murni string '%s' berulang sebanyak count(SUPPORTED_MIMES) (konstanta tetap di kelas ini, bukan input pengguna); semua nilai sesungguhnya tetap lewat larik kedua $wpdb->prepare() di bawah.
     1248                // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $placeholders murni string '%s' berulang sebanyak count(SUPPORTED_MIMES) (konstanta tetap di kelas ini, bukan input pengguna); semua nilai sesungguhnya tetap lewat larik kedua $wpdb->prepare() di bawah. Dipakai gaya blok (disable/enable), bukan ignore satu baris, karena statement-nya berjalan lintas beberapa baris.
    12481249                return (int) $wpdb->get_var(
    12491250                        $wpdb->prepare(
    … …  
    12521253                        )
    12531254                );
     1255                // phpcs:enable
    12541256        }
    12551257
    … …  
    18481850                        global $wpdb;
    18491851                        $placeholders = implode( ',', array_fill( 0, count( $to_query ), '%s' ) );
    1850                         // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $placeholders murni string '%s' berulang sebanyak count($to_query); semua nilai sesungguhnya (termasuk isi $to_query) lewat larik kedua $wpdb->prepare() di bawah, bukan disisipkan mentah ke teks query.
     1852                        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $placeholders murni string '%s' berulang sebanyak count($to_query); semua nilai sesungguhnya (termasuk isi $to_query) lewat larik kedua $wpdb->prepare() di bawah, bukan disisipkan mentah ke teks query. Dipakai gaya blok (disable/enable), bukan ignore satu baris, karena baris query sesungguhnya ada di baris berikutnya (statement lintas beberapa baris).
    18511853                        $query = $wpdb->prepare(
    18521854                                "SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value IN ($placeholders)",
    18531855                                array_merge( array( '_nazemiwpsc_img_original_url' ), $to_query )
    18541856                        );
     1857                        // phpcs:enable
    18551858                        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- $query dibangun via $wpdb->prepare() tepat di atas (bukan konkatenasi manual); query kecil & tertarget (IN pada URL spesifik), bukan full-table scan.
    18561859                        $rows = $wpdb->get_results( $query );
  • masterspeed-cache/trunk/modules/class-wpsc-optimizer.php

    r3670502 r3672525  
    6363                        || ! empty( $this->options['js_defer'] )
    6464                        || ! empty( $this->options['google_fonts_optimizer'] )
    65                         || ! empty( $this->options['combine_assets'] )
    6665                        || ! empty( $this->options['asset_preload'] )
    6766                        || ! empty( $this->options['cdn_photon'] )
    … …  
    9796                        // jalankan ini kalau togglenya aktif, tanpa perlu cek toggle lain.
    9897                        add_action( 'wp_head', array( $this, 'inject_google_fonts_preconnect' ), 1 );
     98                }
     99
     100                if ( ! empty( $this->options['combine_assets'] ) ) {
     101                        // Lihat blok komentar panjang di atas combine_enqueued_styles()
     102                        // untuk penjelasan lengkap. Mekanisme ini SAMA SEKALI TIDAK
     103                        // butuh output buffering (beda dari kebanyakan fitur lain di
     104                        // kelas ini) — makanya 'combine_assets' TIDAK ada di
     105                        // $needs_buffer di atas. Prioritas SENGAJA lebih kecil dari
     106                        // callback cetak bawaan WordPress supaya kita sempat mendaftarkan
     107                        // bundle (wp_register_style()/wp_enqueue_style() ASLI) dan
     108                        // men-dequeue file sumbernya SEBELUM WordPress mencetak antrean
     109                        // $wp_styles/$wp_scripts miliknya sendiri:
     110                        //   wp_print_styles()         @ wp_head   prioritas 8  → kita 7
     111                        //   wp_print_head_scripts()   @ wp_head   prioritas 9  → kita 8
     112                        //   wp_print_footer_scripts() @ wp_footer prioritas 20 → kita 19
     113                        add_action( 'wp_head', array( $this, 'combine_enqueued_styles' ), 7 );
     114                        add_action( 'wp_head', array( $this, 'combine_enqueued_head_scripts' ), 8 );
     115                        add_action( 'wp_footer', array( $this, 'combine_enqueued_footer_scripts' ), 19 );
    99116                }
    100117
    … …  
    214231                }
    215232
    216                 if ( ! empty( $this->options['combine_assets'] ) ) {
    217                         $html = $this->combine_css( $html );
    218                         $html = $this->combine_js( $html );
    219                 }
     233                // combine_css()/combine_js() TIDAK dipanggil di sini — pemindaian &
     234                // penyuntikan tag di HTML jadi (Opsi A, sudah dihapus total dari
     235                // plugin ini) digantikan mekanisme lewat hook wp_head/wp_footer
     236                // (lihat combine_enqueued_styles() dkk., didaftarkan di init())
     237                // yang berjalan SEBELUM HTML ini bahkan tercetak, lewat
     238                // wp_register_style()/wp_enqueue_style() asli.
    220239
    221240                if ( ! empty( $this->options['asset_preload'] ) ) {
    … …  
    11521171
    11531172        // =========================================================================
    1154         // TRANSFORMASI: GABUNGKAN CSS & JS (mengurangi jumlah permintaan HTTP)
     1173        // GABUNGKAN CSS/JS VIA SIKLUS ENQUEUE ASLI (mengurangi jumlah permintaan HTTP)
    11551174        // =========================================================================
    11561175        //
    1157         // Fitur ini menjawab saran umum di tes kecepatan halaman ("Kurangi jumlah
    1158         // permintaan HTTP") dengan menggabungkan beberapa file CSS/JS lokal yang
    1159         // BERURUTAN di HTML menjadi satu file, sehingga browser hanya perlu satu
    1160         // koneksi/request untuk semuanya alih-alih satu per file.
     1176        // [RIWAYAT] Dua percobaan sebelumnya SUDAH DIHAPUS TOTAL dari plugin ini
     1177        // (bukan sekadar tidak dipanggil lagi):
     1178        //   1. Tag <link>/<script> dibangun manual lewat konkatenasi string PHP
     1179        //      di HTML yang sudah jadi — di-flag Plugin Check WordPress.org
     1180        //      sebagai NonEnqueuedStylesheet/NonEnqueuedScript, karena alat
     1181        //      pemeriksa mendeteksi lewat pencarian string "<link"/"<script" di
     1182        //      source code, terlepas apakah string itu lewat filter resmi
     1183        //      'style_loader_tag'/'script_loader_tag' atau tidak.
     1184        //   2. Perbaikan atas #1: tetap memindai HTML jadi, tapi tag dicetak
     1185        //      lewat instance WP_Styles/WP_Scripts TERISOLASI (bukan
     1186        //      $wp_styles/$wp_scripts global situs) + do_item() asli WordPress
     1187        //      core. Ini memang menghilangkan masalah Plugin Check, TAPI
     1188        //      terbukti (diuji langsung di situs nyata) tetap membuat sebagian
     1189        //      halaman DINAMIS gagal dimuat — kemungkinan karena instantiasi
     1190        //      WP_Styles/WP_Scripts baru memicu ulang action 'wp_default_styles'/
     1191        //      'wp_default_scripts' di tengah request yang sedang berjalan,
     1192        //      berinteraksi buruk dengan plugin/tema lain yang juga nge-hook
     1193        //      action itu (mis. mendaftarkan style/script dengan asumsi hook
     1194        //      itu HANYA tertembak sekali per request, saat bootstrap normal).
    11611195        //
    1162         // PRINSIP KEAMANAN (supaya tampilan/fungsi halaman TIDAK PERNAH berubah):
    1163         //  1. Hanya file SATU HOST dengan situs ini yang digabung. File dari domain
    1164         //     lain (mis. fonts.googleapis.com) dibiarkan apa adanya — domain lain
    1165         //     memang selalu butuh koneksi terpisah, tidak ada untungnya dipaksakan,
    1166         //     dan API pihak ketiga umumnya tidak mengizinkan file-nya di-proxy.
    1167         //  2. Hanya tag yang BERURUTAN (tidak ada elemen lain di antaranya) yang
    1168         //     digabung dalam satu "run" — urutan pemuatan CSS/JS TIDAK PERNAH
    1169         //     berubah, krusial karena cascade CSS dan dependensi JS bergantung
    1170         //     pada urutan asli.
    1171         //  3. CSS: dilewati kalau media berbeda (mis. "print") atau dibungkus
    1172         //     conditional comment IE (<!--[if ...]-->) — keduanya sengaja spesifik
    1173         //     dan tidak boleh disatukan dengan CSS lain.
    1174         //  4. JS: HANYA script polos (tanpa async/defer/module/nomodule, dan tanpa
    1175         //     isi inline apa pun di antara tag buka-tutupnya) yang digabung.
    1176         //     Script async/defer/module sengaja tidak disentuh karena
    1177         //     menggabungkannya bisa mengubah urutan/waktu eksekusi.
    1178         //  5. url() relatif di dalam CSS (gambar/font) ditulis ulang jadi URL
    1179         //     absolut berdasarkan lokasi file ASLI-nya — tetap valid walau file
    1180         //     gabungan disimpan di folder cache yang berbeda.
    1181         //  6. Bundle di-cache di disk dengan nama berbasis hash (path + waktu ubah
    1182         //     tiap file sumber) — kalau salah satu file berubah, key-nya otomatis
    1183         //     berubah dan bundle baru dibuat; bundle lama jadi tak terpakai
    1184         //     (dibersihkan lewat aksi "Hapus Cache").
    1185         //
    1186         // Karena transformasi ini berjalan DI DALAM buffer Optimizer yang (sejak
    1187         // perbaikan urutan buffer) diproses SEBELUM CacheBuilder menyimpan ke
    1188         // disk, hasil gabungannya ikut tersimpan permanen di index.html — kunjungan
    1189         // berikutnya yang disajikan langsung dari cache statis otomatis mendapat
    1190         // HTML yang sudah lebih sedikit request-nya, tanpa biaya proses tambahan.
    1191         // =========================================================================
    1192 
    1193         /**
    1194          * Gabungkan <link rel="stylesheet"> lokal yang berurutan di dalam <head>.
    1195          *
    1196          * @param string $html HTML asli.
    1197          * @return string HTML dengan CSS yang bisa digabung sudah disatukan.
    1198          */
    1199         /**
    1200          * Render tag <link> untuk satu bundel CSS gabungan.
    1201          *
    1202          * [BUG FIX #1] Sebelumnya tag <link> dibangun manual lewat konkatenasi
    1203          * string PHP — walau URL-nya sudah di-esc_url() dengan benar, ini
    1204          * tidak melalui API enqueue bawaan WordPress yang diwajibkan pedoman
    1205          * WordPress.org.
    1206          *
    1207          * [BUG FIX #2 — regresi dari perbaikan #1] Percobaan pertama memakai
    1208          * wp_register_style()+wp_enqueue_style()+WP_Styles::do_item() persis
    1209          * seperti cara WordPress mencetak style yang di-enqueue normal. Ini
    1210          * TERNYATA menyebabkan halaman dinamis (yang belum ada di cache HTML
    1211          * statis, sehingga combine_css() benar-benar berjalan live) gagal
    1212          * tampil/tidak selesai dimuat. Penyebabnya: do_item() menjalankan
    1213          * seluruh mesin internal WP_Styles/WP_Scripts (resolusi loading
    1214          * strategy async/defer sejak WP 6.3, pengecekan dependents, dsb.) yang
    1215          * DIRANCANG untuk dipanggil pada siklus normal wp_head/wp_footer —
    1216          * memanggilnya di titik yang sangat tidak lazim ini (di tengah
    1217          * pemrosesan buffer, JAUH SETELAH wp_head/wp_footer asli selesai)
    1218          * berisiko konflik dengan state $wp_styles/$wp_scripts yang sudah
    1219          * "selesai" dan bisa membuat proses macet/fatal di sebagian
    1220          * konfigurasi server (khususnya kalau ada plugin/tema lain yang juga
    1221          * memfilter atau membaca isi antrean itu).
    1222          *
    1223          * Sekarang: TIDAK menyentuh antrean $wp_styles/$wp_scripts global
    1224          * sama sekali (tidak ada register/enqueue/dequeue/deregister). Tag
    1225          * dibangun langsung, lalu dilewatkan ke filter resmi WordPress
    1226          * 'style_loader_tag' — filter YANG SAMA yang dijalankan
    1227          * WP_Styles::do_item() secara internal untuk setiap style yang
    1228          * dicetak. Ini tetap "menggunakan mekanisme enqueue resmi WordPress"
    1229          * (tema/plugin lain yang menambah atribut lewat filter ini tetap
    1230          * berlaku di sini) tanpa risiko mengganggu siklus enqueue yang
    1231          * sesungguhnya sudah selesai berjalan.
    1232          *
    1233          * @param string $handle     Handle unik untuk bundel ini (dipakai sebagai id filter, bukan didaftarkan ke antrean).
    1234          * @param string $bundle_url URL bundel.
    1235          * @param string $media      Atribut media (default 'all').
    1236          * @return string Markup <link> lengkap.
    1237          */
    1238         private function render_enqueued_style_tag( $handle, $bundle_url, $media = 'all' ) {
    1239                 // [PENTING] Guard jaga-jaga (defense-in-depth) — init() sudah
    1240                 // return lebih awal kalau is_admin() (jadi start_optimizer_buffer()
    1241                 // TIDAK PERNAH ter-hook di wp-admin), tapi tetap dipasang di sini
    1242                 // supaya kalau suatu saat ada refactor yang keliru memanggil
    1243                 // fungsi ini dari konteks admin, tidak ada efek samping sama
    1244                 // sekali — bundel gabungan ini murni untuk output HTML front-end.
    1245                 if ( is_admin() ) {
    1246                         return '';
    1247                 }
    1248                 $media = $media ? $media : 'all';
    1249                 $href  = esc_url( $bundle_url );
    1250                 $tag   = "<link rel='stylesheet' id='" . esc_attr( $handle ) . "-css' href='" . $href . "' media='" . esc_attr( $media ) . "' type='text/css' />\n";
    1251                 /** Filter resmi WordPress — sama seperti yang dipakai WP_Styles::do_item(). */
    1252                 return (string) apply_filters( 'style_loader_tag', $tag, $handle, $href, $media );
    1253         }
    1254 
    1255         /**
    1256          * Render tag <script> untuk satu bundel JS gabungan. Lihat catatan
    1257          * lengkap di render_enqueued_style_tag() — pola & alasan yang sama
    1258          * persis. Memakai wp_get_script_tag() (fungsi resmi WordPress sejak
    1259          * 5.7 khusus untuk membangun tag <script> dengan atribut yang benar,
    1260          * termasuk menjalankan filter 'wp_script_attributes') dan filter
    1261          * 'script_loader_tag' — TANPA register/enqueue ke $wp_scripts global.
    1262          *
    1263          * @param string $handle     Handle unik untuk bundel ini.
    1264          * @param string $bundle_url URL bundel.
    1265          * @return string Markup <script> lengkap.
    1266          */
    1267         private function render_enqueued_script_tag( $handle, $bundle_url ) {
    1268                 // [PENTING] Sama seperti render_enqueued_style_tag() — guard
    1269                 // jaga-jaga, lihat catatan lengkap di sana.
    1270                 if ( is_admin() ) {
    1271                         return '';
    1272                 }
    1273                 $src = esc_url( $bundle_url );
    1274                 if ( function_exists( 'wp_get_script_tag' ) ) {
    1275                         $tag = wp_get_script_tag(
    1276                                 array(
    1277                                         'src' => $src,
    1278                                         'id'  => $handle . '-js',
    1279                                 )
    1280                         );
    1281                 } else {
    1282                         // Fallback untuk WP < 5.7 (wp_get_script_tag() belum ada).
    1283                         $tag = "<script src='" . $src . "' id='" . esc_attr( $handle ) . "-js'></script>\n";
    1284                 }
    1285                 /** Filter resmi WordPress — sama seperti yang dipakai WP_Scripts::do_item(). */
    1286                 return (string) apply_filters( 'script_loader_tag', $tag, $handle, $src );
    1287         }
    1288 
    1289 
    1290         private function combine_css( $html ) {
    1291                 if ( false === stripos( $html, 'stylesheet' ) ) {
    1292                         return $html;
    1293                 }
    1294                 if ( ! preg_match( '/<head[^>]*>.*?<\/head>/is', $html, $head_match ) ) {
    1295                         return $html;
    1296                 }
    1297                 $head = $head_match[0];
    1298 
    1299                 if ( ! preg_match_all( '/<link\s+[^>]*rel=[\'"]stylesheet[\'"][^>]*>/i', $head, $matches, PREG_OFFSET_CAPTURE ) ) {
    1300                         return $html;
    1301                 }
    1302                 $tags = $matches[0];
    1303                 if ( count( $tags ) < 2 ) {
    1304                         return $html;
    1305                 }
    1306 
    1307                 $conditional_ranges = $this->find_conditional_comment_ranges( $head );
    1308 
     1196        // SOLUSI YANG DIPAKAI SEKARANG menghindari KEDUA masalah itu sekaligus
     1197        // dengan pindah TITIK WAKTU eksekusi: bukan lagi "setelah semua
     1198        // tercetak" (memindai HTML jadi, baik manual maupun lewat instance
     1199        // terisolasi), tapi "SEBELUM WordPress mencetak antrean $wp_styles/
     1200        // $wp_scripts miliknya sendiri" — lewat hook wp_head/wp_footer dengan
     1201        // prioritas LEBIH RENDAH dari callback cetak bawaan WordPress:
     1202        //   - wp_print_styles()        @ wp_head prioritas 8  → kita di 7
     1203        //   - wp_print_head_scripts()  @ wp_head prioritas 9  → kita di 8
     1204        //   - wp_print_footer_scripts()@ wp_footer prioritas 20 → kita di 19
     1205        // Di titik ini antrean BELUM dicetak sama sekali (bukan "belum
     1206        // selesai", tapi benar-benar belum mulai) — kita baca urutan cetak yang
     1207        // AKAN terjadi (lewat WP_Dependencies::all_deps(), fungsi resmi yang
     1208        // sama dipakai do_items() secara internal), gabungkan file yang layak,
     1209        // lalu daftarkan hasilnya lewat wp_register_style()/wp_enqueue_style()
     1210        // (atau versi script-nya) — FUNGSI ENQUEUE ASLI, bukan tag manual, dan
     1211        // TIDAK PERNAH menyentuh instance WP_Styles/WP_Scripts baru — dan
     1212        // wp_dequeue_style()/wp_dequeue_script() untuk file asli yang sudah
     1213        // digabung. WordPress SENDIRI yang mencetak semuanya sesudah ini,
     1214        // lewat do_items() aslinya, tanpa kita pernah membangun satu pun tag
     1215        // <link>/<script> secara manual maupun memicu ulang action bootstrap
     1216        // apa pun.
     1217
     1218        /**
     1219         * Hook 'wp_head' prioritas 7 — gabungkan CSS yang di-enqueue, sebelum
     1220         * WordPress mencetaknya sendiri (wp_print_styles() @ prioritas 8).
     1221         */
     1222        public function combine_enqueued_styles() {
     1223                if ( empty( $this->options['combine_assets'] ) ) {
     1224                        return;
     1225                }
     1226                global $wp_styles;
     1227                if ( ! ( $wp_styles instanceof WP_Styles ) || empty( $wp_styles->queue ) ) {
     1228                        return;
     1229                }
     1230
     1231                // Urutan cetak SEBENARNYA (mengikuti dependensi antar-handle) —
     1232                // fungsi resmi yang sama dipakai WP_Styles::do_items() secara
     1233                // internal sebelum mencetak.
     1234                $wp_styles->all_deps( $wp_styles->queue );
     1235                $ordered = $wp_styles->to_do;
     1236                if ( count( $ordered ) < 2 ) {
     1237                        return;
     1238                }
     1239
     1240                // [DIOPTIMALKAN] Sebelumnya, handle yang jadi dependensi handle LAIN
     1241                // (di mana pun posisinya) dikecualikan TOTAL dari penggabungan —
     1242                // terlalu konservatif. Sebenarnya TIDAK PERLU: kita tidak pernah
     1243                // mengubah URUTAN cetak, hanya menyatukan beberapa handle
     1244                // BERURUTAN jadi satu bundel yang menempati posisi yang SAMA di
     1245                // urutan cetak (posisi handle PERTAMA dalam rangkaian itu). Baik
     1246                // dependent-nya ikut tergabung dalam bundel yang sama (tetap
     1247                // berurutan benar di dalam isi bundel) MAUPUN dependent-nya
     1248                // berada di rangkaian/posisi LAIN setelahnya (tetap tercetak
     1249                // setelah bundel ini, sama seperti sebelum digabung) — urutan
     1250                // "dependensi harus tercetak sebelum dependent-nya" otomatis
     1251                // tetap terjaga tanpa perlu pengecualian dependensi sama sekali.
     1252                // Yang membatasi penggabungan sekarang murni kelayakan PER-HANDLE
     1253                // (lihat get_combinable_style_info(): media cocok, bukan
     1254                // conditional comment, file lokal & terbaca) dan keharusan
     1255                // BERURUTAN di urutan cetak (lihat pemutus run di bawah).
    13091256                $runs        = array();
    13101257                $current_run = array();
    1311                 $last_end    = 0;
    1312 
    1313                 foreach ( $tags as $t ) {
    1314                         $tag   = $t[0];
    1315                         $start = $t[1];
    1316                         $end   = $start + strlen( $tag );
    1317 
    1318                         if ( ! empty( $current_run ) && '' !== trim( substr( $head, $last_end, $start - $last_end ) ) ) {
    1319                                 if ( count( $current_run ) >= 2 ) {
    1320                                         $runs[] = $current_run;
    1321                                 }
    1322                                 $current_run = array();
    1323                         }
    1324 
    1325                         $info = $this->parse_stylesheet_tag( $tag, $start, $end, $conditional_ranges );
     1258                foreach ( $ordered as $handle ) {
     1259                        $info = $this->get_combinable_style_info( $handle, $wp_styles );
    13261260                        if ( false === $info ) {
    13271261                                if ( count( $current_run ) >= 2 ) {
    … …  
    13291263                                }
    13301264                                $current_run = array();
    1331                         } else {
    1332                                 $current_run[] = $info;
    1333                         }
    1334 
    1335                         $last_end = $end;
     1265                                continue;
     1266                        }
     1267                        $current_run[] = $info;
    13361268                }
    13371269                if ( count( $current_run ) >= 2 ) {
    13381270                        $runs[] = $current_run;
    13391271                }
    1340 
    13411272                if ( empty( $runs ) ) {
    1342                         return $html;
    1343                 }
    1344 
    1345                 // Proses dari run TERAKHIR ke PERTAMA supaya offset yang belum
    1346                 // diproses tidak bergeser akibat penggantian pada run sebelumnya.
    1347                 foreach ( array_reverse( $runs ) as $run ) {
     1273                        return;
     1274                }
     1275
     1276                foreach ( $runs as $run ) {
    13481277                        $bundle_url = $this->build_asset_bundle( 'css', $run );
    13491278                        if ( false === $bundle_url ) {
    13501279                                continue;
    13511280                        }
    1352                         $first      = $run[0];
    1353                         $last       = $run[ count( $run ) - 1 ];
    1354                         $media_attr = ( '' !== $first['media'] && 'all' !== $first['media'] ) ? $first['media'] : 'all';
    1355                         // [BUG FIX] Handle unik per-bundel (bukan cuma satu handle
    1356                         // tetap) — halaman bisa punya lebih dari satu run gabungan
    1357                         // (mis. dipisah oleh <script> conditional di antaranya), dan
    1358                         // setiap bundel punya URL berbeda.
    1359                         $handle      = 'nazemiwpsc-combined-css-' . substr( md5( $bundle_url ), 0, 12 );
    1360                         $replacement = $this->render_enqueued_style_tag( $handle, $bundle_url, $media_attr );
    1361                         if ( '' === $replacement ) {
    1362                                 continue; // WP_Styles gagal mencetak (seharusnya tidak pernah terjadi) — biarkan tag asli, jangan hapus aset.
    1363                         }
    1364 
    1365                         $head = substr_replace( $head, $replacement, $first['start'], $last['end'] - $first['start'] );
    1366                 }
    1367 
    1368                 return substr_replace( $html, $head, strpos( $html, $head_match[0] ), strlen( $head_match[0] ) );
    1369         }
    1370 
    1371         /**
    1372          * Analisis satu tag <link rel="stylesheet">. Mengembalikan array info
    1373          * (path lokal, mtime, media, posisi) jika layak digabung, atau false.
    1374          */
    1375         private function parse_stylesheet_tag( $tag, $start, $end, $conditional_ranges ) {
    1376                 foreach ( $conditional_ranges as $range ) {
    1377                         if ( $start >= $range[0] && $start < $range[1] ) {
    1378                                 return false; // Di dalam conditional comment IE — jangan disentuh.
    1379                         }
    1380                 }
    1381 
    1382                 if ( ! preg_match( '/href=[\'"]([^\'"]+)[\'"]/i', $tag, $m ) ) {
    1383                         return false;
    1384                 }
    1385                 $href = html_entity_decode( $m[1], ENT_QUOTES );
    1386 
    1387                 $media = '';
    1388                 if ( preg_match( '/media=[\'"]([^\'"]+)[\'"]/i', $tag, $mm ) ) {
    1389                         $media = strtolower( trim( $mm[1] ) );
    1390                 }
     1281                        $first  = $run[0];
     1282                        $media  = ( '' !== $first['media'] && 'all' !== $first['media'] ) ? $first['media'] : 'all';
     1283                        $handle = 'nazemiwpsc-combined-css-' . substr( md5( $bundle_url ), 0, 12 );
     1284                        // Versi EKSPLISIT wajib (Plugin Check: NoExplicitVersion) — pakai
     1285                        // nama file bundel itu sendiri (16 karakter hash md5(path|mtime)
     1286                        // tiap sumber, lihat build_asset_bundle()), bukan false/null yang
     1287                        // membuat WordPress diam-diam pakai versi WP core sebagai fallback.
     1288                        // Otomatis berubah kalau isi bundel berubah — cache-busting browser
     1289                        // tetap benar tanpa perlu query string ?ver= terpisah.
     1290                        $ver = pathinfo( $bundle_url, PATHINFO_FILENAME );
     1291
     1292                        // Fungsi ENQUEUE ASLI WordPress — bukan tag manual.
     1293                        wp_register_style( $handle, $bundle_url, array(), $ver, $media );
     1294                        wp_enqueue_style( $handle );
     1295
     1296                        foreach ( $run as $item ) {
     1297                                wp_dequeue_style( $item['handle'] );
     1298                        }
     1299                }
     1300        }
     1301
     1302        /**
     1303         * Analisis satu handle CSS terdaftar. Mengembalikan array info (handle,
     1304         * href, media, path, mtime) jika layak digabung, atau false.
     1305         */
     1306        private function get_combinable_style_info( $handle, $wp_styles ) {
     1307                if ( isset( $wp_styles->done[ $handle ] ) ) {
     1308                        return false; // Sudah tercetak — jaga-jaga, seharusnya tidak pernah terjadi di titik ini.
     1309                }
     1310                $dep = isset( $wp_styles->registered[ $handle ] ) ? $wp_styles->registered[ $handle ] : null;
     1311                if ( ! $dep || empty( $dep->src ) ) {
     1312                        return false; // Tanpa src (mis. hanya pembawa style inline) — tidak ada file untuk digabung.
     1313                }
     1314                if ( ! empty( $dep->extra['conditional'] ) ) {
     1315                        return false; // Dibungkus <!--[if ...]--> (wp_style_add_data 'conditional') — jangan sentuh.
     1316                }
     1317                if ( ! empty( $dep->extra['alt'] ) ) {
     1318                        return false; // rel="alternate stylesheet".
     1319                }
     1320
     1321                $media = isset( $dep->args ) ? strtolower( trim( (string) $dep->args ) ) : 'all';
    13911322                if ( '' !== $media && 'all' !== $media && 'screen' !== $media ) {
    13921323                        return false; // Media selain all/screen (mis. print) — jangan gabung.
    13931324                }
    13941325
    1395                 $path = $this->url_to_local_path( $href );
     1326                $src = $this->resolve_dep_src( $dep->src, $wp_styles->base_url );
     1327                $path = $this->url_to_local_path( $src );
    13961328                if ( false === $path || ! is_readable( $path ) ) {
    1397                         return false; // Bukan file lokal, atau tidak terbaca.
    1398                 }
    1399 
     1329                        return false;
     1330                }
    14001331                $mtime = filemtime( $path );
    14011332                if ( false === $mtime ) {
    … …  
    14041335
    14051336                return array(
    1406                         'tag'   => $tag,
    1407                         'href'  => $href,
    1408                         'media' => $media,
    1409                         'path'  => $path,
    1410                         'mtime' => $mtime,
    1411                         'start' => $start,
    1412                         'end'   => $end,
     1337                        'handle' => $handle,
     1338                        'href'   => $src,
     1339                        'media'  => $media,
     1340                        'path'   => $path,
     1341                        'mtime'  => $mtime,
    14131342                );
    14141343        }
    14151344
    14161345        /**
    1417          * Gabungkan <script src="..."> POLOS (tanpa async/defer/module) yang
    1418          * berurutan — dipisah antara yang di <head> dan yang di <body>/footer
    1419          * supaya posisi asli (head vs footer) tidak ikut berubah.
    1420          *
    1421          * @param string $html HTML asli.
    1422          * @return string HTML dengan JS yang bisa digabung sudah disatukan.
    1423          */
    1424         private function combine_js( $html ) {
    1425                 if ( false === stripos( $html, '<script' ) ) {
    1426                         return $html;
    1427                 }
    1428 
    1429                 $pattern = '/<script\s+[^>]*\bsrc=[\'"][^\'"]+[\'"][^>]*>\s*<\/script>/i';
    1430                 if ( ! preg_match_all( $pattern, $html, $matches, PREG_OFFSET_CAPTURE ) ) {
    1431                         return $html;
    1432                 }
    1433                 $tags = $matches[0];
    1434                 if ( count( $tags ) < 2 ) {
    1435                         return $html;
    1436                 }
    1437 
     1346         * Hook 'wp_head' prioritas 8 — gabungkan JS grup HEAD (in_footer=false)
     1347         * yang di-enqueue, sebelum WordPress mencetaknya sendiri
     1348         * (wp_print_head_scripts() @ prioritas 9).
     1349         */
     1350        public function combine_enqueued_head_scripts() {
     1351                $this->combine_enqueued_scripts_by_group( 0 );
     1352        }
     1353
     1354        /**
     1355         * Hook 'wp_footer' prioritas 19 — gabungkan JS grup FOOTER (in_footer=true)
     1356         * yang di-enqueue, sebelum WordPress mencetaknya sendiri
     1357         * (wp_print_footer_scripts() @ prioritas 20).
     1358         */
     1359        public function combine_enqueued_footer_scripts() {
     1360                $this->combine_enqueued_scripts_by_group( 1 );
     1361        }
     1362
     1363        /**
     1364         * Logika bersama untuk kedua grup (head/footer) JS — lihat dua fungsi
     1365         * publik di atas. $group: 0 = head, 1 = footer (sama seperti nilai
     1366         * $dep->extra['group'] milik WP_Scripts, diisi WordPress sendiri
     1367         * berdasarkan parameter $in_footer saat wp_enqueue_script() dipanggil).
     1368         *
     1369         * Handle yang BUKAN milik $group yang sedang diproses SENGAJA dianggap
     1370         * "pemutus run" (bukan diabaikan begitu saja) — supaya file head tidak
     1371         * pernah ikut tergabung ke bundle footer, atau sebaliknya; posisi
     1372         * head-vs-footer aslinya harus tetap terjaga.
     1373         *
     1374         * @param int $group 0 (head) atau 1 (footer).
     1375         */
     1376        private function combine_enqueued_scripts_by_group( $group ) {
     1377                if ( empty( $this->options['combine_assets'] ) ) {
     1378                        return;
     1379                }
     1380                global $wp_scripts;
     1381                if ( ! ( $wp_scripts instanceof WP_Scripts ) || empty( $wp_scripts->queue ) ) {
     1382                        return;
     1383                }
     1384
     1385                $wp_scripts->all_deps( $wp_scripts->queue );
     1386                $ordered = $wp_scripts->to_do;
     1387                if ( count( $ordered ) < 2 ) {
     1388                        return;
     1389                }
     1390
     1391                // [DIOPTIMALKAN] Lihat catatan lengkap di combine_enqueued_styles()
     1392                // — pengecualian dependensi TIDAK DIPERLUKAN untuk keamanan
     1393                // (kita tidak pernah mengubah urutan cetak, hanya menyatukan
     1394                // handle BERURUTAN jadi satu bundel di posisi yang sama), jadi
     1395                // dihapus di sini juga supaya lebih banyak JS bisa digabung.
    14381396                $runs        = array();
    14391397                $current_run = array();
    1440                 $last_end    = 0;
    1441 
    1442                 foreach ( $tags as $t ) {
    1443                         $tag   = $t[0];
    1444                         $start = $t[1];
    1445                         $end   = $start + strlen( $tag );
    1446 
    1447                         if ( ! empty( $current_run ) && '' !== trim( substr( $html, $last_end, $start - $last_end ) ) ) {
     1398                foreach ( $ordered as $handle ) {
     1399                        $dep          = isset( $wp_scripts->registered[ $handle ] ) ? $wp_scripts->registered[ $handle ] : null;
     1400                        $handle_group = ( $dep && ! empty( $dep->extra['group'] ) ) ? 1 : 0;
     1401
     1402                        if ( $handle_group !== $group ) {
     1403                                // Handle milik grup lain (head vs footer) — pemutus run,
     1404                                // BUKAN diproses di pemanggilan fungsi ini sama sekali.
    14481405                                if ( count( $current_run ) >= 2 ) {
    14491406                                        $runs[] = $current_run;
    14501407                                }
    14511408                                $current_run = array();
    1452                         }
    1453 
    1454                         $info = $this->parse_script_tag( $tag, $start, $end );
     1409                                continue;
     1410                        }
     1411
     1412                        $info = $this->get_combinable_script_info( $handle, $wp_scripts );
    14551413                        if ( false === $info ) {
    14561414                                if ( count( $current_run ) >= 2 ) {
    … …  
    14581416                                }
    14591417                                $current_run = array();
    1460                         } else {
    1461                                 $current_run[] = $info;
    1462                         }
    1463 
    1464                         $last_end = $end;
     1418                                continue;
     1419                        }
     1420                        $current_run[] = $info;
    14651421                }
    14661422                if ( count( $current_run ) >= 2 ) {
    14671423                        $runs[] = $current_run;
    14681424                }
    1469 
    14701425                if ( empty( $runs ) ) {
    1471                         return $html;
    1472                 }
    1473 
    1474                 foreach ( array_reverse( $runs ) as $run ) {
     1426                        return;
     1427                }
     1428
     1429                foreach ( $runs as $run ) {
    14751430                        $bundle_url = $this->build_asset_bundle( 'js', $run );
    14761431                        if ( false === $bundle_url ) {
    14771432                                continue;
    14781433                        }
    1479                         $first       = $run[0];
    1480                         $last        = $run[ count( $run ) - 1 ];
    1481                         $handle      = 'nazemiwpsc-combined-js-' . substr( md5( $bundle_url ), 0, 12 );
    1482                         $replacement = $this->render_enqueued_script_tag( $handle, $bundle_url );
    1483                         if ( '' === $replacement ) {
    1484                                 continue; // WP_Scripts gagal mencetak (seharusnya tidak pernah terjadi) — biarkan tag asli, jangan hapus aset.
    1485                         }
    1486 
    1487                         $html = substr_replace( $html, $replacement, $first['start'], $last['end'] - $first['start'] );
    1488                 }
    1489 
    1490                 return $html;
    1491         }
    1492 
    1493         /**
    1494          * Analisis satu tag <script src="...">. Mengembalikan array info jika
    1495          * layak digabung (script polos, lokal, tanpa isi inline), atau false.
    1496          */
    1497         private function parse_script_tag( $tag, $start, $end ) {
    1498                 // Tolak module/nomodule/async/defer — urutan & waktu eksekusinya
    1499                 // khusus, tidak boleh disatukan dengan script polos lain.
    1500                 if ( preg_match( '/\b(type=[\'"]module[\'"]|nomodule|async|defer)\b/i', $tag ) ) {
     1434                        $handle = 'nazemiwpsc-combined-js-' . substr( md5( $bundle_url ), 0, 12 );
     1435                        // Versi EKSPLISIT wajib (Plugin Check: NoExplicitVersion) — lihat
     1436                        // catatan yang sama di combine_enqueued_styles().
     1437                        $ver = pathinfo( $bundle_url, PATHINFO_FILENAME );
     1438
     1439                        // Fungsi ENQUEUE ASLI WordPress — bukan tag manual. Parameter
     1440                        // ke-5 (boolean, bukan array strategy WP 6.3+) sengaja dipakai
     1441                        // demi kompatibilitas mundur — readme.txt: "Requires at least: 6.0".
     1442                        wp_register_script( $handle, $bundle_url, array(), $ver, 1 === $group );
     1443                        wp_enqueue_script( $handle );
     1444
     1445                        foreach ( $run as $item ) {
     1446                                wp_dequeue_script( $item['handle'] );
     1447                        }
     1448                }
     1449        }
     1450
     1451        /**
     1452         * Analisis satu handle JS terdaftar. Mengembalikan array info (handle,
     1453         * href, path, mtime) jika layak digabung, atau false.
     1454         */
     1455        private function get_combinable_script_info( $handle, $wp_scripts ) {
     1456                if ( isset( $wp_scripts->done[ $handle ] ) ) {
    15011457                        return false;
    15021458                }
    1503                 // Catatan: tidak perlu pengecualian khusus untuk script internal
    1504                 // plugin ini (lazy-loader, delay-js-manager, prefetch-hover) — fungsi
    1505                 // ini hanya dipanggil untuk tag yang sudah cocok dengan pola
    1506                 // `<script src="...">` (lihat combine_js()), dan ketiga script
    1507                 // tersebut selalu inline TANPA atribut src, jadi tidak akan pernah
    1508                 // sampai ke sini sama sekali.
    1509                 // Tolak kalau ada isi inline di antara tag buka & tutup (mis. fallback
    1510                 // script) — hanya <script src="..."></script> yang benar-benar kosong
    1511                 // yang aman digabung.
    1512                 if ( ! preg_match( '/<script\b[^>]*>\s*<\/script>/is', $tag ) ) {
     1459                $dep = isset( $wp_scripts->registered[ $handle ] ) ? $wp_scripts->registered[ $handle ] : null;
     1460                if ( ! $dep || empty( $dep->src ) ) {
     1461                        return false; // Tanpa src (mis. hanya pembawa wp_add_inline_script mandiri).
     1462                }
     1463                if ( ! empty( $dep->extra['data'] ) || ! empty( $dep->extra['after'] ) ) {
     1464                        return false; // Ada inline script (before/after, lihat wp_add_inline_script()) melekat pada handle ini — jangan sentuh, supaya inline script itu tidak ikut hilang.
     1465                }
     1466                if ( ! empty( $dep->extra['conditional'] ) ) {
    15131467                        return false;
    15141468                }
    1515                 if ( preg_match( '/type=[\'"]([^\'"]+)[\'"]/i', $tag, $tm )
    1516                         && ! in_array( strtolower( $tm[1] ), array( 'text/javascript', 'application/javascript' ), true )
    1517                 ) {
    1518                         return false;
    1519                 }
    1520                 if ( ! preg_match( '/\bsrc=[\'"]([^\'"]+)[\'"]/i', $tag, $m ) ) {
    1521                         return false;
    1522                 }
    1523                 $src = html_entity_decode( $m[1], ENT_QUOTES );
    1524 
     1469                if ( ! empty( $dep->extra['strategy'] ) ) {
     1470                        return false; // async/defer (WP 6.3+ script loading strategy) — urutan/waktu eksekusinya signifikan, jangan gabung.
     1471                }
     1472                if ( isset( $dep->extra['type'] ) && 'module' === $dep->extra['type'] ) {
     1473                        return false; // ES module — semantik importnya beda, jangan gabung sebagai script biasa.
     1474                }
     1475
     1476                $src  = $this->resolve_dep_src( $dep->src, $wp_scripts->base_url );
    15251477                $path = $this->url_to_local_path( $src );
    15261478                if ( false === $path || ! is_readable( $path ) ) {
    15271479                        return false;
    15281480                }
    1529 
    15301481                $mtime = filemtime( $path );
    15311482                if ( false === $mtime ) {
    … …  
    15341485
    15351486                return array(
    1536                         'tag'   => $tag,
    1537                         'src'   => $src,
    1538                         'path'  => $path,
    1539                         'mtime' => $mtime,
    1540                         'start' => $start,
    1541                         'end'   => $end,
     1487                        'handle' => $handle,
     1488                        'href'   => $src,
     1489                        'path'   => $path,
     1490                        'mtime'  => $mtime,
    15421491                );
    15431492        }
    15441493
    15451494        /**
    1546          * Temukan rentang byte [start, end) dari setiap blok conditional comment
    1547          * IE (<!--[if ...]> ... <![endif]-->) di dalam string.
    1548          *
    1549          * @param string $str String yang diperiksa.
    1550          * @return array Daftar rentang [start, end].
    1551          */
    1552         private function find_conditional_comment_ranges( $str ) {
    1553                 $ranges = array();
    1554                 if ( preg_match_all( '/<!--\[if[^\]]*\]>.*?<!\[endif\]-->/is', $str, $m, PREG_OFFSET_CAPTURE ) ) {
    1555                         foreach ( $m[0] as $match ) {
    1556                                 $ranges[] = array( $match[1], $match[1] + strlen( $match[0] ) );
    1557                         }
    1558                 }
    1559                 return $ranges;
    1560         }
     1495         * $dep->src bisa berupa URL lengkap, protokol-relatif ("//..."), atau
     1496         * root-relatif ("/wp-content/..."). WP_Styles/WP_Scripts::do_item()
     1497         * sendiri yang menambahkan $base_url saat mencetak kalau src tidak
     1498         * diawali "http"/"//" — replikasi logika yang sama di sini supaya
     1499         * url_to_local_path() menerima URL yang benar & lengkap.
     1500         *
     1501         * @param string $src      Nilai mentah $dep->src.
     1502         * @param string $base_url $wp_styles->base_url atau $wp_scripts->base_url.
     1503         * @return string URL lengkap.
     1504         */
     1505        private function resolve_dep_src( $src, $base_url ) {
     1506                if ( preg_match( '#^(https?:)?//#i', $src ) ) {
     1507                        return $src;
     1508                }
     1509                return $base_url . $src;
     1510        }
     1511
    15611512
    15621513        /**
    … …  
    17011652         *
    17021653         * @param string $type 'css' atau 'js'.
    1703          * @param array  $items Daftar file (hasil parse_stylesheet_tag/parse_script_tag).
     1654         * @param array  $items Daftar file (hasil get_combinable_style_info()/get_combinable_script_info()).
    17041655         * @return string|false URL bundle, atau false jika gagal.
    17051656         */
    … …  
    18681819                $browser_cache_on = ! empty( $this->options['browser_cache'] );
    18691820
    1870                 // [DISEDERHANAKAN] Konsisten dengan get_browser_cache_rules() di
    1871                 // server/class-wpsc-htaccess.php — "Durasi Browser Cache" dan "Bypass
    1872                 // HTML dari Browser Cache" sudah dihapus dari UI, disatukan jadi satu
    1873                 // perilaku tetap: selalu "Selalu Validasi ke Server".
    1874                 $asset_cache_control = 'no-cache, must-revalidate';
     1821                // [DIPERBAIKI] Konsisten dengan get_browser_cache_rules() di
     1822                // server/class-wpsc-htaccess.php — dulu SELALU "Selalu Validasi ke
     1823                // Server" (no-cache, must-revalidate) untuk file di folder _assets/
     1824                // ini juga (termasuk .css.gz / .js.gz hasil "Gabungkan CSS & JS"),
     1825                // memaksa round-trip ke server untuk file yang justru paling sering
     1826                // diminta. Sekarang publik, 30 hari — SELARAS dengan
     1827                // $static_cache_control di get_browser_cache_rules(). .htaccess di
     1828                // folder _assets/ ini lebih spesifik (lebih dalam) daripada
     1829                // .htaccess utama, jadi nilai di sinilah yang sebenarnya berlaku
     1830                // untuk file .css.gz/.js.gz/.ttf.gz/.otf.gz/.woff2 — perlu diubah di
     1831                // KEDUA tempat supaya konsisten.
     1832                $asset_cache_control = 'public, max-age=2592000'; // 30 hari.
    18751833
    18761834                // Blok rewrite + header .gz di sini hanya ditulis kalau "Kompres
    … …  
    21612119         * Blok <script> diekstrak sebelum PASS 5 (CSS url()) dan dipulihkan
    21622120         * sesudahnya, supaya string JavaScript yang kebetulan mengandung pola
    2163          * url-gambar tidak ikut ter-rewrite — teknik placeholder-token yang sama
    2164          * dipakai di beberapa fungsi lain di kelas ini (lihat mis. combine_css()).
     2121         * url-gambar tidak ikut ter-rewrite — teknik placeholder-token.
    21652122         *
    21662123         * @param string $html Konten HTML.
    … …  
    22962253
    22972254                // PASS 5: CSS url() di atribut style dan blok <style>.
    2298                 // Blok <script> diekstrak dulu (placeholder token, sama seperti
    2299                 // combine_css()) agar url() di dalam kode JavaScript tidak ikut
    2300                 // diproses oleh regex ini.
     2255                // Blok <script> diekstrak dulu (placeholder token) agar url() di
     2256                // dalam kode JavaScript tidak ikut diproses oleh regex ini.
    23012257                $script_blocks = array();
    23022258                $token         = substr( md5( uniqid( 'nazemiwpsc_cdn', true ) ), 0, 10 );
  • masterspeed-cache/trunk/readme.txt

    r3671949 r3672525  
    55Tested up to: 7.1
    66Requires PHP: 8.0
    7 Stable tag: 1.1
     7Stable tag: 1.2
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
  • masterspeed-cache/trunk/server/class-wpsc-htaccess.php

    r3670502 r3672525  
    569569
    570570        private function get_browser_cache_rules( $options ) {
    571                 // [DISEDERHANAKAN] "Durasi Browser Cache" (dropdown TTL) dan "Bypass
    572                 // HTML dari Browser Cache" (toggle terpisah) sudah DIHAPUS dari UI —
    573                 // keduanya sekarang disatukan menjadi satu perilaku tetap: HTML DAN
    574                 // aset statis (CSS/JS/gambar/font) SELALU memakai "Selalu Validasi
    575                 // ke Server" (Cache-Control: no-cache, must-revalidate). Browser
    576                 // tetap menyimpan berkasnya, tapi wajib mengonfirmasi dulu ke server
    577                 // (conditional request/304) sebelum memakainya di setiap kunjungan —
    578                 // jadi purge di server selalu langsung terlihat pengunjung tanpa
    579                 // syarat, untuk HTML maupun aset statis.
    580                 $cache_control = 'no-cache, must-revalidate';
    581                 $expires_str   = 'access plus 0 seconds';
     571                // [DIPERBAIKI] Sebelumnya HTML DAN semua aset statis (CSS/JS/gambar/
     572                // font/arsip/dll.) sama-sama dipaksa "Selalu Validasi ke Server"
     573                // (Cache-Control: no-cache, must-revalidate) — artinya browser WAJIB
     574                // mengirim conditional request (round-trip penuh ke server) untuk
     575                // SETIAP aset statis di SETIAP kunjungan, walau isinya tidak pernah
     576                // berubah. Ini meniadakan manfaat paling dasar dari browser caching
     577                // dan bertentangan langsung dengan tujuan plugin ini (kecepatan).
     578                //
     579                // Sekarang dipisah:
     580                //  - HTML tetap "no-cache, must-revalidate" ($cache_control) — supaya
     581                //    purge cache di server selalu langsung terlihat pengunjung tanpa
     582                //    syarat (perilaku ini TIDAK diubah).
     583                //  - Aset statis (termasuk CSS & JS) memakai Cache-Control publik
     584                //    dengan masa berlaku 30 hari ($static_cache_control /
     585                //    $static_expires_str). 30 hari (bukan 1 tahun/"immutable")
     586                //    dipilih secara sengaja: root .htaccess ini berlaku site-wide,
     587                //    termasuk /wp-admin/ (lihat $this->htaccess_file = ABSPATH .
     588                //    '.htaccess') — kalau suatu saat ada CSS/JS admin yang namanya
     589                //    tidak berubah walau isinya diperbarui (mis. lewat custom code
     590                //    tanpa query string ?ver=), staleness-nya dibatasi maksimal 30
     591                //    hari, bukan seterusnya.
     592                $cache_control        = 'no-cache, must-revalidate';
     593                $expires_str          = 'access plus 0 seconds';
     594                $static_cache_control = 'public, max-age=2592000';
     595                $static_expires_str   = 'access plus 2592000 seconds'; // 30 hari.
    582596
    583597                // [DIGANTI] Kompresi font OTF/TTF sekarang TIDAK lagi pakai mod_deflate
    … …  
    625639    ExpiresByType text/html                    "' . $expires_str . '"
    626640
    627     # CSS & JavaScript
    628     ExpiresByType text/css                     "' . $expires_str . '"
    629     ExpiresByType application/javascript       "' . $expires_str . '"
    630     ExpiresByType application/x-javascript    "' . $expires_str . '"
    631     ExpiresByType text/javascript              "' . $expires_str . '"
    632 
    633     # Gambar
    634     ExpiresByType image/jpeg                   "' . $expires_str . '"
    635     ExpiresByType image/png                    "' . $expires_str . '"
    636     ExpiresByType image/gif                    "' . $expires_str . '"
    637     ExpiresByType image/webp                   "' . $expires_str . '"
    638     ExpiresByType image/avif                   "' . $expires_str . '"
    639     ExpiresByType image/svg+xml                "' . $expires_str . '"
    640     ExpiresByType image/x-icon                 "' . $expires_str . '"
    641     ExpiresByType image/vnd.microsoft.icon     "' . $expires_str . '"
    642 
    643     # Font
    644     ExpiresByType font/ttf                     "' . $expires_str . '"
    645     ExpiresByType font/otf                     "' . $expires_str . '"
    646     ExpiresByType font/woff                    "' . $expires_str . '"
    647     ExpiresByType font/woff2                   "' . $expires_str . '"
    648     ExpiresByType application/font-woff        "' . $expires_str . '"
    649     ExpiresByType application/font-woff2       "' . $expires_str . '"
    650     ExpiresByType application/x-font-ttf       "' . $expires_str . '"
     641    # CSS & JavaScript — 30 hari.
     642    ExpiresByType text/css                     "' . $static_expires_str . '"
     643    ExpiresByType application/javascript       "' . $static_expires_str . '"
     644    ExpiresByType application/x-javascript    "' . $static_expires_str . '"
     645    ExpiresByType text/javascript              "' . $static_expires_str . '"
     646
     647    # Gambar — 30 hari.
     648    ExpiresByType image/jpeg                   "' . $static_expires_str . '"
     649    ExpiresByType image/png                    "' . $static_expires_str . '"
     650    ExpiresByType image/gif                    "' . $static_expires_str . '"
     651    ExpiresByType image/webp                   "' . $static_expires_str . '"
     652    ExpiresByType image/avif                   "' . $static_expires_str . '"
     653    ExpiresByType image/svg+xml                "' . $static_expires_str . '"
     654    ExpiresByType image/x-icon                 "' . $static_expires_str . '"
     655    ExpiresByType image/vnd.microsoft.icon     "' . $static_expires_str . '"
     656
     657    # Font — 30 hari.
     658    ExpiresByType font/ttf                     "' . $static_expires_str . '"
     659    ExpiresByType font/otf                     "' . $static_expires_str . '"
     660    ExpiresByType font/woff                    "' . $static_expires_str . '"
     661    ExpiresByType font/woff2                   "' . $static_expires_str . '"
     662    ExpiresByType application/font-woff        "' . $static_expires_str . '"
     663    ExpiresByType application/font-woff2       "' . $static_expires_str . '"
     664    ExpiresByType application/x-font-ttf       "' . $static_expires_str . '"
    651665
    652666    # Data — tidak di-cache
    … …  
    663677
    664678<IfModule mod_headers.c>
    665     # Aset statis DAN HTML: selalu "no-cache, must-revalidate" (lihat catatan
    666     # di atas fungsi ini) — browser tetap menyimpan berkasnya tapi wajib
    667     # mengonfirmasi dulu ke server (conditional request/304) sebelum
    668     # memakainya, jadi purge di server selalu langsung terlihat pengunjung.
     679    # Aset statis: Cache-Control publik 30 hari ($static_cache_control) —
     680    # HTML tetap "no-cache, must-revalidate" secara terpisah di bawah (lihat
     681    # catatan lengkap di awal fungsi ini).
    669682    # [PERUBAHAN] Daftar ekstensi di sini SEKARANG disamakan persis dengan
    670683    # daftar ekstensi Cache Rule #2 Cloudflare (Static Assets — lihat
    … …  
    675688    # dapat Cache-Control yang sama dari origin — tidak konsisten antara
    676689    # kedua lapis cache untuk jenis file yang sama-sama statis/immutable.
     690    # [DIPERBAIKI] Aset statis (termasuk CSS & JS) sekarang dapat
     691    # Cache-Control publik 30 hari, bukan lagi "no-cache, must-revalidate"
     692    # (lihat catatan lengkap di awal fungsi ini) — browser boleh langsung
     693    # memakai salinan lokalnya tanpa round-trip ke server sampai 30 hari,
     694    # bukan wajib validasi di SETIAP kunjungan seperti sebelumnya.
    677695    <FilesMatch "\.(7z|avi|avif|apk|bin|bmp|bz2|class|css|csv|doc|docx|dmg|ejs|eot|eps|exe|flac|gif|gz|ico|iso|jar|jpg|jpeg|js|mid|midi|mkv|mp3|mp4|ogg|otf|pdf|pict|pls|png|ppt|pptx|ps|rar|svg|svgz|swf|tar|tif|tiff|ttf|webm|webp|woff|woff2|xls|xlsx|zip|zst)$">
    678         Header set Cache-Control "' . $cache_control . '"
     696        Header set Cache-Control "' . $static_cache_control . '"
    679697    </FilesMatch>
    680698
    681699    # [PERUBAHAN] Versi .gz dari bundle CSS/JS & font (lihat build_asset_bundle()
    682700    # dan relocate_font_for_bundle() di modules/class-wpsc-optimizer.php) —
    683     # kalau "Kompres Cache" aktif, tag <link>/<script> di HTML menunjuk
    684     # LANGSUNG ke file ".css.gz" / ".js.gz" / ".ttf.gz" / ".otf.gz", bukan
    685     # lagi ke nama plain-nya. Tanpa entri terpisah ini, aturan di atas
    686     # ("\.css$" dst.) tidak akan cocok dengan nama file yang berakhiran
    687     # ".gz" (regex "$" mensyaratkan akhir string persis di ekstensi itu),
    688     # jadi file yang justru paling sering diminta (versi terkompresi) malah
    689     # tidak kebagian Cache-Control sama sekali.
     701    # walau URL yang TERLIHAT di tag <link>/<script> selalu plain (lihat
     702    # catatan [REVERT] di build_asset_bundle()), file FISIK yang disajikan
     703    # lewat rewrite internal berakhiran ".css.gz" / ".js.gz" / ".ttf.gz" /
     704    # ".otf.gz". Tanpa entri terpisah ini, aturan di atas ("\.css$" dst.)
     705    # tidak akan cocok dengan nama file yang berakhiran ".gz" (regex "$"
     706    # mensyaratkan akhir string persis di ekstensi itu), jadi file yang
     707    # justru paling sering disajikan (versi terkompresi) malah tidak
     708    # kebagian Cache-Control sama sekali. Sama seperti di atas: 30 hari,
     709    # bukan no-cache.
    690710    <FilesMatch "\.(css|js|ttf|otf)\.gz$">
    691         Header set Cache-Control "' . $cache_control . '"
     711        Header set Cache-Control "' . $static_cache_control . '"
    692712    </FilesMatch>
    693713
Note: See TracChangeset for help on using the changeset viewer.