rebuild_css( $locations, $display ); // When display mode changes, rebuild kept locations for the old display // so their stale inline/external options are cleared. $kept = array_intersect( $old_locations, $locations ); if ( ! empty( $kept ) && ! empty( $old_display ) && $old_display !== $display ) { $this->rebuild_css( array_values( $kept ), $old_display ); } $removed = array_diff( $old_locations, $locations ); if ( ! empty( $removed ) && ! empty( $old_display ) ) { $this->rebuild_css( array_values( $removed ), $old_display ); } } else { $position = get_post_meta( $post_id, '_ao_cc_position', true ); if ( ! empty( $position ) ) { $this->rebuild_scripts( $locations, $position, $type ); } $removed = array_diff( $old_locations, $locations ); if ( ! empty( $removed ) && ! empty( $old_position ) ) { $this->rebuild_scripts( $removed, $old_position, $type ); } } } /** * Rebuild every cache entry from scratch. * * Collects all unique location+display and location+position combinations * from both the stored location index (which may contain stale entries) and * all currently published posts, then rebuilds each one. Any location whose * aggregated CSS/JS is now empty will have its option deleted automatically * by the underlying rebuild_css / rebuild_scripts methods. * * @return void */ public function rebuild_all(): void { // --- CSS --- $css_combos = []; // Start from the stored index so stale entries from deleted posts get cleared. $stored_css = get_option( 'ao_cc_css_locations', [] ); foreach ( Custom_Code::VALID_DISPLAYS as $d ) { foreach ( array_keys( $stored_css[ $d ] ?? [] ) as $loc ) { $css_combos[ $d ][ $loc ] = 1; } } // Collect combos from published posts (index may be missing new entries). $css_posts = get_posts( [ 'post_type' => Custom_Code::POST_TYPE, 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', 'meta_query' => [ [ 'key' => '_ao_cc_type', 'value' => 'css' ] ], // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Admin-triggered full cache rebuild over the low-volume Custom Code post type, not a hot request-time query. ] ); update_postmeta_cache( $css_posts ); foreach ( $css_posts as $id ) { $locs = get_post_meta( $id, '_ao_cc_locations', true ); $display_raw = get_post_meta( $id, '_ao_cc_display', true ); $display = $display_raw !== '' ? $display_raw : 'inline'; if ( is_array( $locs ) ) { foreach ( $locs as $loc ) { $css_combos[ $display ][ $loc ] = 1; } } } // Sitewide is never tracked in the index — always rebuild it. $css_combos['inline']['sitewide'] = 1; $css_combos['external']['sitewide'] = 1; foreach ( $css_combos as $display => $locs_map ) { $this->rebuild_css( array_keys( $locs_map ), $display ); } // --- Scripts --- $script_combos = []; $stored_scripts = get_option( 'ao_cc_script_locations', [] ); $stored_html = get_option( 'ao_cc_html_locations', [] ); foreach ( Custom_Code::VALID_POSITIONS as $pos ) { foreach ( array_keys( $stored_scripts[ $pos ] ?? [] ) as $loc ) { $script_combos[ $pos ][ $loc ] = 1; } foreach ( array_keys( $stored_html[ $pos ] ?? [] ) as $loc ) { $script_combos[ $pos ][ $loc ] = 1; } } $script_posts = get_posts( [ 'post_type' => Custom_Code::POST_TYPE, 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Admin-triggered full cache rebuild over the low-volume Custom Code post type, not a hot request-time query. 'meta_query' => [ 'relation' => 'AND', [ 'key' => '_ao_cc_type', 'value' => [ 'js', 'html' ], 'compare' => 'IN', ], ], ] ); update_postmeta_cache( $script_posts ); foreach ( $script_posts as $id ) { $locs = get_post_meta( $id, '_ao_cc_locations', true ); $position = get_post_meta( $id, '_ao_cc_position', true ); if ( is_array( $locs ) && in_array( $position, Custom_Code::VALID_POSITIONS, true ) ) { foreach ( $locs as $loc ) { $script_combos[ $position ][ $loc ] = 1; } } } // Sitewide is never tracked in the index — always rebuild it. foreach ( Custom_Code::VALID_POSITIONS as $pos ) { $script_combos[ $pos ]['sitewide'] = 1; } foreach ( $script_combos as $position => $locs_map ) { $this->rebuild_scripts( array_keys( $locs_map ), $position, 'js' ); $this->rebuild_scripts( array_keys( $locs_map ), $position, 'html' ); } do_action( 'ao_cc_cache_rebuilt' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- ao_cc_ is this module's established hook prefix, used consistently throughout Custom Code. } /** * Rebuild the CSS cache for the given locations and display method. * * @param array $locations * @param string $display * @return void */ private function rebuild_css( array $locations, string $display ): void { if ( ! in_array( $display, Custom_Code::VALID_DISPLAYS, true ) || empty( $locations ) ) { return; } $filesystem = new Custom_Code_Filesystem(); $ao_cc_css_locations = get_option( 'ao_cc_css_locations', [] ); $all_css_posts = get_posts( [ 'post_type' => Custom_Code::POST_TYPE, 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Admin-triggered full cache rebuild over the low-volume Custom Code post type, not a hot request-time query. 'meta_query' => [ [ 'key' => '_ao_cc_type', 'value' => 'css', ], ], ] ); update_postmeta_cache( $all_css_posts ); foreach ( $locations as $location ) { if ( ! array_key_exists( $location, Custom_Code::ALL_LOCATIONS ) ) { continue; } $css_code = ''; foreach ( $all_css_posts as $css_id ) { $post_locations = get_post_meta( $css_id, '_ao_cc_locations', true ); $post_display_raw = get_post_meta( $css_id, '_ao_cc_display', true ); $post_display = $post_display_raw !== '' ? $post_display_raw : 'inline'; if ( ! is_array( $post_locations ) || ! in_array( $location, $post_locations, true ) || $post_display !== $display ) { continue; } $css_code = $this->append_code( $css_code, get_post_field( 'post_content', $css_id ) ); } if ( ! empty( $css_code ) ) { $autoload = 'sitewide' === $location ? 'yes' : 'no'; if ( 'inline' === $display ) { update_option( "ao_cc_css_{$location}_inline", $css_code, $autoload ); delete_option( "ao_cc_css_{$location}_external" ); $filesystem->delete_file( 'css', $location ); do_action( "ao_cc_css_updated_inline_{$location}", $css_code ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- ao_cc_ is this module's established hook prefix, used consistently throughout Custom Code. } elseif ( $filesystem->can_write( 'css', $location ) ) { $file_uri = $filesystem->create_external_file( $css_code, 'css', $location ); if ( $file_uri ) { update_option( "ao_cc_css_{$location}_external", $file_uri, $autoload ); delete_option( "ao_cc_css_{$location}_inline" ); } } else { $this->warning( 'Cannot write external CSS file.', [ 'location' => $location ], 'external_css_write_failed' ); } if ( 'sitewide' !== $location ) { $ao_cc_css_locations[ $display ][ $location ] = 1; } } else { if ( 'inline' === $display ) { delete_option( "ao_cc_css_{$location}_inline" ); } else { delete_option( "ao_cc_css_{$location}_external" ); $filesystem->delete_file( 'css', $location ); } if ( 'sitewide' !== $location ) { unset( $ao_cc_css_locations[ $display ][ $location ] ); } } } update_option( 'ao_cc_css_locations', $ao_cc_css_locations ); } /** * Rebuild the script/HTML cache for the given locations and position. * * @param array $locations * @param string $position * @param string $type 'js' or 'html'. * @return void */ private function rebuild_scripts( array $locations, string $position, string $type = 'js' ): void { if ( ! in_array( $position, Custom_Code::VALID_POSITIONS, true ) || empty( $locations ) ) { return; } $option_prefix = 'html' === $type ? 'ao_cc_html' : 'ao_cc_script'; $locations_key = 'html' === $type ? 'ao_cc_html_locations' : 'ao_cc_script_locations'; $ao_cc_script_locations = get_option( $locations_key, [] ); $all_script_posts = get_posts( [ 'post_type' => Custom_Code::POST_TYPE, 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Admin-triggered full cache rebuild over the low-volume Custom Code post type, not a hot request-time query. 'meta_query' => [ 'relation' => 'AND', [ 'key' => '_ao_cc_type', 'value' => $type, ], [ 'key' => '_ao_cc_position', 'value' => $position, ], ], ] ); update_postmeta_cache( $all_script_posts ); foreach ( $locations as $location ) { if ( ! array_key_exists( $location, Custom_Code::ALL_LOCATIONS ) ) { continue; } $script_code = ''; foreach ( $all_script_posts as $script_id ) { $post_locations = get_post_meta( $script_id, '_ao_cc_locations', true ); if ( ! is_array( $post_locations ) || ! in_array( $location, $post_locations, true ) ) { continue; } $script_code = $this->append_code( $script_code, get_post_field( 'post_content', $script_id ) ); } $autoload = 'sitewide' === $location ? 'yes' : 'no'; if ( ! empty( $script_code ) ) { update_option( "{$option_prefix}_{$position}_{$location}", $script_code, $autoload ); if ( 'sitewide' !== $location ) { $ao_cc_script_locations[ $position ][ $location ] = 1; } } else { delete_option( "{$option_prefix}_{$position}_{$location}" ); if ( 'sitewide' !== $location ) { unset( $ao_cc_script_locations[ $position ][ $location ] ); } } } update_option( $locations_key, $ao_cc_script_locations ); } /** * Append one snippet to an aggregate cache string with normalized line endings. * * @param string $aggregate Existing cached code. * @param string $content Snippet content to append. * @return string */ protected function append_code( string $aggregate, string $content ): string { $content = str_replace( [ "\r\n", "\r" ], "\n", $content ); if ( '' === $aggregate ) { return $content; } return rtrim( $aggregate ) . "\n" . ltrim( $content ); } /** * Rebuild the post-meta cache for post-ID-targeted snippets. * * A no-op on Free — specific-post targeting is a Pro-only capability. * Custom_Code_Cache_Pro overrides this with the real implementation. * * @param array $old_ids Post IDs targeted before this save. * @param array $new_ids Post IDs targeted after this save. * @return void */ public function rebuild_post_meta( array $old_ids, array $new_ids ): void { } }