Skip to content
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

Added before_create_directory and after_create_directory hooks. #5766

Open
wants to merge 11 commits into
base: trunk
Choose a base branch
from
22 changes: 22 additions & 0 deletions src/wp-includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2027,6 +2027,7 @@ function wp_get_original_referer() {
* Will attempt to set permissions on folders.
*
* @since 2.0.1
* @since 6.5.0 Added `before_create_directory` and `after_create_directory` hooks.
*
* @param string $target Full path to attempt to create.
* @return bool Whether the path was created. True if path already exists.
Expand Down Expand Up @@ -2079,8 +2080,29 @@ function wp_mkdir_p( $target ) {
$dir_perms = 0777;
}

/**
* Fires before the directory creation.
*
* @since 6.5.0
*
* @param string $target Full path to attempt to create.
* @param int $dir_perms Directory permissions.
*/
do_action( 'before_create_directory', $target, $dir_perms );

if ( @mkdir( $target, $dir_perms, true ) ) {

/**
* Fires after the directory is created and the permissions are set.
*
*
* @since 6.5 The action was added.
*
* @param strin $target Full path to created directory
* @param int $dir_perms Directory permissions
*/
do_action( 'after_create_directory', $target, $dir_perms );

/*
* If a umask is set that modifies $dir_perms, we'll have to re-set
* the $dir_perms correctly with chmod()
Expand Down
171 changes: 171 additions & 0 deletions tests/phpunit/tests/filesystem/wpMkdirP.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<?php

/**
* Tests wp_mkdir_p().
*
* @group functions.php
*
* @covers ::wp_mkdir_p
*/
class Tests_Filesystem_WpMkdirP extends WP_UnitTestCase {
/**
* The directory in which to create other directories.
*
* @var string
*/
private static $test_directory;

/**
* Sets and creates the test directory before any tests run.
*/
public static function set_up_before_class() {
parent::set_up_before_class();

self::$test_directory = realpath( DIR_TESTDATA ) . '/test_wp_mkdir_p/';
mkdir( self::$test_directory );
}

/**
* Deletes the contents of the test directory after each test runs.
*/
public function tear_down() {
foreach ( $this->files_in_dir( self::$test_directory ) as $file ) {
$this->unlink( $file );
}

$matched_dirs = $this->scandir( self::$test_directory );
foreach ( array_reverse( $matched_dirs ) as $dir ) {
rmdir( $dir );
}

parent::tear_down();
}

/**
* Deletes the test directory after all tests have run.
*/
public static function tear_down_after_class() {
rmdir( self::$test_directory );

parent::tear_down_after_class();
}

/**
* Tests that `wp_mkdir_p()` fires an action.
*
* @ticket 44083
*
* @dataProvider data_actions
*
* @param string $hook_name The name of the action hook.
*/
public function test_wp_mkdir_p_should_fire_action( $hook_name ) {
$action = new MockAction();
add_action( $hook_name, array( $action, 'action' ) );

wp_mkdir_p( self::$test_directory . $hook_name );

$this->assertSame( 1, $action->get_call_count() );
}

/**
* Tests that `wp_mkdir_p()` does not fire an action when a file exists of the same name.
*
* @ticket 44083
*
* @dataProvider data_actions
*
* @param string $hook_name The name of the action hook.
*/
public function test_wp_mkdir_p_should_not_fire_action_when_a_file_exists_of_the_same_name( $hook_name ) {
$action = new MockAction();
add_action( $hook_name, array( $action, 'action' ) );

$target = self::$test_directory . $hook_name;

// Force a failure.
$this->touch( $target );

wp_mkdir_p( $target );

$this->assertSame( 0, $action->get_call_count() );
}

/**
* Tests that `wp_mkdir_p()` does not fire an action when the target directory contains '../'.
*
* @ticket 44083
*
* @dataProvider data_actions
*
* @param string $hook_name The name of the action hook.
*/
public function test_wp_mkdir_p_should_not_fire_action_when_the_target_contains_path_traversal( $hook_name ) {
$action = new MockAction();
add_action( $hook_name, array( $action, 'action' ) );

// Force a failure by not using `realpath()`.
$target = DIR_TESTDATA . "/test_wp_mkdir_p/$hook_name/";

wp_mkdir_p( $target );

$this->assertSame( 0, $action->get_call_count() );
}

/**
* Tests that `wp_mkdir_p()` does not fire an action when the target directory contains '..DIRECTORY_SEPARATOR'.
*
* @ticket 44083
*
* @dataProvider data_actions
*
* @param string $hook_name The name of the action hook.
*/
public function test_wp_mkdir_p_should_not_fire_action_when_the_target_contains_path_traversal_with_directory_separator( $hook_name ) {
$action = new MockAction();
add_action( $hook_name, array( $action, 'action' ) );

// Force a failure.
$target = str_replace( '../', '..' . DIRECTORY_SEPARATOR, DIR_TESTDATA ) . "/test_wp_mkdir_p/$hook_name";

wp_mkdir_p( $target );

$this->assertSame( 0, $action->get_call_count() );
}

/**
* Data provider.
*
* @throws Exception
*
* @return array[]
*/
public function data_actions() {
return self::text_array_to_dataprovider( array( 'before_create_directory', 'after_create_directory' ) );
}

/**
* Tests that `wp_mkdir_p()` does not fire the 'after_create_directory' action when `mkdir()` fails.
*
* @ticket 44083
*/
public function test_wp_mkdir_p_should_not_fire_the_after_create_directory_action_when_mkdir_fails() {
$action = new MockAction();
add_action( 'after_create_directory', array( $action, 'action' ) );

add_action(
'before_create_directory',
function ( $target ) {
/*
* Force a failure by creating a file of the same name
* just before `mkdir()` runs.
*/
$this->touch( $target );
}
);

wp_mkdir_p( self::$test_directory . 'after_create_directory' );

$this->assertSame( 0, $action->get_call_count() );
}
}
Loading