-
Notifications
You must be signed in to change notification settings - Fork 235
Fix #2698: Prevent unnecessary cache purges for hierarchical pages #7613
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 8 commits
3764e57
725bd16
3efad6d
89f8361
abd1625
b987f77
2b90162
ea371e1
f1532cb
0efd7a1
6bd142e
ce12709
29a10b9
d87b9fe
82a67d4
8b169a4
2a4b5d5
d6e36f6
8217938
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -131,11 +131,15 @@ | |
| $purge_urls[] = $author_url; | ||
| } | ||
|
|
||
| // Add all parents. | ||
| $parents = get_post_ancestors( $post_id ); | ||
| if ( (bool) $parents ) { | ||
| foreach ( $parents as $parent_id ) { | ||
| $purge_urls[] = get_permalink( $parent_id ); | ||
| $post_name = get_post_field( 'post_name', $post_id ); | ||
| // If the slug has changed, add all children pages with the new slug. | ||
| if ( $post_name != $post_data['post_name'] ) { | ||
|
Check warning on line 136 in inc/common/purge.php
|
||
| // Add all children. | ||
| $children = rocket_get_all_descendant( $post_id ); | ||
| if ( (bool) $children ) { | ||
| foreach ( $children as $child_id ) { | ||
| $purge_urls[] = get_permalink( $child_id ); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -146,6 +150,27 @@ | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Recursively retrieves all descendant page IDs for a given parent page ID. | ||
| * | ||
| * @param int $parent_id The ID of the parent page. | ||
| * @return int[] A flat array with all descendant page IDs (children, grandchildren, etc.). | ||
| */ | ||
| function rocket_get_all_descendant( $parent_id ) { | ||
| // 'child_of' retrieves all descendants, not just direct children. | ||
| $all_descendants = get_pages( | ||
| [ 'child_of' => $parent_id ] | ||
|
Check notice on line 162 in inc/common/purge.php
|
||
| ); | ||
|
|
||
| if ( empty( $all_descendants ) ) { | ||
| return []; | ||
| } | ||
|
|
||
| // Convert the array of page objects to an array of IDs only. | ||
| return wp_list_pluck( $all_descendants, 'ID' ); | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * Update cache when a post is updated or commented | ||
| * | ||
|
|
@@ -636,6 +661,19 @@ | |
| if ( empty( $post_name ) ) { | ||
| return; | ||
| } | ||
| rocket_clean_files( get_the_permalink( $post_id ) ); | ||
|
|
||
| $purge_urls = []; | ||
| $purge_urls[] = get_the_permalink( $post_id ); | ||
|
|
||
| // Clear cache for all child pages. | ||
| $children = rocket_get_all_descendant( $post_id ); | ||
| if ( (bool) $children ) { | ||
| foreach ( $children as $child_id ) { | ||
| $purge_urls[] = get_the_permalink( $child_id ); | ||
| } | ||
| } | ||
|
|
||
| rocket_clean_files( $purge_urls ); | ||
|
Comment on lines
+665
to
+676
|
||
|
|
||
| } | ||
| add_action( 'pre_post_update', 'rocket_clean_post_cache_on_slug_change', PHP_INT_MAX, 2 ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -530,6 +530,28 @@ | |
| return get_rocket_parse_url( $new_permalink ); | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * Count the number of path segments in a URL. | ||
| * | ||
| * Parses the given URL, extracts its path component, trims leading/trailing | ||
| * slashes, and counts the remaining segments separated by '/'. | ||
| * | ||
| * Examples: | ||
| * - "https://example.com/" => 0 | ||
| * - "https://example.com/a/b/c" => 3 | ||
| * - "/a/b/" => 2 | ||
| * | ||
| * @param string $url The URL (absolute or relative) to analyze. | ||
| * @return int Number of segments in the URL path (0 if empty or no path). | ||
| */ | ||
| function rocket_count_path_segments(string $url): int { | ||
|
Check notice on line 548 in inc/functions/files.php
|
||
| $path = parse_url($url, PHP_URL_PATH) ?? ''; | ||
|
Check notice on line 549 in inc/functions/files.php
|
||
| $path = trim($path, '/'); // "/" -> "" | ||
|
Check notice on line 550 in inc/functions/files.php
|
||
| if ($path === '') return 0; | ||
|
Check notice on line 551 in inc/functions/files.php
|
||
| return count(explode('/', $path)); // "a/b/c" -> 3 | ||
|
Check notice on line 552 in inc/functions/files.php
|
||
Jeroen-1978 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /** | ||
| * Delete one or several cache files. | ||
| * | ||
|
|
@@ -563,6 +585,24 @@ | |
| $filesystem = rocket_direct_filesystem(); | ||
| } | ||
|
|
||
| // Sort: most segments first (deepest URLs first). | ||
| usort( | ||
| $urls, | ||
| static function ( $a, $b ) { | ||
| $da = rocket_count_path_segments( (string) $a ); | ||
| $db = rocket_count_path_segments( (string) $b ); | ||
|
|
||
| // First by depth (descending) | ||
| if ( $da !== $db ) { | ||
| return $db <=> $da; | ||
| } | ||
|
|
||
| // If depth is equal, alphabetically | ||
| return strcmp( (string) $a, (string) $b ); | ||
| } | ||
| ); | ||
|
Comment on lines
+592
to
+607
|
||
|
|
||
|
|
||
| if ( $run_actions ) { | ||
| /** | ||
| * Fires before all cache files are deleted. | ||
|
|
@@ -575,6 +615,7 @@ | |
| } | ||
|
|
||
| foreach ( $urls as $url_key => $url ) { | ||
|
|
||
| if ( $run_actions ) { | ||
| /** | ||
| * Fires before the cache file is deleted. | ||
|
|
@@ -591,55 +632,85 @@ | |
|
|
||
| $parsed_url = get_rocket_parse_url( $url ); | ||
|
|
||
| if ( ! empty( $parsed_url['host'] ) ) { | ||
| foreach ( _rocket_get_cache_dirs( $parsed_url['host'], $cache_path ) as $dir ) { | ||
| // Decode url path. | ||
| $url_chunks = explode( '/', $parsed_url['path'] ); | ||
| $matches = preg_grep( '/%/', $url_chunks ); | ||
| if ( empty( $parsed_url['host'] ) ) { | ||
| continue; | ||
| } | ||
|
|
||
| if ( ! empty( $matches ) ) { | ||
| $parsed_url['path'] = rawurldecode( $parsed_url['path'] ); | ||
| } | ||
| foreach ( _rocket_get_cache_dirs( $parsed_url['host'], $cache_path ) as $dir ) { | ||
| // Decode url path. | ||
| $url_chunks = explode( '/', $parsed_url['path'] ); | ||
| $matches = preg_grep( '/%/', $url_chunks ); | ||
|
|
||
| // Encode Non-latin characters if found in url path. | ||
| if ( false !== preg_match_all( '/(?<non_latin>[^\x00-\x7F]+)/', $parsed_url['path'], $matches ) ) { | ||
| $cb_encode_non_latin = function ( $non_latin ) { | ||
| return strtolower( rawurlencode( $non_latin ) ); | ||
| }; | ||
| if ( ! empty( $matches ) ) { | ||
| $parsed_url['path'] = rawurldecode( $parsed_url['path'] ); | ||
| } | ||
|
|
||
| $parsed_url['path'] = str_replace( $matches['non_latin'], array_map( $cb_encode_non_latin, $matches['non_latin'] ), $parsed_url['path'] ); | ||
| } | ||
| // Encode Non-latin characters if found in url path. | ||
| if ( false !== preg_match_all( '/(?<non_latin>[^\x00-\x7F]+)/', $parsed_url['path'], $matches ) ) { | ||
| $cb_encode_non_latin = function ( $non_latin ) { | ||
| return strtolower( rawurlencode( $non_latin ) ); | ||
| }; | ||
|
|
||
| $entry = $dir . $parsed_url['path']; | ||
|
|
||
| // For regex we use it for file names only, and it should include the * character. | ||
| if ( str_contains( $entry, '*' ) ) { | ||
| $regex_part = basename( $entry ); | ||
| $search_dir = str_replace( $regex_part, '', $entry ); | ||
| $matched_files = _rocket_get_dir_files_by_regex( $search_dir, '#' . $regex_part . '#i' ); | ||
| foreach ( $matched_files as $item ) { | ||
| $current_file = $item->getPath() . DIRECTORY_SEPARATOR . $item->getFilename(); | ||
| if ( $filesystem->exists( $current_file ) ) { | ||
| $filesystem->delete( $current_file ); | ||
| } | ||
| $parsed_url['path'] = str_replace( $matches['non_latin'], array_map( $cb_encode_non_latin, $matches['non_latin'] ), $parsed_url['path'] ); | ||
| } | ||
|
|
||
| $entry = $dir . $parsed_url['path']; | ||
|
|
||
| // For regex we use it for file names only, and it should include the * character. | ||
| if ( str_contains( $entry, '*' ) ) { | ||
| $regex_part = basename( $entry ); | ||
| $search_dir = str_replace( $regex_part, '', $entry ); | ||
| $matched_files = _rocket_get_dir_files_by_regex( $search_dir, '#' . $regex_part . '#i' ); | ||
| foreach ( $matched_files as $item ) { | ||
| $current_file = $item->getPath() . DIRECTORY_SEPARATOR . $item->getFilename(); | ||
| if ( $filesystem->exists( $current_file ) ) { | ||
| $filesystem->delete( $current_file ); | ||
| } | ||
| // Remove the regex part from the url. | ||
| $url = str_replace( $regex_part, '', $url ); | ||
| $urls[ $url_key ] = $url; | ||
| } | ||
| // Remove the regex part from the url. | ||
| $url = str_replace( $regex_part, '', $url ); | ||
| $urls[ $url_key ] = $url; | ||
| } | ||
|
|
||
| // Skip if the dir/file does not exist. | ||
| if ( ! $filesystem->exists( $entry ) ) { | ||
| continue; | ||
| // Skip if the dir/file does not exist. | ||
| if ( ! $filesystem->exists( $entry ) ) { | ||
| continue; | ||
| } | ||
|
|
||
| if ( ! $filesystem->is_dir( $entry ) ) { | ||
| $filesystem->delete( $entry ); | ||
| continue; | ||
| } | ||
|
|
||
| // Check whether the directory contains subfolders (vs only files). | ||
| $has_subdirs = false; | ||
| try { | ||
| foreach ( new FilesystemIterator( $entry, FilesystemIterator::SKIP_DOTS ) as $child ) { | ||
| if ( $child->isDir() ) { | ||
| $has_subdirs = true; | ||
| break; | ||
| } | ||
| } | ||
| } catch ( Exception $e ) { | ||
| // If we can't inspect the directory, be conservative and only delete files. | ||
| $has_subdirs = true; | ||
| } | ||
|
|
||
| if ( $filesystem->is_dir( $entry ) ) { | ||
| rocket_rrmdir( $entry, [], $filesystem ); | ||
| } else { | ||
| $filesystem->delete( $entry ); | ||
| if ( ! $has_subdirs ) { | ||
| // Directory contains only files: remove it entirely. | ||
| rocket_rrmdir( $entry, [], $filesystem ); | ||
| continue; | ||
| } | ||
|
|
||
| // Directory contains subfolders: delete only the files in the top-level. | ||
| foreach ( _rocket_get_dir_files_by_regex( $entry, '#.+#' ) as $child ) { | ||
| if ( $child->isFile() ) { | ||
|
Comment on lines
+688
to
+710
|
||
| $filesystem->delete( $child->getPathname() ); | ||
| } | ||
| } | ||
|
|
||
| } | ||
|
|
||
| if ( $run_actions ) { | ||
| /** | ||
| * Fires after the cache file is deleted. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.