Compare commits

..

8 Commits

Author SHA1 Message Date
Guy Sartorelli
01ec7c7be3
Merge 88a1f0362f465bbd5ff4d8d55242b3b69ae7e81e into c523022cb9d52cb8c2567127844f7e16be63cb77 2024-09-25 00:25:35 +00:00
Guy Sartorelli
88a1f0362f
NEW Refactor CLI interaction with Silverstripe app
- Turn sake into a symfony/console app
- Avoid using HTTPRequest for CLI interaction
- Implement abstract hybrid execution path
2024-09-25 12:25:24 +12:00
Guy Sartorelli
c523022cb9
Merge branch '5' into 6 2024-09-24 14:07:57 +12:00
Guy Sartorelli
a81b7855de
Merge pull request #11393 from creative-commoners/pulls/5/fix-constructor
FIX Use correct contructor for HTTPOutputHandler
2024-09-24 09:35:35 +12:00
Steve Boyd
e93dafb2fe FIX Use correct contructor for HTTPOutputHandler 2024-09-19 17:21:12 +12:00
Guy Sartorelli
6287b6ebeb
API Rename Deprecation::withNoReplacement (#11390) 2024-09-19 11:27:08 +12:00
Guy Sartorelli
c7ba8d19c5
Merge pull request #11383 from wilr/features/9394-mysqli-flags
feat: support defining MySQLi flags
2024-09-19 09:38:13 +12:00
Will Rossiter
5fa88663b0
feat: support defining MySQLi flags 2024-09-19 07:24:19 +12:00
19 changed files with 91 additions and 57 deletions

View File

@ -39,8 +39,8 @@ abstract class BuildTask extends PolyCommand
private static array $permissions_for_browser_execution = [
'ADMIN',
'anyone_with_dev_admin_permissions' => 'ALL_DEV_ADMIN',
'anyone_with_task_permissions' => 'BUILDTASK_CAN_RUN',
'ALL_DEV_ADMIN' => true,
'BUILDTASK_CAN_RUN' => true,
];
public function __construct()

View File

@ -14,7 +14,7 @@ abstract class DevCommand extends PolyCommand
{
private static array $permissions_for_browser_execution = [
'ADMIN',
'anyone_with_dev_admin_permissions' => 'ALL_DEV_ADMIN',
'ALL_DEV_ADMIN' => true,
];
public function run(InputInterface $input, PolyOutput $output): int

View File

@ -53,7 +53,7 @@ class Deprecation
/**
* @internal
*/
private static bool $insideWithNoReplacement = false;
private static bool $insideNoticeSuppression = false;
/**
* @internal
@ -103,22 +103,32 @@ class Deprecation
}
/**
* Used to wrap deprecated methods and deprecated config get()/set() that will be removed
* in the next major version with no replacement. This is done to surpress deprecation notices
* by for calls from the vendor dir to deprecated code that projects have no ability to change
* Used to wrap deprecated methods and deprecated config get()/set() called from the vendor
* dir that projects have no ability to change.
*
* @return mixed
* @deprecated 5.4.0 Use withSuppressedNotice() instead
*/
public static function withNoReplacement(callable $func)
{
if (Deprecation::$insideWithNoReplacement) {
Deprecation::notice('5.4.0', 'Use withSuppressedNotice() instead');
return Deprecation::withSuppressedNotice($func);
}
/**
* Used to wrap deprecated methods and deprecated config get()/set() called from the vendor
* dir that projects have no ability to change.
*/
public static function withSuppressedNotice(callable $func): mixed
{
if (Deprecation::$insideNoticeSuppression) {
return $func();
}
Deprecation::$insideWithNoReplacement = true;
Deprecation::$insideNoticeSuppression = true;
try {
return $func();
} finally {
Deprecation::$insideWithNoReplacement = false;
Deprecation::$insideNoticeSuppression = false;
}
}
@ -137,8 +147,8 @@ class Deprecation
$level = 1;
}
$newLevel = $level;
// handle closures inside withNoReplacement()
if (Deprecation::$insideWithNoReplacement
// handle closures inside withSuppressedNotice()
if (Deprecation::$insideNoticeSuppression
&& substr($backtrace[$newLevel]['function'], -strlen('{closure}')) === '{closure}'
) {
$newLevel = $newLevel + 2;
@ -247,8 +257,8 @@ class Deprecation
$count++;
$arr = array_shift(Deprecation::$userErrorMessageBuffer);
$message = $arr['message'];
$calledInsideWithNoReplacement = $arr['calledInsideWithNoReplacement'];
if ($calledInsideWithNoReplacement && !Deprecation::$showNoReplacementNotices) {
$calledWithNoticeSuppression = $arr['calledWithNoticeSuppression'];
if ($calledWithNoticeSuppression && !Deprecation::$showNoReplacementNotices) {
continue;
}
Deprecation::$isTriggeringError = true;
@ -284,7 +294,7 @@ class Deprecation
$data = [
'key' => sha1($string),
'message' => $string,
'calledInsideWithNoReplacement' => Deprecation::$insideWithNoReplacement
'calledWithNoticeSuppression' => Deprecation::$insideNoticeSuppression
];
} else {
if (!Deprecation::isEnabled()) {
@ -310,7 +320,7 @@ class Deprecation
$string .= ".";
}
$level = Deprecation::$insideWithNoReplacement ? 4 : 2;
$level = Deprecation::$insideNoticeSuppression ? 4 : 2;
$string .= " Called from " . Deprecation::get_called_method_from_trace($backtrace, $level) . '.';
if ($caller) {
@ -319,7 +329,7 @@ class Deprecation
$data = [
'key' => sha1($string),
'message' => $string,
'calledInsideWithNoReplacement' => Deprecation::$insideWithNoReplacement
'calledWithNoticeSuppression' => Deprecation::$insideNoticeSuppression
];
}
if ($data && !array_key_exists($data['key'], Deprecation::$userErrorMessageBuffer)) {

View File

@ -21,8 +21,8 @@ class CleanupTestDatabasesTask extends BuildTask
protected static string $description = 'Cleans up leftover databases from aborted test executions (starting with ss_tmpdb)';
private static array $permissions_for_browser_execution = [
'anyone_with_dev_admin_permissions' => null,
'anyone_with_task_permissions' => null,
'ALL_DEV_ADMIN' => false,
'BUILDTASK_CAN_RUN' => false,
];
protected function execute(InputInterface $input, PolyOutput $output): int

View File

@ -25,7 +25,7 @@ class GridFieldConfig_Base extends GridFieldConfig
$this->addComponent(GridFieldPageCount::create('toolbar-header-right'));
$this->addComponent($pagination = GridFieldPaginator::create($itemsPerPage));
Deprecation::withNoReplacement(function () use ($sort, $filter, $pagination) {
Deprecation::withSuppressedNotice(function () use ($sort, $filter, $pagination) {
$sort->setThrowExceptionOnBadDataType(false);
$filter->setThrowExceptionOnBadDataType(false);
$pagination->setThrowExceptionOnBadDataType(false);

View File

@ -32,7 +32,7 @@ class GridFieldConfig_RecordEditor extends GridFieldConfig
$this->addComponent($pagination = GridFieldPaginator::create($itemsPerPage));
$this->addComponent(GridFieldDetailForm::create(null, $showPagination, $showAdd));
Deprecation::withNoReplacement(function () use ($sort, $filter, $pagination) {
Deprecation::withSuppressedNotice(function () use ($sort, $filter, $pagination) {
$sort->setThrowExceptionOnBadDataType(false);
$filter->setThrowExceptionOnBadDataType(false);
$pagination->setThrowExceptionOnBadDataType(false);

View File

@ -45,7 +45,7 @@ class GridFieldConfig_RelationEditor extends GridFieldConfig
$this->addComponent($pagination = GridFieldPaginator::create($itemsPerPage));
$this->addComponent(GridFieldDetailForm::create());
Deprecation::withNoReplacement(function () use ($sort, $filter, $pagination) {
Deprecation::withSuppressedNotice(function () use ($sort, $filter, $pagination) {
$sort->setThrowExceptionOnBadDataType(false);
$filter->setThrowExceptionOnBadDataType(false);
$pagination->setThrowExceptionOnBadDataType(false);

View File

@ -4,6 +4,7 @@ namespace SilverStripe\Logging;
use Monolog\Formatter\FormatterInterface;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Level;
use Monolog\LogRecord;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
@ -31,10 +32,10 @@ class ErrorOutputHandler extends AbstractProcessingHandler
*/
private $cliFormatter = null;
public function __construct()
public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true)
{
parent::__construct();
Deprecation::withNoReplacement(function () {
parent::__construct($level, $bubble);
Deprecation::withSuppressedNotice(function () {
Deprecation::notice(
'5.4.0',
'Will be renamed to ErrorOutputHandler',

View File

@ -720,7 +720,7 @@ class ArrayList extends ModelData implements SS_List, Filterable, Sortable, Limi
// Apply default case sensitivity for backwards compatability
if (!str_contains($filterKey, ':case') && !str_contains($filterKey, ':nocase')) {
$caseSensitive = Deprecation::withNoReplacement(fn() => static::config()->get('default_case_sensitive'));
$caseSensitive = Deprecation::withSuppressedNotice(fn() => static::config()->get('default_case_sensitive'));
if ($caseSensitive && in_array('case', $searchFilter->getSupportedModifiers())) {
$searchFilter->setModifiers($searchFilter->getModifiers() + ['case']);
} elseif (!$caseSensitive && in_array('nocase', $searchFilter->getSupportedModifiers())) {

View File

@ -6,6 +6,7 @@ use mysqli;
use mysqli_sql_exception;
use mysqli_stmt;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Environment;
/**
* Connector for MySQL using the MySQLi method
@ -81,15 +82,21 @@ class MySQLiConnector extends DBConnector
// Connection charset and collation
$connCharset = Config::inst()->get(MySQLDatabase::class, 'connection_charset');
$connCollation = Config::inst()->get(MySQLDatabase::class, 'connection_collation');
$socket = Environment::getEnv('SS_DATABASE_SOCKET');
$flags = Environment::getEnv('SS_DATABASE_FLAGS');
$flags = $flags ? array_reduce(explode(',', $flags), function ($carry, $item) {
$item = trim($item);
return $carry | constant($item);
}, 0) : $flags;
$this->dbConn = mysqli_init();
// Use native types (MysqlND only)
if (defined('MYSQLI_OPT_INT_AND_FLOAT_NATIVE')) {
$this->dbConn->options(MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
// The alternative is not ideal, throw a notice-level error
} else {
// The alternative is not ideal, throw a notice-level error
user_error(
'mysqlnd PHP library is not available, numeric values will be fetched from the DB as strings',
E_USER_NOTICE
@ -117,7 +124,9 @@ class MySQLiConnector extends DBConnector
$parameters['username'],
$parameters['password'],
$selectedDB,
!empty($parameters['port']) ? $parameters['port'] : ini_get("mysqli.default_port")
!empty($parameters['port']) ? $parameters['port'] : ini_get("mysqli.default_port"),
$socket ?? null,
$flags ?? 0
);
if ($this->dbConn->connect_error) {
@ -126,8 +135,8 @@ class MySQLiConnector extends DBConnector
// Set charset and collation if given and not null. Can explicitly set to empty string to omit
$charset = isset($parameters['charset'])
? $parameters['charset']
: $connCharset;
? $parameters['charset']
: $connCharset;
if (!empty($charset)) {
$this->dbConn->set_charset($charset);

View File

@ -1982,7 +1982,7 @@ class DataObject extends ModelData implements DataObjectInterface, i18nEntityPro
if ($details['polymorphic']) {
$result = PolymorphicHasManyList::create($componentClass, $details['joinField'], static::class);
if ($details['needsRelation']) {
Deprecation::withNoReplacement(fn () => $result->setForeignRelation($componentName));
Deprecation::withSuppressedNotice(fn () => $result->setForeignRelation($componentName));
}
} else {
$result = HasManyList::create($componentClass, $details['joinField']);

View File

@ -43,6 +43,10 @@ abstract class PolyCommand
* Users must have at least one of these permissions to run the command in an HTTP request.
* If `can_run_in_browser` is false, these permissions are ignored.
* Must be defined in the subclass.
*
* If permissions are set as keys, the value must be boolean, indicating whether to check
* that permission or not. This is useful for allowing developers to turn off permission checks
* that aren't valid for their project or for their subclass.
*/
private static array $permissions_for_browser_execution = [];
@ -162,8 +166,18 @@ abstract class PolyCommand
}
// Check permissions if there are any
$permissions = static::config()->get('permissions_for_browser_execution');
if ($permissions) {
return Permission::check($permissions);
if (!empty($permissions)) {
$usePermissions = [];
// Only use permissions that aren't set to false
// Allow permissions to also be simply set as values, for simplicity
foreach ($permissions as $key => $value) {
if (is_string($value)) {
$usePermissions[] = $value;
} elseif ($value) {
$usePermissions[] = $key;
}
}
return Permission::check($usePermissions);
}
return true;
}

View File

@ -176,7 +176,7 @@ class CookieAuthenticationHandler implements AuthenticationHandler
}
// Renew the token
Deprecation::withNoReplacement(fn() => $rememberLoginHash->renew());
Deprecation::withSuppressedNotice(fn() => $rememberLoginHash->renew());
// Send the new token to the client if it was changed
if ($rememberLoginHash->getToken()) {

View File

@ -434,7 +434,7 @@ class SSViewer_DataPresenter extends SSViewer_Scope
return $value;
}
// Wrap list arrays in ViewableData so templates can handle them
// Wrap list arrays in ModelData so templates can handle them
if (is_array($value) && array_is_list($value)) {
return ArrayList::create($value);
}

View File

@ -116,72 +116,72 @@ class DeprecationTest extends SapphireTest
Deprecation::outputNotices();
}
public function testWithNoReplacementDefault()
public function testwithSuppressedNoticeDefault()
{
Deprecation::enable();
$ret = Deprecation::withNoReplacement(function () {
$ret = Deprecation::withSuppressedNotice(function () {
return $this->myDeprecatedMethod();
});
$this->assertSame('abc', $ret);
Deprecation::outputNotices();
}
public function testWithNoReplacementTrue()
public function testwithSuppressedNoticeTrue()
{
$message = implode(' ', [
'SilverStripe\Dev\Tests\DeprecationTest->myDeprecatedMethod is deprecated.',
'My message.',
'Called from SilverStripe\Dev\Tests\DeprecationTest->testWithNoReplacementTrue.'
'Called from SilverStripe\Dev\Tests\DeprecationTest->testwithSuppressedNoticeTrue.'
]);
$this->expectException(DeprecationTestException::class);
$this->expectExceptionMessage($message);
Deprecation::enable(true);
$ret = Deprecation::withNoReplacement(function () {
$ret = Deprecation::withSuppressedNotice(function () {
return $this->myDeprecatedMethod();
});
$this->assertSame('abc', $ret);
Deprecation::outputNotices();
}
public function testWithNoReplacementTrueCallUserFunc()
public function testwithSuppressedNoticeTrueCallUserFunc()
{
$message = implode(' ', [
'SilverStripe\Dev\Tests\DeprecationTest->myDeprecatedMethod is deprecated.',
'My message.',
'Called from SilverStripe\Dev\Tests\DeprecationTest->testWithNoReplacementTrueCallUserFunc.'
'Called from SilverStripe\Dev\Tests\DeprecationTest->testwithSuppressedNoticeTrueCallUserFunc.'
]);
$this->expectException(DeprecationTestException::class);
$this->expectExceptionMessage($message);
Deprecation::enable(true);
$ret = Deprecation::withNoReplacement(function () {
$ret = Deprecation::withSuppressedNotice(function () {
return call_user_func([$this, 'myDeprecatedMethod']);
});
$this->assertSame('abc', $ret);
Deprecation::outputNotices();
}
public function testNoticeWithNoReplacementTrue()
public function testNoticewithSuppressedNoticeTrue()
{
$message = implode(' ', [
'SilverStripe\Dev\Tests\DeprecationTest->testNoticeWithNoReplacementTrue is deprecated.',
'SilverStripe\Dev\Tests\DeprecationTest->testNoticewithSuppressedNoticeTrue is deprecated.',
'My message.',
'Called from PHPUnit\Framework\TestCase->runTest.'
]);
$this->expectException(DeprecationTestException::class);
$this->expectExceptionMessage($message);
Deprecation::enable(true);
Deprecation::withNoReplacement(function () {
Deprecation::withSuppressedNotice(function () {
Deprecation::notice('123', 'My message.');
});
Deprecation::outputNotices();
}
public function testClassWithNoReplacement()
public function testClasswithSuppressedNotice()
{
$message = implode(' ', [
'SilverStripe\Dev\Tests\DeprecationTest\DeprecationTestObject is deprecated.',
'Some class message.',
'Called from SilverStripe\Dev\Tests\DeprecationTest->testClassWithNoReplacement.'
'Called from SilverStripe\Dev\Tests\DeprecationTest->testClasswithSuppressedNotice.'
]);
$this->expectException(DeprecationTestException::class);
$this->expectExceptionMessage($message);
@ -193,12 +193,12 @@ class DeprecationTest extends SapphireTest
Deprecation::outputNotices();
}
public function testClassWithInjectorWithNoReplacement()
public function testClassWithInjectorwithSuppressedNotice()
{
$message = implode(' ', [
'SilverStripe\Dev\Tests\DeprecationTest\DeprecationTestObject is deprecated.',
'Some class message.',
'Called from SilverStripe\Dev\Tests\DeprecationTest->testClassWithInjectorWithNoReplacement.'
'Called from SilverStripe\Dev\Tests\DeprecationTest->testClassWithInjectorwithSuppressedNotice.'
]);
$this->expectException(DeprecationTestException::class);
$this->expectExceptionMessage($message);

View File

@ -11,7 +11,7 @@ class DeprecationTestObject extends DataObject implements TestOnly
public function __construct()
{
parent::__construct();
Deprecation::withNoReplacement(function () {
Deprecation::withSuppressedNotice(function () {
Deprecation::notice(
'1.2.3',
'Some class message',

View File

@ -156,7 +156,7 @@ class PasswordEncryptorTest extends SapphireTest
'encryptors',
['test_sha1legacy' => [PasswordEncryptor_LegacyPHPHash::class => 'sha1']]
);
$e = Deprecation::withNoReplacement(fn() => PasswordEncryptor::create_for_algorithm('test_sha1legacy'));
$e = Deprecation::withSuppressedNotice(fn() => PasswordEncryptor::create_for_algorithm('test_sha1legacy'));
// precomputed hashes for 'mypassword' from different architectures
$amdHash = 'h1fj0a6m4o6k0sosks88oo08ko4gc4s';
$intelHash = 'h1fj0a6m4o0g04ocg00o4kwoc4wowws';

View File

@ -111,7 +111,7 @@ class RememberLoginHashTest extends SapphireTest
$member = $this->objFromFixture(Member::class, 'main');
Deprecation::withNoReplacement(
Deprecation::withSuppressedNotice(
fn() => RememberLoginHash::config()->set('replace_token_during_session_renewal', $replaceToken)
);
@ -122,7 +122,7 @@ class RememberLoginHashTest extends SapphireTest
// Fetch the token from the DB - otherwise we still have the token from when this was originally created
$storedHash = RememberLoginHash::get()->find('ID', $hash->ID);
Deprecation::withNoReplacement(fn() => $storedHash->renew());
Deprecation::withSuppressedNotice(fn() => $storedHash->renew());
if ($replaceToken) {
$this->assertNotEquals($oldToken, $storedHash->getToken());

View File

@ -2370,14 +2370,14 @@ EOC;
public function testLoopingThroughArrayInOverlay(): void
{
$viewableData = new ViewableData();
$modelData = new ModelData();
$theArray = [
['Val' => 'one'],
['Val' => 'two'],
['Val' => 'red'],
['Val' => 'blue'],
];
$output = $viewableData->renderWith('SSViewerTestLoopArray', ['MyArray' => $theArray]);
$output = $modelData->renderWith('SSViewerTestLoopArray', ['MyArray' => $theArray]);
$this->assertEqualIgnoringWhitespace('one two red blue', $output);
}
}