diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 6d5ad942d5..db1e5cddf1 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -4745,11 +4745,6 @@ parameters: count: 2 path: src/Controllers/Table/ReplaceController.php - - - message: "#^Call to function method_exists\\(\\) with PhpMyAdmin\\\\Plugins\\\\IOTransformationsPlugin and 'isSuccess' will always evaluate to true\\.$#" - count: 1 - path: src/Controllers/Table/ReplaceController.php - - message: "#^Cannot access an offset on mixed\\.$#" count: 2 @@ -7032,22 +7027,7 @@ parameters: - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 26 - path: src/Display/Results.php - - - - message: "#^Dynamic call to static method PhpMyAdmin\\\\Plugins\\\\TransformationsInterface\\:\\:getMIMESubtype\\(\\)\\.$#" - count: 1 - path: src/Display/Results.php - - - - message: "#^Dynamic call to static method PhpMyAdmin\\\\Plugins\\\\TransformationsInterface\\:\\:getMIMEType\\(\\)\\.$#" - count: 2 - path: src/Display/Results.php - - - - message: "#^Dynamic call to static method PhpMyAdmin\\\\Plugins\\\\TransformationsInterface\\:\\:getName\\(\\)\\.$#" - count: 1 + count: 24 path: src/Display/Results.php - @@ -8792,7 +8772,7 @@ parameters: - message: "#^Cannot cast mixed to string\\.$#" - count: 3 + count: 4 path: src/InsertEdit.php - @@ -8876,7 +8856,7 @@ parameters: path: src/InsertEdit.php - - message: "#^Parameter \\#1 \\$buffer of method PhpMyAdmin\\\\Plugins\\\\TransformationsPlugin\\:\\:applyTransformation\\(\\) expects string, mixed given\\.$#" + message: "#^Parameter \\#1 \\$buffer of method PhpMyAdmin\\\\Plugins\\\\TransformationsInterface\\:\\:applyTransformation\\(\\) expects string, mixed given\\.$#" count: 1 path: src/InsertEdit.php @@ -14590,6 +14570,11 @@ parameters: count: 1 path: src/Transformations.php + - + message: "#^Method PhpMyAdmin\\\\Transformations\\:\\:getClassName\\(\\) should return class\\-string\\ but returns string\\.$#" + count: 1 + path: src/Transformations.php + - message: "#^Only booleans are allowed in a negated boolean, resource\\|false given\\.$#" count: 1 diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 42050825c1..88c85faad8 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -3592,9 +3592,6 @@ - - - @@ -5277,10 +5274,6 @@ - - - transformationInfo[$dbLower][$tblLower][$nameLower][1]()]]> - @@ -5339,10 +5332,6 @@ statement->where)]]> statement->where)]]> - - - transformationInfo[$dbLower][$tblLower][$nameLower])]]> - @@ -6514,7 +6503,7 @@ field]]]> field]]]> field]]]> - + field] ?? '']]> @@ -6554,7 +6543,6 @@ field]]]> field]]]> field]]]> - @@ -6570,12 +6558,7 @@ - - - - - @@ -9839,12 +9822,6 @@ - - - - - - @@ -11471,6 +11448,9 @@ + + + @@ -11480,6 +11460,9 @@ + + ]]> + diff --git a/src/Controllers/Table/ReplaceController.php b/src/Controllers/Table/ReplaceController.php index 94d882c6d8..425faca36b 100644 --- a/src/Controllers/Table/ReplaceController.php +++ b/src/Controllers/Table/ReplaceController.php @@ -29,12 +29,9 @@ use PhpMyAdmin\Util; use function __; use function array_keys; use function array_values; -use function class_exists; use function implode; use function in_array; use function is_array; -use function is_file; -use function method_exists; use function parse_str; use function sprintf; @@ -168,35 +165,27 @@ final class ReplaceController extends AbstractController ! empty($mimeMap[$columnName]) && ! empty($mimeMap[$columnName]['input_transformation']) ) { - $filename = 'src/Plugins/Transformations/' - . $mimeMap[$columnName]['input_transformation']; - if (is_file(ROOT_PATH . $filename)) { - $className = $this->transformations->getClassName($filename); - if (class_exists($className)) { - /** @var IOTransformationsPlugin $transformationPlugin */ - $transformationPlugin = new $className(); - $transformationOptions = $this->transformations->getOptions( - $mimeMap[$columnName]['input_transformation_options'], + $filename = $mimeMap[$columnName]['input_transformation']; + $transformationPlugin = $this->transformations->getPluginInstance($filename); + if ($transformationPlugin instanceof IOTransformationsPlugin) { + $transformationOptions = $this->transformations->getOptions( + $mimeMap[$columnName]['input_transformation_options'], + ); + $currentValue = $transformationPlugin->applyTransformation( + $currentValue, + $transformationOptions, + ); + // check if transformation was successful or not + // and accordingly set error messages & insert_fail + if (! $transformationPlugin->isSuccess()) { + $insertFail = true; + $rowSkipped = true; + $insertErrors[] = sprintf( + __('Row: %1$s, Column: %2$s, Error: %3$s'), + $rowNumber, + $columnName, + $transformationPlugin->getError(), ); - $currentValue = $transformationPlugin->applyTransformation( - $currentValue, - $transformationOptions, - ); - // check if transformation was successful or not - // and accordingly set error messages & insert_fail - if ( - method_exists($transformationPlugin, 'isSuccess') - && ! $transformationPlugin->isSuccess() - ) { - $insertFail = true; - $rowSkipped = true; - $insertErrors[] = sprintf( - __('Row: %1$s, Column: %2$s, Error: %3$s'), - $rowNumber, - $columnName, - $transformationPlugin->getError(), - ); - } } } } diff --git a/src/Display/Results.php b/src/Display/Results.php index 304544f512..82554d150b 100644 --- a/src/Display/Results.php +++ b/src/Display/Results.php @@ -20,7 +20,7 @@ use PhpMyAdmin\Plugins\Transformations\Output\Text_Octetstream_Sql; use PhpMyAdmin\Plugins\Transformations\Output\Text_Plain_Json; use PhpMyAdmin\Plugins\Transformations\Output\Text_Plain_Sql; use PhpMyAdmin\Plugins\Transformations\Text_Plain_Link; -use PhpMyAdmin\Plugins\TransformationsPlugin; +use PhpMyAdmin\Plugins\TransformationsInterface; use PhpMyAdmin\ResponseRenderer; use PhpMyAdmin\Sql; use PhpMyAdmin\SqlParser\Parser; @@ -44,10 +44,8 @@ use function array_merge; use function array_shift; use function bin2hex; use function ceil; -use function class_exists; use function count; use function explode; -use function file_exists; use function floor; use function htmlspecialchars; use function implode; @@ -66,7 +64,6 @@ use function md5; use function mt_getrandmax; use function pack; use function preg_match; -use function preg_replace; use function random_int; use function str_contains; use function str_ends_with; @@ -187,10 +184,9 @@ class Results * One element of this array represent all relevant columns in all tables in * one specific database * - * @var array>> - * @psalm-var array>> $transformationInfo + * @var array>>> $transformationInfo */ - public array $transformationInfo = []; + private array $transformationInfo = []; private Relation $relation; @@ -229,26 +225,10 @@ class Results */ private function setDefaultTransformations(): void { - $jsonHighlightingData = [ - 'src/Plugins/Transformations/Output/Text_Plain_Json.php', - Text_Plain_Json::class, - 'Text_Plain', - ]; - $sqlHighlightingData = [ - 'src/Plugins/Transformations/Output/Text_Plain_Sql.php', - Text_Plain_Sql::class, - 'Text_Plain', - ]; - $blobSqlHighlightingData = [ - 'src/Plugins/Transformations/Output/Text_Octetstream_Sql.php', - Text_Octetstream_Sql::class, - 'Text_Octetstream', - ]; - $linkData = [ - 'src/Plugins/Transformations/Text_Plain_Link.php', - Text_Plain_Link::class, - 'Text_Plain', - ]; + $jsonHighlightingData = Text_Plain_Json::class; + $sqlHighlightingData = Text_Plain_Sql::class; + $blobSqlHighlightingData = Text_Octetstream_Sql::class; + $linkData = Text_Plain_Link::class; $this->transformationInfo = [ 'information_schema' => [ 'events' => ['event_definition' => $sqlHighlightingData], @@ -1740,7 +1720,8 @@ class Results $classes = array_filter([$class, $nowrap]); if ($meta->internalMediaType !== null) { - $classes[] = preg_replace('/\//', '_', $meta->internalMediaType); + // TODO: Is str_replace() necessary here? + $classes[] = str_replace('/', '_', $meta->internalMediaType); } if ($conditionField) { @@ -2161,7 +2142,6 @@ class Results $conditionField = isset($this->highlightColumns[$meta->name]) || isset($this->highlightColumns[Util::backquote($meta->name)]); - // Wrap MIME-transformations. [MIME] $transformationPlugin = null; $transformOptions = []; @@ -2172,25 +2152,14 @@ class Results && ! empty($this->mediaTypeMap[$orgFullColName]['transformation']) ) { $file = $this->mediaTypeMap[$orgFullColName]['transformation']; - $includeFile = 'src/Plugins/Transformations/' . $file; + $plugin = $this->transformations->getPluginInstance($file); + if ($plugin instanceof TransformationsInterface) { + $transformationPlugin = $plugin; + $transformOptions = $this->transformations->getOptions( + $this->mediaTypeMap[$orgFullColName]['transformation_options'] ?? '', + ); - if (@file_exists(ROOT_PATH . $includeFile)) { - $className = $this->transformations->getClassName($includeFile); - if (class_exists($className)) { - $plugin = new $className(); - if ($plugin instanceof TransformationsPlugin) { - $transformationPlugin = $plugin; - $transformOptions = $this->transformations->getOptions( - $this->mediaTypeMap[$orgFullColName]['transformation_options'] ?? '', - ); - - $meta->internalMediaType = str_replace( - '_', - '/', - $this->mediaTypeMap[$orgFullColName]['mimetype'], - ); - } - } + $meta->internalMediaType = $this->mediaTypeMap[$orgFullColName]['mimetype']; } } @@ -2200,33 +2169,25 @@ class Results $tblLower = mb_strtolower($meta->orgtable); $nameLower = mb_strtolower($meta->orgname); if ( - ! empty($this->transformationInfo[$dbLower][$tblLower][$nameLower]) + isset($this->transformationInfo[$dbLower][$tblLower][$nameLower]) && isset($row[$i]) && trim($row[$i]) !== '' && ! $_SESSION['tmpval']['hide_transformation'] ) { - /** @psalm-suppress UnresolvableInclude */ - include_once ROOT_PATH . $this->transformationInfo[$dbLower][$tblLower][$nameLower][0]; - $plugin = new $this->transformationInfo[$dbLower][$tblLower][$nameLower][1](); - if ($plugin instanceof TransformationsPlugin) { - $transformationPlugin = $plugin; - $transformOptions = $this->transformations->getOptions( - $this->mediaTypeMap[$orgFullColName]['transformation_options'] ?? '', - ); + $transformationPlugin = new $this->transformationInfo[$dbLower][$tblLower][$nameLower](); + $transformOptions = $this->transformations->getOptions( + $this->mediaTypeMap[$orgFullColName]['transformation_options'] ?? '', + ); - $orgTable = mb_strtolower($meta->orgtable); - $orgName = mb_strtolower($meta->orgname); + $orgTable = mb_strtolower($meta->orgtable); + $orgName = mb_strtolower($meta->orgname); - $meta->internalMediaType = str_replace( - '_', - '/', - $this->transformationInfo[$dbLower][$orgTable][$orgName][2], - ); - } + $meta->internalMediaType = $this->transformationInfo[$dbLower][$orgTable][$orgName]::getMIMEType() + . '_' . $this->transformationInfo[$dbLower][$orgTable][$orgName]::getMIMESubtype(); } // Check for the predefined fields need to show as link in schemas - if (! empty($specialSchemaLinks[$dbLower][$tblLower][$nameLower])) { + if (isset($specialSchemaLinks[$dbLower][$tblLower][$nameLower])) { $linkingUrl = $this->getSpecialLinkUrl( $specialSchemaLinks[$dbLower][$tblLower][$nameLower], $row[$i], @@ -2236,7 +2197,7 @@ class Results $transformOptions = [0 => $linkingUrl, 2 => true]; - $meta->internalMediaType = str_replace('_', '/', 'Text/Plain'); + $meta->internalMediaType = 'Text_Plain'; } $expressions = []; @@ -2685,7 +2646,7 @@ class Results FieldMetadata $meta, array $map, StatementInfo $statementInfo, - TransformationsPlugin|null $transformationPlugin, + TransformationsInterface|null $transformationPlugin, array $transformOptions, ): string { if ($column === null) { @@ -2735,7 +2696,7 @@ class Results array $map, array $urlParams, bool $conditionField, - TransformationsPlugin|null $transformationPlugin, + TransformationsInterface|null $transformationPlugin, array $transformOptions, StatementInfo $statementInfo, ): string { @@ -2851,13 +2812,13 @@ class Results array $map, array $urlParams, bool $conditionField, - TransformationsPlugin|null $transformationPlugin, + TransformationsInterface|null $transformationPlugin, array $transformOptions, StatementInfo $statementInfo, ): string { $originalLength = 0; - $bIsText = $transformationPlugin !== null && ! str_contains($transformationPlugin->getMIMEType(), 'Text'); + $bIsText = $transformationPlugin !== null && ! str_contains($transformationPlugin::getMIMEType(), 'Text'); // disable inline grid editing // if binary fields are protected @@ -2892,7 +2853,7 @@ class Results $isFieldTruncated = false; if ( ! ($transformationPlugin !== null - && str_contains($transformationPlugin->getName(), 'Link')) + && str_contains($transformationPlugin::getName(), 'Link')) && ! $meta->isBinary() ) { [$isFieldTruncated, $column, $originalLength] = $this->getPartialText($column); @@ -3709,7 +3670,7 @@ class Results private function handleNonPrintableContents( string $category, string|null $content, - TransformationsPlugin|null $transformationPlugin, + TransformationsInterface|null $transformationPlugin, array $transformOptions, FieldMetadata $meta, array $urlParams = [], @@ -3733,10 +3694,10 @@ class Results // if we want to use a text transformation on a BLOB column if ($transformationPlugin !== null) { $posMimeOctetstream = strpos( - $transformationPlugin->getMIMESubtype(), + $transformationPlugin::getMIMESubtype(), 'Octetstream', ); - if ($posMimeOctetstream || str_contains($transformationPlugin->getMIMEType(), 'Text')) { + if ($posMimeOctetstream || str_contains($transformationPlugin::getMIMEType(), 'Text')) { // Applying Transformations on hex string of binary data // seems more appropriate $result = pack('H*', bin2hex($content)); @@ -3853,7 +3814,7 @@ class Results array $map, string $data, string $displayedData, - TransformationsPlugin|null $transformationPlugin, + TransformationsInterface|null $transformationPlugin, string $nowrap, string $whereComparison, array $transformOptions, diff --git a/src/InsertEdit.php b/src/InsertEdit.php index e4208bd6f0..d08ca492a7 100644 --- a/src/InsertEdit.php +++ b/src/InsertEdit.php @@ -8,7 +8,7 @@ use PhpMyAdmin\ConfigStorage\Relation; use PhpMyAdmin\Dbal\ResultInterface; use PhpMyAdmin\Html\Generator; use PhpMyAdmin\Plugins\IOTransformationsPlugin; -use PhpMyAdmin\Plugins\TransformationsPlugin; +use PhpMyAdmin\Plugins\TransformationsInterface; use PhpMyAdmin\Utils\Gis; use function __; @@ -17,7 +17,6 @@ use function array_key_exists; use function array_merge; use function array_values; use function bin2hex; -use function class_exists; use function count; use function current; use function explode; @@ -25,7 +24,6 @@ use function htmlspecialchars; use function implode; use function in_array; use function is_array; -use function is_file; use function is_string; use function json_encode; use function max; @@ -1106,36 +1104,31 @@ class InsertEdit array $extraData, string $type, ): array { - $includeFile = 'src/Plugins/Transformations/' . $file; - if (is_file(ROOT_PATH . $includeFile)) { - // $cfg['SaveCellsAtOnce'] = true; JS code sends an array - $whereClause = is_array($_POST['where_clause']) ? $_POST['where_clause'][0] : $_POST['where_clause']; - $urlParams = [ - 'db' => $db, - 'table' => $table, - 'where_clause_sign' => Core::signSqlQuery($whereClause), - 'where_clause' => $whereClause, - 'transform_key' => $columnName, - ]; - $transformOptions = $this->transformations->getOptions($transformation[$type . '_options'] ?? ''); - $transformOptions['wrapper_link'] = Url::getCommon($urlParams); - $transformOptions['wrapper_params'] = $urlParams; - $className = $this->transformations->getClassName($includeFile); - if (class_exists($className)) { - /** @var TransformationsPlugin $transformationPlugin */ - $transformationPlugin = new $className(); + // $cfg['SaveCellsAtOnce'] = true; JS code sends an array + $whereClause = is_array($_POST['where_clause']) ? $_POST['where_clause'][0] : $_POST['where_clause']; + $urlParams = [ + 'db' => $db, + 'table' => $table, + 'where_clause_sign' => Core::signSqlQuery($whereClause), + 'where_clause' => $whereClause, + 'transform_key' => $columnName, + ]; + $transformOptions = $this->transformations->getOptions($transformation[$type . '_options'] ?? ''); + $transformOptions['wrapper_link'] = Url::getCommon($urlParams); + $transformOptions['wrapper_params'] = $urlParams; - foreach ($editedValues as $cellIndex => $currCellEditedValues) { - if (! isset($currCellEditedValues[$columnName])) { - continue; - } - - $extraData['transformations'][$cellIndex] = $transformationPlugin->applyTransformation( - $currCellEditedValues[$columnName], - $transformOptions, - ); - $editedValues[$cellIndex][$columnName] = $extraData['transformations'][$cellIndex]; + $transformationPlugin = $this->transformations->getPluginInstance($file); + if ($transformationPlugin instanceof TransformationsInterface) { + foreach ($editedValues as $cellIndex => $currCellEditedValues) { + if (! isset($currCellEditedValues[$columnName])) { + continue; } + + $extraData['transformations'][$cellIndex] = $transformationPlugin->applyTransformation( + $currCellEditedValues[$columnName], + $transformOptions, + ); + $editedValues[$cellIndex][$columnName] = $extraData['transformations'][$cellIndex]; } } @@ -1696,40 +1689,33 @@ class InsertEdit $transformedHtml = ''; if (! empty($columnMime['input_transformation'])) { $file = $columnMime['input_transformation']; - $includeFile = 'src/Plugins/Transformations/' . $file; - if (is_file(ROOT_PATH . $includeFile)) { - $className = $this->transformations->getClassName($includeFile); - if (class_exists($className)) { - $transformationPlugin = new $className(); - $transformationOptions = $this->transformations->getOptions( - $columnMime['input_transformation_options'], - ); - $urlParams = [ - 'db' => $db, - 'table' => $table, - 'transform_key' => $column->field, - 'where_clause_sign' => Core::signSqlQuery($whereClause), - 'where_clause' => $whereClause, - ]; - $transformationOptions['wrapper_link'] = Url::getCommon($urlParams); - $transformationOptions['wrapper_params'] = $urlParams; - $currentValue = $currentRow[$column->field] ?? ''; + $transformationPlugin = $this->transformations->getPluginInstance((string) $file); + if ($transformationPlugin instanceof IOTransformationsPlugin) { + $transformationOptions = $this->transformations->getOptions( + $columnMime['input_transformation_options'], + ); + $urlParams = [ + 'db' => $db, + 'table' => $table, + 'transform_key' => $column->field, + 'where_clause_sign' => Core::signSqlQuery($whereClause), + 'where_clause' => $whereClause, + ]; + $transformationOptions['wrapper_link'] = Url::getCommon($urlParams); + $transformationOptions['wrapper_params'] = $urlParams; - if ($transformationPlugin instanceof IOTransformationsPlugin) { - $transformedHtml = $transformationPlugin->getInputHtml( - $columnNameAppendix, - $transformationOptions, - $currentValue, - $textDir, - $this->fieldIndex, - ); + $transformedHtml = $transformationPlugin->getInputHtml( + $columnNameAppendix, + $transformationOptions, + $currentRow[$column->field] ?? '', + $textDir, + $this->fieldIndex, + ); - $GLOBALS['plugin_scripts'] = array_merge( - $GLOBALS['plugin_scripts'], - $transformationPlugin->getScripts(), - ); - } - } + $GLOBALS['plugin_scripts'] = array_merge( + $GLOBALS['plugin_scripts'], + $transformationPlugin->getScripts(), + ); } } diff --git a/src/Plugins/TransformationsInterface.php b/src/Plugins/TransformationsInterface.php index 21e4bad6c8..126bd6baef 100644 --- a/src/Plugins/TransformationsInterface.php +++ b/src/Plugins/TransformationsInterface.php @@ -7,6 +7,8 @@ declare(strict_types=1); namespace PhpMyAdmin\Plugins; +use PhpMyAdmin\FieldMetadata; + /** * Provides a common interface that will have to be implemented by all of the * transformations plugins. @@ -32,4 +34,26 @@ interface TransformationsInterface * Gets the transformation name of the specific plugin */ public static function getName(): string; + + /** + * Does the actual work of each specific transformations plugin. + * + * @param string $buffer text to be transformed + * @param mixed[] $options transformation options + * @param FieldMetadata|null $meta meta information + * + * @return string the transformed text + */ + public function applyTransformation( + string $buffer, + array $options = [], + FieldMetadata|null $meta = null, + ): string; + + /** + * Returns true if the element requires no wrapping + * + * @param mixed[] $options transformation options + */ + public function applyTransformationNoWrap(array $options = []): bool; } diff --git a/src/Plugins/TransformationsPlugin.php b/src/Plugins/TransformationsPlugin.php index 9acca49b32..224a77f70f 100644 --- a/src/Plugins/TransformationsPlugin.php +++ b/src/Plugins/TransformationsPlugin.php @@ -7,8 +7,6 @@ declare(strict_types=1); namespace PhpMyAdmin\Plugins; -use PhpMyAdmin\FieldMetadata; - /** * Provides a common interface that will have to * be implemented by all of the transformations plugins. @@ -16,7 +14,7 @@ use PhpMyAdmin\FieldMetadata; abstract class TransformationsPlugin implements TransformationsInterface { /** - * Does the actual work of each specific transformations plugin. + * Returns true if the element requires no wrapping * * @param mixed[] $options transformation options */ @@ -25,21 +23,6 @@ abstract class TransformationsPlugin implements TransformationsInterface return false; } - /** - * Does the actual work of each specific transformations plugin. - * - * @param string $buffer text to be transformed - * @param mixed[] $options transformation options - * @param FieldMetadata|null $meta meta information - * - * @return string the transformed text - */ - abstract public function applyTransformation( - string $buffer, - array $options = [], - FieldMetadata|null $meta = null, - ): string; - /** * Returns passed options or default values if they were not set * diff --git a/src/Transformations.php b/src/Transformations.php index f79e8b1f44..168a37db4c 100644 --- a/src/Transformations.php +++ b/src/Transformations.php @@ -184,11 +184,21 @@ class Transformations * * @param string $filename transformation file name * - * @return string the class name of transformation + * @return class-string the class name of transformation */ - public function getClassName(string $filename): string + private function getClassName(string $filename): string { - return 'PhpMyAdmin\\' . str_replace('/', '\\', mb_substr(explode('.php', $filename)[0], strlen('src/'))); + return 'PhpMyAdmin\\Plugins\\Transformations\\' . str_replace('/', '\\', explode('.php', $filename)[0]); + } + + public function getPluginInstance(string $filename): TransformationsInterface|null + { + $className = $this->getClassName($filename); + if (class_exists($className)) { + return new $className(); + } + + return null; } /** @@ -200,9 +210,7 @@ class Transformations */ public function getDescription(string $file): string { - $includeFile = 'src/Plugins/Transformations/' . $file; - /** @psalm-var class-string $className */ - $className = $this->getClassName($includeFile); + $className = $this->getClassName($file); if (class_exists($className)) { return $className::getInfo(); } @@ -219,9 +227,7 @@ class Transformations */ public function getName(string $file): string { - $includeFile = 'src/Plugins/Transformations/' . $file; - /** @psalm-var class-string $className */ - $className = $this->getClassName($includeFile); + $className = $this->getClassName($file); if (class_exists($className)) { return $className::getName(); }