-
-
Notifications
You must be signed in to change notification settings - Fork 78
fix: purge Divi clone CSS cache #1352
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
Changes from all commits
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,116 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <?php | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Tests for the General_Compat class. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @package WP_Ultimo | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @subpackage Tests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace WP_Ultimo\Tests; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use WP_Ultimo\Compat\General_Compat; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use WP_UnitTestCase; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Test general compatibility fixes. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class General_Compat_Test extends WP_UnitTestCase { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+17
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Match the repo's PHP test-file conventions. This file is missing the standard Suggested cleanup <?php
+defined('ABSPATH') || exit;
+
/**
* Tests for the General_Compat class.
*
* `@package` WP_Ultimo
* `@subpackage` Tests
*/
-namespace WP_Ultimo\Tests;
+namespace WP_Ultimo\Compat;
-use WP_Ultimo\Compat\General_Compat;
use WP_UnitTestCase;As per coding guidelines, 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Cache directories created by a test. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @var array | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private $cache_dirs = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Clean up generated cache fixtures. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public function tearDown(): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach ($this->cache_dirs as $cache_dir) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $this->remove_directory($cache_dir); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parent::tearDown(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Test init registers the Divi cache purge duplication hook. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public function test_init_registers_divi_cache_purge_hook(): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $instance = General_Compat::get_instance(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $instance->init(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $this->assertNotFalse(has_action('wu_duplicate_site', [$instance, 'clear_divi_static_css_cache'])); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Test Divi et-cache files are deleted only for the cloned site. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public function test_clear_divi_static_css_cache_deletes_cloned_site_cache_only(): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( ! is_multisite()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $this->markTestSkipped('Divi cache purge tests require multisite'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $blog_id = self::factory()->blog->create(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $other_blog_id = self::factory()->blog->create(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $network_id = (int) get_current_network_id(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $cache_root = trailingslashit(WP_CONTENT_DIR) . 'et-cache'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $cache_dir = trailingslashit($cache_root) . $network_id . '/' . $blog_id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $other_dir = trailingslashit($cache_root) . $network_id . '/' . $other_blog_id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $this->cache_dirs = [$cache_dir, $other_dir]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| wp_mkdir_p($cache_dir . '/9'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| wp_mkdir_p($other_dir . '/9'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- Test fixture setup. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| file_put_contents($cache_dir . '/9/et-core-unified-deferred-9.min.css', 'stale divi css'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- Test fixture setup. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| file_put_contents($other_dir . '/9/et-core-unified-deferred-9.min.css', 'other divi css'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| General_Compat::get_instance()->clear_divi_static_css_cache(['site_id' => $blog_id]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $this->assertDirectoryDoesNotExist($cache_dir); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $this->assertDirectoryExists($other_dir); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Recursively remove a test directory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param string $dir Directory path. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return void | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private function remove_directory(string $dir): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ('' === $dir || ! is_dir($dir)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $iterator = new \RecursiveIteratorIterator( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| \RecursiveIteratorIterator::CHILD_FIRST | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach ($iterator as $file) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $path = $file->getRealPath(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (false === $path) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ($file->isDir()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir, WordPress.PHP.NoSilencedErrors.Discouraged -- Test fixture cleanup. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @rmdir($path); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| wp_delete_file($path); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir, WordPress.PHP.NoSilencedErrors.Discouraged -- Test fixture cleanup. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @rmdir($dir); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Does PHPSplFileInfo::getRealPath()resolve symlink targets, and doesRecursiveDirectoryIteratorstill yield symlink entries whenFilesystemIterator::FOLLOW_SYMLINKSis not set?💡 Result:
Yes, PHP's SplFileInfo::getRealPath resolves all symbolic links [1][2]. It expands symlinks, resolves relative references (like /./ or /../), and returns the canonicalized absolute path to the file [3][2]. If the file does not exist, the method returns false [1][2]. Regarding RecursiveDirectoryIterator, it does still yield symlink entries even when the FilesystemIterator::FOLLOW_SYMLINKS flag is not set [4][5]. The iterator will return the symlink itself as an entry in the iteration [6]. The FilesystemIterator::FOLLOW_SYMLINKS flag controls whether the iterator should follow these symlinks to traverse into the target directories, but it does not prevent the iterator from encountering and yielding the symlink files themselves [7][4].
Citations:
Skip symlinks when purging cache contents to prevent path escape
SplFileInfo::getRealPath()resolves symlink targets, andRecursiveDirectoryIteratorstill yields symlink entries even withoutFOLLOW_SYMLINKS. A symlink inside$real_cache_dircan therefore makewp_delete_file()act on a path outside the cache tree. UsegetPathname(), skip links, and re-check each entry against a$real_cache_dirprefix before deleting.Suggested hardening
$real_cache_dir = untrailingslashit(wp_normalize_path($real_cache_dir)); $real_cache_root = untrailingslashit(wp_normalize_path($real_cache_root)); if ($real_cache_root === $real_cache_dir || 0 !== strpos(trailingslashit($real_cache_dir), trailingslashit($real_cache_root))) { return; } + + $real_cache_dir_prefix = trailingslashit($real_cache_dir); $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($real_cache_dir, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST ); foreach ($iterator as $file) { - $path = $file->getRealPath(); + if ($file->isLink()) { + continue; + } - if (false === $path) { + $path = untrailingslashit(wp_normalize_path($file->getPathname())); + + if ('' === $path || 0 !== strpos(trailingslashit($path), $real_cache_dir_prefix)) { continue; } if ($file->isDir()) {🤖 Prompt for AI Agents