Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 55 additions & 13 deletions src/wp-admin/includes/class-wp-automatic-updater.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,24 +107,34 @@ public function is_allowed_dir( $dir ) {
}

/**
* Checks for version control checkouts.
* Discovers version control checkouts without applying filters.
*
* Checks for Subversion, Git, Mercurial, and Bazaar. It recursively looks up the
* filesystem to the top of the drive, erring on the side of detecting a VCS
* checkout somewhere.
* Checks for Subversion, Git, Mercurial, and Bazaar. It walks up from each
* relevant directory to the top of the drive, erring on the side of detecting
* a VCS checkout somewhere.
*
* ABSPATH is always checked in addition to whatever `$context` is (which may be the
* wp-content directory, for example). The underlying assumption is that if you are
* using version control *anywhere*, then you should be making decisions for
* how things get updated.
*
* @since 3.7.0
* @since 7.1.0
*
* @param string $context The filesystem path to check, in addition to ABSPATH.
* @return bool True if a VCS checkout was discovered at `$context` or ABSPATH,
* or anywhere higher. False otherwise.
* @return array{
* checkout: bool,
* check_dir: string,
* vcs_dir: string
* } {
* @type bool $checkout Whether a VCS checkout was discovered at `$context`
* or ABSPATH, or anywhere higher.
* @type string $check_dir Directory where the VCS metadata directory was found.
* Empty string when `$checkout` is false.
* @type string $vcs_dir Version control metadata directory name (e.g. `.git`).
* Empty string when `$checkout` is false.
* }
*/
public function is_vcs_checkout( $context ) {
public function get_vcs_checkout_details( $context ) {
$context_dirs = array( untrailingslashit( $context ) );
if ( ABSPATH !== $context ) {
$context_dirs[] = untrailingslashit( ABSPATH );
Expand All @@ -149,21 +159,53 @@ public function is_vcs_checkout( $context ) {

$check_dirs = array_unique( $check_dirs );
$checkout = false;
$check_dir = '';
$vcs_dir = '';

// Search all directories we've found for evidence of version control.
foreach ( $vcs_dirs as $vcs_dir ) {
foreach ( $check_dirs as $check_dir ) {
if ( ! $this->is_allowed_dir( $check_dir ) ) {
foreach ( $vcs_dirs as $vcs_dir_candidate ) {
foreach ( $check_dirs as $check_dir_candidate ) {
if ( ! $this->is_allowed_dir( $check_dir_candidate ) ) {
continue;
}

$checkout = is_dir( rtrim( $check_dir, '\\/' ) . "/$vcs_dir" );
$checkout = is_dir( rtrim( $check_dir_candidate, '\\/' ) . "/$vcs_dir_candidate" );
if ( $checkout ) {
$check_dir = $check_dir_candidate;
$vcs_dir = $vcs_dir_candidate;
break 2;
}
}
}

return array(
'checkout' => $checkout,
'check_dir' => $check_dir,
'vcs_dir' => $vcs_dir,
);
}

/**
* Checks for version control checkouts.
*
* Checks for Subversion, Git, Mercurial, and Bazaar. It recursively looks up the
* filesystem to the top of the drive, erring on the side of detecting a VCS
* checkout somewhere.
*
* ABSPATH is always checked in addition to whatever `$context` is (which may be the
* wp-content directory, for example). The underlying assumption is that if you are
* using version control *anywhere*, then you should be making decisions for
* how things get updated.
*
* @since 3.7.0
*
* @param string $context The filesystem path to check, in addition to ABSPATH.
* @return bool True if a VCS checkout was discovered at `$context` or ABSPATH,
* or anywhere higher. False otherwise.
*/
public function is_vcs_checkout( $context ) {
$details = $this->get_vcs_checkout_details( $context );

/**
* Filters whether the automatic updater should consider a filesystem
* location to be potentially managed by a version control system.
Expand All @@ -175,7 +217,7 @@ public function is_vcs_checkout( $context ) {
* @param string $context The filesystem context (a path) against which
* filesystem status should be checked.
*/
return apply_filters( 'automatic_updates_is_vcs_checkout', $checkout, $context );
return apply_filters( 'automatic_updates_is_vcs_checkout', $details['checkout'], $context );
}

/**
Expand Down
44 changes: 10 additions & 34 deletions src/wp-admin/includes/class-wp-site-health-auto-updates.php
Original file line number Diff line number Diff line change
Expand Up @@ -214,44 +214,20 @@ public function test_if_failed_update() {
* @return array The test results.
*/
public function test_vcs_abspath() {
$context_dirs = array( ABSPATH );
$vcs_dirs = array( '.svn', '.git', '.hg', '.bzr' );
$check_dirs = array();

foreach ( $context_dirs as $context_dir ) {
// Walk up from $context_dir to the root.
do {
$check_dirs[] = $context_dir;

// Once we've hit '/' or 'C:\', we need to stop. dirname will keep returning the input here.
if ( dirname( $context_dir ) === $context_dir ) {
break;
}

// Continue one level at a time.
} while ( $context_dir = dirname( $context_dir ) );
if ( ! class_exists( 'WP_Automatic_Updater' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-automatic-updater.php';
}

$check_dirs = array_unique( $check_dirs );
$updater = new WP_Automatic_Updater();
$checkout = false;

// Search all directories we've found for evidence of version control.
foreach ( $vcs_dirs as $vcs_dir ) {
foreach ( $check_dirs as $check_dir ) {
if ( ! $updater->is_allowed_dir( $check_dir ) ) {
continue;
}

$checkout = is_dir( rtrim( $check_dir, '\\/' ) . "/$vcs_dir" );
if ( $checkout ) {
break 2;
}
}
}
$updater = new WP_Automatic_Updater();
$details = $updater->get_vcs_checkout_details( ABSPATH );
$checkout = $details['checkout'];
$check_dir = $details['check_dir'];
$vcs_dir = $details['vcs_dir'];

/** This filter is documented in wp-admin/includes/class-wp-automatic-updater.php */
if ( $checkout && ! apply_filters( 'automatic_updates_is_vcs_checkout', true, ABSPATH ) ) {
$filtered = apply_filters( 'automatic_updates_is_vcs_checkout', $checkout, ABSPATH );

if ( $checkout && ! $filtered ) {
return array(
'description' => sprintf(
/* translators: 1: Folder name. 2: Version control directory. 3: Filter name. */
Expand Down
41 changes: 41 additions & 0 deletions tests/phpunit/tests/admin/wpAutomaticUpdater.php
Original file line number Diff line number Diff line change
Expand Up @@ -733,4 +733,45 @@ public function test_is_vcs_checkout_should_return_false_when_no_directories_are

$this->assertFalse( $updater_mock->is_vcs_checkout( get_temp_dir() ) );
}

/**
* Tests that `WP_Automatic_Updater::get_vcs_checkout_details()` reports no checkout
* when none of the checked directories are allowed.
*
* @ticket 47428
*
* @covers WP_Automatic_Updater::get_vcs_checkout_details
*/
public function test_get_vcs_checkout_details_should_report_no_checkout_when_no_directories_are_allowed() {
$updater_mock = $this->getMockBuilder( 'WP_Automatic_Updater' )
->setMethods( array( 'is_allowed_dir' ) )
->getMock();

$updater_mock->expects( $this->any() )->method( 'is_allowed_dir' )->willReturn( false );

$details = $updater_mock->get_vcs_checkout_details( get_temp_dir() );

$this->assertIsArray( $details );
$this->assertFalse( $details['checkout'] );
$this->assertSame( '', $details['check_dir'] );
$this->assertSame( '', $details['vcs_dir'] );
}

/**
* Tests that `is_vcs_checkout()` applies the same filter input as a direct
* `apply_filters()` call using raw discovery from `get_vcs_checkout_details()`.
*
* @ticket 47428
*
* @covers WP_Automatic_Updater::get_vcs_checkout_details
* @covers WP_Automatic_Updater::is_vcs_checkout
*/
public function test_is_vcs_checkout_matches_filtered_get_vcs_checkout_details_checkout() {
$context = ABSPATH;

$details = self::$updater->get_vcs_checkout_details( $context );
$expected = apply_filters( 'automatic_updates_is_vcs_checkout', $details['checkout'], $context );

$this->assertSame( $expected, self::$updater->is_vcs_checkout( $context ) );
}
}
Loading