Skip to content

Commit

Permalink
Merge branch '4' into 5
Browse files Browse the repository at this point in the history
  • Loading branch information
GuySartorelli committed Dec 14, 2022
2 parents aefa37f + 4e1b99b commit fa75a36
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 8 deletions.
2 changes: 2 additions & 0 deletions src/Core/Convert.php
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,11 @@ public static function xml2raw($val)
* @param SimpleXMLElement $xml
*
* @return mixed
* @deprecated 4.11.0 Will be removed without equivalent functionality
*/
protected static function recursiveXMLToArray($xml)
{
Deprecation::notice('4.11.0', 'Will be removed without equivalent functionality');
$x = null;
if ($xml instanceof SimpleXMLElement) {
$attributes = $xml->attributes();
Expand Down
7 changes: 7 additions & 0 deletions src/Dev/Deprecation.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,16 +141,23 @@ public static function outputNotices(): void
if (!self::isEnabled()) {
return;
}
$outputMessages = [];
// using a while loop with array_shift() to ensure that self::$userErrorMessageBuffer will have
// have values removed from it before calling user_error()
while (count(self::$userErrorMessageBuffer)) {
$arr = array_shift(self::$userErrorMessageBuffer);
$message = $arr['message'];
// often the same deprecation message appears dozens of times, which isn't helpful
// only need to show a single instance of each message
if (in_array($message, $outputMessages)) {
continue;
}
$calledInsideWithNoReplacement = $arr['calledInsideWithNoReplacement'];
if ($calledInsideWithNoReplacement && !self::$showNoReplacementNotices) {
continue;
}
user_error($message, E_USER_DEPRECATED);
$outputMessages[] = $message;
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/Dev/State/FixtureTestState.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,10 @@ protected function resolveFixturePath($fixtureFilePath, SapphireTest $test)
*/
protected function getTestAbsolutePath(SapphireTest $test)
{
$filename = ClassLoader::inst()->getItemPath(get_class($test));
$class = get_class($test);
$filename = ClassLoader::inst()->getItemPath($class);
if (!$filename) {
throw new LogicException('getItemPath returned null for ' . static::class
throw new LogicException('getItemPath returned null for ' . $class
. '. Try adding flush=1 to the test run.');
}
return dirname($filename ?? '');
Expand Down
2 changes: 1 addition & 1 deletion src/ORM/RelatedData/StandardRelatedDataService.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ private function findTableNameContainingComponentIDField(string $class, string $
$tableName = $this->dataObjectSchema->tableName($candidateClass);
break;
}
$candidateClass = get_parent_class($class ?? '');
$candidateClass = get_parent_class($candidateClass ?? '');
}
return $tableName;
}
Expand Down
76 changes: 71 additions & 5 deletions src/i18n/TextCollection/i18nTextCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -548,40 +548,83 @@ protected function getFileListForModule(Module $module)
*/
public function collectFromCode($content, $fileName, Module $module)
{
// Get namespace either from $fileName or $module fallback
// Get "namespace" either from $fileName or $module fallback
$namespace = $fileName ? basename($fileName) : $module->getName();

$usedFQCNs = [];
$entities = [];

$tokens = token_get_all("<?php\n" . $content);
$inTransFn = false;
$inConcat = false;
$inNamespace = false;
$inClass = false; // after `class` but before `{`
$inUse = false; // pulling in classes from other namespaces
$inArrayClosedBy = false; // Set to the expected closing token, or false if not in array
$inSelf = false; // Tracks progress of collecting self::class
$currentEntity = [];
$currentNameSpace = []; // The actual namespace for the current class
$currentClass = []; // Class components
$previousToken = null;
$thisToken = null; // used to populate $previousToken on next iteration
$potentialClassName = null;
$currentUse = null;
$currentUseAlias = null;
foreach ($tokens as $token) {
// Shuffle last token to $lastToken
$previousToken = $thisToken;
$thisToken = $token;
if (is_array($token)) {
list($id, $text) = $token;

// Collect use statements so we can get fully qualified class names
if ($id === T_USE) {
$inUse = true;
$currentUse = [];
continue;
}

if ($inUse) {
// PHP 8.0+
if (defined('T_NAME_QUALIFIED') && $id === T_NAME_QUALIFIED) {
$currentUse[] = $text;
$text = explode('\\', $text);
$currentUseAlias = end($text);
continue;
}
// PHP 7.4 or an alias declaration
if ($id === T_STRING) {
// Only add to the FQCN if it's the first string or comes after a namespace separator
if (empty($currentUse) || (is_array($previousToken) && $previousToken[0] === T_NS_SEPARATOR)) {
$currentUse[] = $text;
}
// The last part of the use statement is always the alias or the actual class name
$currentUseAlias = $text;
continue;
}
}

// Check class
if ($id === T_NAMESPACE) {
$inNamespace = true;
$currentClass = [];
$currentNameSpace = [];
continue;
}
if ($inNamespace && ($id === T_STRING || (defined('T_NAME_QUALIFIED') && $id === T_NAME_QUALIFIED))) {
$currentClass[] = $text;
$currentNameSpace[] = $text;
continue;
}

// This could be a ClassName::class declaration
if ($id === T_DOUBLE_COLON && is_array($previousToken) && $previousToken[0] === T_STRING) {
$prevString = $previousToken[1];
if (!in_array($prevString, ['self', 'static', 'parent'])) {
$potentialClassName = $prevString;
}
}

// Check class
if ($id === T_CLASS) {
// Skip if previous token was '::'. E.g. 'Object::class'
Expand All @@ -591,6 +634,16 @@ public function collectFromCode($content, $fileName, Module $module)
// for __CLASS__ to handle an array of class parts
$id = T_CLASS_C;
$inSelf = false;
} elseif ($potentialClassName) {
$id = T_CONSTANT_ENCAPSED_STRING;
if (array_key_exists($potentialClassName, $usedFQCNs)) {
// Handle classes that we explicitly know about from use statements
$text = "'" . $usedFQCNs[$potentialClassName] . "'";
} else {
// Assume the class is in the current namespace
$potentialFQCN = [...$currentNameSpace, $potentialClassName];
$text = "'" . implode('\\', $potentialFQCN) . "'";
}
} else {
// Don't handle other ::class definitions. We can't determine which
// class was invoked, so parent::class is not possible at this point.
Expand All @@ -600,7 +653,11 @@ public function collectFromCode($content, $fileName, Module $module)
$inClass = true;
continue;
}
} elseif (is_array($previousToken) && $previousToken[0] === T_DOUBLE_COLON) {
// We had a potential class but it turns out it was probably a method call.
$potentialClassName = null;
}

if ($inClass && $id === T_STRING) {
$currentClass[] = $text;
$inClass = false;
Expand Down Expand Up @@ -691,10 +748,19 @@ function ($input) {
continue;
}

// Check if we can close the namespace
if ($inNamespace && $token === ';') {
$inNamespace = false;
continue;
// Check if we can close the namespace or use statement
if ($token === ';') {
if ($inNamespace) {
$inNamespace = false;
continue;
}
if ($inUse) {
$inUse = false;
$usedFQCNs[$currentUseAlias] = implode('\\', $currentUse);
$currentUse = null;
$currentUseAlias = null;
continue;
}
}

// Continue only if in translation and not in array
Expand Down
3 changes: 3 additions & 0 deletions tests/php/Logging/MonologErrorHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public function testStartThrowsExceptionWithoutLoggerDefined()

public function testSetLoggerResetsStack()
{
if (Deprecation::isEnabled()) {
$this->markTestSkipped('Test calls deprecated code');
}
/** @var LoggerInterface $logger */
$logger = $this->createMock(LoggerInterface::class);

Expand Down
2 changes: 2 additions & 0 deletions tests/php/Security/SecurityDefaultAdminTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use SilverStripe\Security\PasswordEncryptor;
use SilverStripe\Security\Permission;
use SilverStripe\Security\DefaultAdminService;
use SilverStripe\Security\Security;

class SecurityDefaultAdminTest extends SapphireTest
{
Expand Down Expand Up @@ -35,6 +36,7 @@ protected function setUp(): void
$this->defaultUsername = null;
$this->defaultPassword = null;
}
Security::config()->set('password_encryption_algorithm', 'blowfish');
DefaultAdminService::setDefaultAdmin('admin', 'password');
Permission::reset();
}
Expand Down
1 change: 1 addition & 0 deletions tests/php/View/RequirementsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use SilverStripe\Core\Manifest\ResourceURLGenerator;
use SilverStripe\Control\SimpleResourceURLGenerator;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\Deprecation;
use SilverStripe\View\SSViewer;
use SilverStripe\View\ThemeResourceLoader;

Expand Down
58 changes: 58 additions & 0 deletions tests/php/i18n/i18nTextCollectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,64 @@ public function getMagicConstantStringFromSelf()
);
}

public function testCollectFromClassSyntax()
{
$c = i18nTextCollector::create();
$mymodule = ModuleLoader::inst()->getManifest()->getModule('i18ntestmodule');
$php = <<<PHP
<?php
namespace SilverStripe\Framework\Core;
use SilverStripe\ORM\DataObject;
use SilverStripe\Versioned\Versioned;
use Some\Space\MyClass as AliasClass;
use NoNamespaceClass;
class MyClass extends Base implements SomeService {
public function pointlessFunction1(\$class) {
if (
!is_subclass_of(\$class, DataObject::class)
|| !Object::has_extension(\$class, Versioned::class)
) {
return null;
}
return _t(
Versioned::class . '.OTHER_NAMESPACE',
'New Lines'
);
}
public function pointlessFunction2() {
return _t(
SameNamespaceClass::class . '.SAME_NAMESPACE',
'Slash=\\\\, Quote=\\''
);
}
public function pointlessFunction3() {
return _t(
AliasClass::class . ".ALIAS_CLASS",
"Slash=\\\\, Quote=\\""
);
}
public function pointlessFunction4()
{
return _t(
NoNamespaceClass::class . '.NO_NAMESPACE',
'Self Class'
);
}
}
PHP;

$this->assertEquals(
[
'SilverStripe\\Versioned\\Versioned.OTHER_NAMESPACE' => "New Lines",
'SilverStripe\\Framework\\Core\\SameNamespaceClass.SAME_NAMESPACE' => 'Slash=\\, Quote=\'',
'Some\\Space\\MyClass.ALIAS_CLASS' => 'Slash=\\, Quote="',
'NoNamespaceClass.NO_NAMESPACE' => 'Self Class',
],
$c->collectFromCode($php, null, $mymodule)
);
}

public function testNewlinesInEntityValues()
{
Expand Down

0 comments on commit fa75a36

Please sign in to comment.