Initial Drupal 11 with DDEV setup
This commit is contained in:
300
web/core/tests/Drupal/BuildTests/QuickStart/QuickStartTest.php
Normal file
300
web/core/tests/Drupal/BuildTests/QuickStart/QuickStartTest.php
Normal file
@ -0,0 +1,300 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\BuildTests\QuickStart;
|
||||
|
||||
use Drupal\sqlite\Driver\Database\sqlite\Install\Tasks;
|
||||
use Drupal\BuildTests\Framework\BuildTestBase;
|
||||
use Drupal\Core\Test\TestDatabase;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\PreserveGlobalState;
|
||||
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
|
||||
use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
/**
|
||||
* Tests the quick-start commands.
|
||||
*
|
||||
* These tests are run in a separate process because they load Drupal code via
|
||||
* an include.
|
||||
*/
|
||||
#[Group('Command')]
|
||||
#[PreserveGlobalState(FALSE)]
|
||||
#[RequiresPhpExtension('pdo_sqlite')]
|
||||
#[RunTestsInSeparateProcesses]
|
||||
class QuickStartTest extends BuildTestBase {
|
||||
|
||||
/**
|
||||
* The PHP executable path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $php;
|
||||
|
||||
/**
|
||||
* A test database object.
|
||||
*
|
||||
* @var \Drupal\Core\Test\TestDatabase
|
||||
*/
|
||||
protected $testDb;
|
||||
|
||||
/**
|
||||
* The Drupal root directory.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$php_executable_finder = new PhpExecutableFinder();
|
||||
$this->php = $php_executable_finder->find();
|
||||
$this->root = dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__)), 2);
|
||||
chdir($this->root);
|
||||
if (!is_writable("{$this->root}/sites/simpletest")) {
|
||||
$this->markTestSkipped('This test requires a writable sites/simpletest directory');
|
||||
}
|
||||
// Get a lock and a valid site path.
|
||||
$this->testDb = new TestDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function tearDown(): void {
|
||||
if ($this->testDb) {
|
||||
$test_site_directory = $this->root . DIRECTORY_SEPARATOR . $this->testDb->getTestSitePath();
|
||||
if (file_exists($test_site_directory)) {
|
||||
// @todo use the tear down command from
|
||||
// https://www.drupal.org/project/drupal/issues/2926633
|
||||
// Delete test site directory.
|
||||
$this->fileUnmanagedDeleteRecursive($test_site_directory, [
|
||||
BrowserTestBase::class,
|
||||
'filePreDeleteCallback',
|
||||
]);
|
||||
}
|
||||
}
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the quick-start command.
|
||||
*/
|
||||
public function testQuickStartCommand(): void {
|
||||
$sqlite = (new \PDO('sqlite::memory:'))->query('select sqlite_version()')->fetch()[0];
|
||||
if (version_compare($sqlite, Tasks::SQLITE_MINIMUM_VERSION) < 0) {
|
||||
$this->markTestSkipped();
|
||||
}
|
||||
|
||||
// Install a site using the standard profile to ensure the one time login
|
||||
// link generation works.
|
||||
|
||||
$install_command = [
|
||||
$this->php,
|
||||
'core/scripts/drupal',
|
||||
'quick-start',
|
||||
'standard',
|
||||
"--site-name='Test site {$this->testDb->getDatabasePrefix()}'",
|
||||
'--suppress-login',
|
||||
];
|
||||
$process = new Process($install_command, NULL, ['DRUPAL_DEV_SITE_PATH' => $this->testDb->getTestSitePath()]);
|
||||
$process->setTimeout(500);
|
||||
$process->start();
|
||||
$guzzle = new Client();
|
||||
$port = FALSE;
|
||||
$process->waitUntil(function ($type, $output) use (&$port) {
|
||||
if (preg_match('/127.0.0.1:(\d+)/', $output, $match)) {
|
||||
$port = $match[1];
|
||||
return TRUE;
|
||||
}
|
||||
});
|
||||
// The progress bar uses STDERR to write messages.
|
||||
$this->assertStringContainsString('Congratulations, you installed Drupal!', $process->getErrorOutput());
|
||||
// Ensure the command does not trigger any PHP deprecations.
|
||||
$this->assertStringNotContainsString('Deprecated', $process->getErrorOutput());
|
||||
$this->assertNotFalse($port, "Web server running on port $port");
|
||||
|
||||
// Give the server a couple of seconds to be ready.
|
||||
sleep(2);
|
||||
$this->assertStringContainsString("127.0.0.1:$port/user/reset/1/", $process->getOutput());
|
||||
|
||||
// Generate a cookie so we can make a request against the installed site.
|
||||
define('DRUPAL_TEST_IN_CHILD_SITE', FALSE);
|
||||
chmod($this->testDb->getTestSitePath(), 0755);
|
||||
$cookieJar = CookieJar::fromArray([
|
||||
'SIMPLETEST_USER_AGENT' => drupal_generate_test_ua($this->testDb->getDatabasePrefix()),
|
||||
], '127.0.0.1');
|
||||
|
||||
$response = $guzzle->get('http://127.0.0.1:' . $port, ['cookies' => $cookieJar]);
|
||||
$content = (string) $response->getBody();
|
||||
$this->assertStringContainsString('Test site ' . $this->testDb->getDatabasePrefix(), $content);
|
||||
|
||||
// Stop the web server.
|
||||
$process->stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the quick-start commands.
|
||||
*/
|
||||
public function testQuickStartInstallAndServerCommands(): void {
|
||||
$sqlite = (new \PDO('sqlite::memory:'))->query('select sqlite_version()')->fetch()[0];
|
||||
if (version_compare($sqlite, Tasks::SQLITE_MINIMUM_VERSION) < 0) {
|
||||
$this->markTestSkipped();
|
||||
}
|
||||
|
||||
// Install a site.
|
||||
$install_command = [
|
||||
$this->php,
|
||||
'core/scripts/drupal',
|
||||
'install',
|
||||
'minimal',
|
||||
"--password='secret'",
|
||||
"--site-name='Test site {$this->testDb->getDatabasePrefix()}'",
|
||||
];
|
||||
$install_process = new Process($install_command, NULL, ['DRUPAL_DEV_SITE_PATH' => $this->testDb->getTestSitePath()]);
|
||||
$install_process->setTimeout(500);
|
||||
$result = $install_process->run();
|
||||
// The progress bar uses STDERR to write messages.
|
||||
$this->assertStringContainsString('Congratulations, you installed Drupal!', $install_process->getErrorOutput());
|
||||
$this->assertStringContainsString("Password: 'secret'", $install_process->getOutput());
|
||||
$this->assertSame(0, $result);
|
||||
|
||||
// Run the PHP built-in webserver.
|
||||
$server_command = [
|
||||
$this->php,
|
||||
'core/scripts/drupal',
|
||||
'server',
|
||||
'--suppress-login',
|
||||
];
|
||||
$server_process = new Process($server_command, NULL, ['DRUPAL_DEV_SITE_PATH' => $this->testDb->getTestSitePath()]);
|
||||
$server_process->start();
|
||||
$guzzle = new Client();
|
||||
$port = FALSE;
|
||||
$server_process->waitUntil(function ($type, $output) use (&$port) {
|
||||
if (preg_match('/127.0.0.1:(\d+)\/user\/reset\/1\//', $output, $match)) {
|
||||
$port = $match[1];
|
||||
return TRUE;
|
||||
}
|
||||
});
|
||||
$this->assertEquals('', $server_process->getErrorOutput());
|
||||
$this->assertStringContainsString("127.0.0.1:$port/user/reset/1/", $server_process->getOutput());
|
||||
$this->assertNotFalse($port, "Web server running on port $port");
|
||||
|
||||
// Give the server a couple of seconds to be ready.
|
||||
sleep(2);
|
||||
|
||||
// Generate a cookie so we can make a request against the installed site.
|
||||
define('DRUPAL_TEST_IN_CHILD_SITE', FALSE);
|
||||
chmod($this->testDb->getTestSitePath(), 0755);
|
||||
$cookieJar = CookieJar::fromArray([
|
||||
'SIMPLETEST_USER_AGENT' => drupal_generate_test_ua($this->testDb->getDatabasePrefix()),
|
||||
], '127.0.0.1');
|
||||
|
||||
$response = $guzzle->get('http://127.0.0.1:' . $port, ['cookies' => $cookieJar]);
|
||||
$content = (string) $response->getBody();
|
||||
$this->assertStringContainsString('Test site ' . $this->testDb->getDatabasePrefix(), $content);
|
||||
|
||||
// Try to re-install over the top of an existing site.
|
||||
$install_command = [
|
||||
$this->php,
|
||||
'core/scripts/drupal',
|
||||
'install',
|
||||
'testing',
|
||||
"--site-name='Test another site {$this->testDb->getDatabasePrefix()}'",
|
||||
];
|
||||
$install_process = new Process($install_command, NULL, ['DRUPAL_DEV_SITE_PATH' => $this->testDb->getTestSitePath()]);
|
||||
$install_process->setTimeout(500);
|
||||
$result = $install_process->run();
|
||||
$this->assertStringContainsString('Drupal is already installed.', $install_process->getOutput());
|
||||
$this->assertSame(0, $result);
|
||||
|
||||
// Ensure the site name has not changed.
|
||||
$response = $guzzle->get('http://127.0.0.1:' . $port, ['cookies' => $cookieJar]);
|
||||
$content = (string) $response->getBody();
|
||||
$this->assertStringContainsString('Test site ' . $this->testDb->getDatabasePrefix(), $content);
|
||||
|
||||
// Stop the web server.
|
||||
$server_process->stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the install command with an invalid profile.
|
||||
*/
|
||||
public function testQuickStartCommandProfileValidation(): void {
|
||||
// Install a site using the standard profile to ensure the one time login
|
||||
// link generation works.
|
||||
$install_command = [
|
||||
$this->php,
|
||||
'core/scripts/drupal',
|
||||
'quick-start',
|
||||
'umami',
|
||||
"--site-name='Test site {$this->testDb->getDatabasePrefix()}' --suppress-login",
|
||||
];
|
||||
$process = new Process($install_command, NULL, ['DRUPAL_DEV_SITE_PATH' => $this->testDb->getTestSitePath()]);
|
||||
$process->run();
|
||||
$this->assertMatchesRegularExpression("/'umami' is not a valid install profile or recipe\. Did you mean \W*'demo_umami'?/", $process->getErrorOutput());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the server command when there is no installation.
|
||||
*/
|
||||
public function testServerWithNoInstall(): void {
|
||||
$server_command = [
|
||||
$this->php,
|
||||
'core/scripts/drupal',
|
||||
'server',
|
||||
'--suppress-login',
|
||||
];
|
||||
$server_process = new Process($server_command, NULL, ['DRUPAL_DEV_SITE_PATH' => $this->testDb->getTestSitePath()]);
|
||||
$server_process->run();
|
||||
$this->assertStringContainsString('No installation found. Use the \'install\' command.', $server_process->getErrorOutput());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all files and directories in the specified path recursively.
|
||||
*
|
||||
* Note this method has no dependencies on Drupal core to ensure that the
|
||||
* test site can be torn down even if something in the test site is broken.
|
||||
*
|
||||
* @param string $path
|
||||
* A string containing either a URI or a file or directory path.
|
||||
* @param callable $callback
|
||||
* (optional) Callback function to run on each file prior to deleting it and
|
||||
* on each directory prior to traversing it. For example, can be used to
|
||||
* modify permissions.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE for success or if path does not exist, FALSE in the event of an
|
||||
* error.
|
||||
*
|
||||
* @see \Drupal\Core\File\FileSystemInterface::deleteRecursive()
|
||||
*/
|
||||
protected function fileUnmanagedDeleteRecursive($path, $callback = NULL): bool {
|
||||
if (isset($callback)) {
|
||||
call_user_func($callback, $path);
|
||||
}
|
||||
if (is_dir($path)) {
|
||||
$dir = dir($path);
|
||||
while (($entry = $dir->read()) !== FALSE) {
|
||||
if ($entry == '.' || $entry == '..') {
|
||||
continue;
|
||||
}
|
||||
$entry_path = $path . '/' . $entry;
|
||||
$this->fileUnmanagedDeleteRecursive($entry_path, $callback);
|
||||
}
|
||||
$dir->close();
|
||||
|
||||
return rmdir($path);
|
||||
}
|
||||
return unlink($path);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\BuildTests\QuickStart;
|
||||
|
||||
use Drupal\BuildTests\Framework\BuildTestBase;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
|
||||
/**
|
||||
* Helper methods for using the quickstart feature of Drupal.
|
||||
*/
|
||||
abstract class QuickStartTestBase extends BuildTestBase {
|
||||
|
||||
/**
|
||||
* User name of the admin account generated during install.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $adminUsername;
|
||||
|
||||
/**
|
||||
* Password of the admin account generated during install.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $adminPassword;
|
||||
|
||||
/**
|
||||
* Install a Drupal site using the quick start feature.
|
||||
*
|
||||
* @param string $profile
|
||||
* Drupal profile to install.
|
||||
* @param string $working_dir
|
||||
* (optional) A working directory relative to the workspace, within which to
|
||||
* execute the command. Defaults to the workspace directory.
|
||||
*/
|
||||
public function installQuickStart($profile, $working_dir = NULL) {
|
||||
$php_finder = new PhpExecutableFinder();
|
||||
$install_process = $this->executeCommand($php_finder->find() . ' ./core/scripts/drupal install ' . $profile, $working_dir);
|
||||
$this->assertCommandOutputContains('Username:');
|
||||
preg_match('/Username: (.+)\vPassword: (.+)/', $install_process->getOutput(), $matches);
|
||||
$this->assertNotEmpty($this->adminUsername = $matches[1]);
|
||||
$this->assertNotEmpty($this->adminPassword = $matches[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper that uses Drupal's user/login form to log in.
|
||||
*
|
||||
* @param string $username
|
||||
* Username.
|
||||
* @param string $password
|
||||
* Password.
|
||||
* @param string $working_dir
|
||||
* (optional) A working directory within which to login. Defaults to the
|
||||
* workspace directory.
|
||||
*/
|
||||
public function formLogin($username, $password, $working_dir = NULL) {
|
||||
$this->visit('/user/login', $working_dir);
|
||||
$assert = $this->getMink()->assertSession();
|
||||
$assert->statusCodeEquals(200);
|
||||
$assert->fieldExists('edit-name')->setValue($username);
|
||||
$assert->fieldExists('edit-pass')->setValue($password);
|
||||
$session = $this->getMink()->getSession();
|
||||
$session->getPage()->findButton('Log in')->submit();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\BuildTests\QuickStart;
|
||||
|
||||
use Drupal\sqlite\Driver\Database\sqlite\Install\Tasks;
|
||||
use Drupal\BuildTests\Framework\BuildTestBase;
|
||||
use Drupal\Core\Test\TestDatabase;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\PreserveGlobalState;
|
||||
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
|
||||
use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
/**
|
||||
* Tests the quick-start command with recipes.
|
||||
*
|
||||
* These tests are run in a separate process because they load Drupal code via
|
||||
* an include.
|
||||
*/
|
||||
#[Group('Command')]
|
||||
#[Group('Recipe')]
|
||||
#[PreserveGlobalState(FALSE)]
|
||||
#[RequiresPhpExtension('pdo_sqlite')]
|
||||
#[RunTestsInSeparateProcesses]
|
||||
class RecipeQuickStartTest extends BuildTestBase {
|
||||
|
||||
/**
|
||||
* The PHP executable path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $php;
|
||||
|
||||
/**
|
||||
* A test database object.
|
||||
*
|
||||
* @var \Drupal\Core\Test\TestDatabase
|
||||
*/
|
||||
protected TestDatabase $testDb;
|
||||
|
||||
/**
|
||||
* The Drupal root directory.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $root;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$php_executable_finder = new PhpExecutableFinder();
|
||||
$this->php = (string) $php_executable_finder->find();
|
||||
$this->root = dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__)), 2);
|
||||
if (!is_writable("{$this->root}/sites/simpletest")) {
|
||||
$this->markTestSkipped('This test requires a writable sites/simpletest directory');
|
||||
}
|
||||
// Get a lock and a valid site path.
|
||||
$this->testDb = new TestDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function tearDown(): void {
|
||||
if ($this->testDb) {
|
||||
$test_site_directory = $this->root . DIRECTORY_SEPARATOR . $this->testDb->getTestSitePath();
|
||||
if (file_exists($test_site_directory)) {
|
||||
// @todo use the tear down command from
|
||||
// https://www.drupal.org/project/drupal/issues/2926633
|
||||
// Delete test site directory.
|
||||
$this->fileUnmanagedDeleteRecursive($test_site_directory, BrowserTestBase::filePreDeleteCallback(...));
|
||||
}
|
||||
}
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the quick-start command with a recipe.
|
||||
*/
|
||||
public function testQuickStartRecipeCommand(): void {
|
||||
$sqlite = (string) (new \PDO('sqlite::memory:'))->query('select sqlite_version()')->fetch()[0];
|
||||
if (version_compare($sqlite, Tasks::SQLITE_MINIMUM_VERSION) < 0) {
|
||||
$this->markTestSkipped();
|
||||
}
|
||||
|
||||
// Install a site using the standard recipe to ensure the one time login
|
||||
// link generation works.
|
||||
|
||||
$script = $this->root . '/core/scripts/drupal';
|
||||
$install_command = [
|
||||
$this->php,
|
||||
$script,
|
||||
'quick-start',
|
||||
'core/recipes/standard',
|
||||
"--site-name='Test site {$this->testDb->getDatabasePrefix()}'",
|
||||
'--suppress-login',
|
||||
];
|
||||
$this->assertFileExists($script, "Install script is found in $script");
|
||||
|
||||
$process = new Process($install_command, NULL, ['DRUPAL_DEV_SITE_PATH' => $this->testDb->getTestSitePath()]);
|
||||
$process->setTimeout(500);
|
||||
$process->start();
|
||||
$guzzle = new Client();
|
||||
$port = FALSE;
|
||||
$process->waitUntil(function ($type, $output) use (&$port) {
|
||||
if (preg_match('/127.0.0.1:(\d+)/', $output, $match)) {
|
||||
$port = $match[1];
|
||||
return TRUE;
|
||||
}
|
||||
});
|
||||
// The progress bar uses STDERR to write messages.
|
||||
$this->assertStringContainsString('Congratulations, you installed Drupal!', $process->getErrorOutput());
|
||||
// Ensure the command does not trigger any PHP deprecations.
|
||||
$this->assertStringNotContainsStringIgnoringCase('deprecated', $process->getErrorOutput());
|
||||
$this->assertNotFalse($port, "Web server running on port $port");
|
||||
|
||||
// Give the server a couple of seconds to be ready.
|
||||
sleep(2);
|
||||
$this->assertStringContainsString("127.0.0.1:$port/user/reset/1/", $process->getOutput());
|
||||
|
||||
// Generate a cookie so we can make a request against the installed site.
|
||||
define('DRUPAL_TEST_IN_CHILD_SITE', FALSE);
|
||||
chmod($this->root . '/' . $this->testDb->getTestSitePath(), 0755);
|
||||
$cookieJar = CookieJar::fromArray([
|
||||
'SIMPLETEST_USER_AGENT' => drupal_generate_test_ua($this->testDb->getDatabasePrefix()),
|
||||
], '127.0.0.1');
|
||||
|
||||
$response = $guzzle->get('http://127.0.0.1:' . $port, ['cookies' => $cookieJar]);
|
||||
$content = (string) $response->getBody();
|
||||
$this->assertStringContainsString('Test site ' . $this->testDb->getDatabasePrefix(), $content);
|
||||
// Test content from Standard front page.
|
||||
$this->assertStringContainsString('Congratulations and welcome to the Drupal community.', $content);
|
||||
|
||||
// Stop the web server.
|
||||
$process->stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all files and directories in the specified path recursively.
|
||||
*
|
||||
* Note this method has no dependencies on Drupal core to ensure that the
|
||||
* test site can be torn down even if something in the test site is broken.
|
||||
*
|
||||
* @param string $path
|
||||
* A string containing either a URI or a file or directory path.
|
||||
* @param callable $callback
|
||||
* (optional) Callback function to run on each file prior to deleting it and
|
||||
* on each directory prior to traversing it. For example, can be used to
|
||||
* modify permissions.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE for success or if path does not exist, FALSE in the event of an
|
||||
* error.
|
||||
*
|
||||
* @see \Drupal\Core\File\FileSystemInterface::deleteRecursive()
|
||||
*/
|
||||
protected function fileUnmanagedDeleteRecursive($path, $callback = NULL): bool {
|
||||
if (isset($callback)) {
|
||||
call_user_func($callback, $path);
|
||||
}
|
||||
if (is_dir($path)) {
|
||||
$dir = dir($path);
|
||||
assert($dir instanceof \Directory);
|
||||
while (($entry = $dir->read()) !== FALSE) {
|
||||
if ($entry == '.' || $entry == '..') {
|
||||
continue;
|
||||
}
|
||||
$entry_path = $path . '/' . $entry;
|
||||
$this->fileUnmanagedDeleteRecursive($entry_path, $callback);
|
||||
}
|
||||
$dir->close();
|
||||
|
||||
return rmdir($path);
|
||||
}
|
||||
return unlink($path);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user