Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [unreleased]
## [Unreleased]

### Fixed

- Fixed table renaming during migration when tables with numeric names exceeded the maximum table name length.

## [1.24.2] - 2026-06-30

Expand Down
80 changes: 67 additions & 13 deletions inc/container.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,18 @@ public static function installBaseData(Migration $migration, $version)
}
}

$container_obj = new self();
foreach ($DB->request(['FROM' => $table]) as $container) {
self::generateTemplate($container);
$itemtypes = PluginFieldsToolbox::decodeJSONItemtypes($container['itemtypes']);
if (empty($itemtypes) || self::checkContainerName($container)) {
continue;
}

$container = $container_obj->setContainerName($container);
return $container_obj->update($container);
}

return true;
}

Expand Down Expand Up @@ -648,6 +660,10 @@ public function defineTabs($options = [])

public function prepareInputForUpdate($input)
{
if (!isset($input['itemtypes'])) {
$input['itemtypes'] = $this->fields['itemtypes'];
}

return PluginFieldsToolbox::prepareLabel($input);
}

Expand Down Expand Up @@ -703,19 +719,8 @@ public function prepareInputForAdd($input)
}

$input = PluginFieldsToolbox::prepareLabel($input);

//reject adding when container name is too long for mysql table name
foreach ($input['itemtypes'] as $itemtype) {
$tmp = getTableForItemType(self::getClassname($itemtype, $input['name']));
if (strlen($tmp) > 64) {
Session::AddMessageAfterRedirect(
__('Container name is too long for database (digits in name are replaced by characters, try to remove them)', 'fields'),
false,
ERROR,
);

return false;
}
if ($input === false) {
return false;
}

//check for already existing container with same name
Expand Down Expand Up @@ -2416,4 +2421,53 @@ public function getCloneRelations(): array
PluginFieldsProfile::class,
];
}

public static function checkContainerName(array $container): bool
{
if (!isset($container['name']) || empty($container['name'])) {
return false;
}

if (!isset($container['itemtypes']) || empty($container['itemtypes'])) {
return true;
}

// Names made entirely of digits are invalid (generated prior to Fields 1.9.2).
if (preg_match('/^\d+$/', (string) $container['name'])) {
return false;
}

$itemtypes = $container['itemtypes'];
if (is_string($itemtypes)) {
$itemtypes = PluginFieldsToolbox::decodeJSONItemtypes($itemtypes);
}

foreach ($itemtypes as $itemtype) {
if (strlen(getTableForItemType(self::getClassname($itemtype, $container['name']))) > 64) {
return false;
}
}

return true;
}

public function setContainerName(array $container): array
{
$base = empty($container['label']) ? $container['name'] : $container['label'];
$new_name = (new PluginFieldsToolbox())->getSystemNameFromLabel((string) $base);
$itemtypes = $container['itemtypes'];
if (is_string($itemtypes)) {
$itemtypes = PluginFieldsToolbox::decodeJSONItemtypes($itemtypes);
}

foreach ($itemtypes as $itemtype) {
while ($new_name !== '' && strlen(getTableForItemType(self::getClassname($itemtype, $new_name))) > 64) {
$new_name = substr($new_name, 0, -1);
}
}

$container['name'] = $new_name;

return $container;
}
}
13 changes: 11 additions & 2 deletions inc/toolbox.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -396,13 +396,22 @@ public static function sanitizeLabel(string $label): string
* Centralises the prepare-label pattern used in prepareInputForAdd/Update.
*
* @param array $input Input array with at least a 'label' key.
* @return array Updated input with sanitized 'label' and derived 'name'.
* @return array|bool Updated input with sanitized 'label' and derived 'name', or false on error.
*/
public static function prepareLabel(array $input): array
public static function prepareLabel(array $input): array|bool
{
$input['label'] = self::sanitizeLabel((string) ($input['label'] ?? ''));
$input['name'] = (new self())->getSystemNameFromLabel($input['label']);

if (!PluginFieldsContainer::checkContainerName($input)) {
Session::addMessageAfterRedirect(
__('Container name is invalid or too long for database', 'fields'),
false,
ERROR,
);
return false;
}

return $input;
}
}
85 changes: 85 additions & 0 deletions tests/Units/ContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,89 @@ public function testAddWithValidItemtypesSucceeds(): void

$this->assertGreaterThan(0, $container->getID());
}

/**
* Provides invalid container names for testing.
*/
public static function provideInvalidContainerName(): iterable
{
yield 'wrong numeric name' => ['7777777777'];
yield 'long name' => [str_repeat('a', 256)];
}

/**
* Tests that adding a container with an invalid name is rejected.
*/
#[DataProvider('provideInvalidContainerName')]
public function testInvalidCheckContainerName(string $label): void
{
$container = new PluginFieldsContainer();
$input = [
'label' => $label,
'itemtypes' => [Computer::class],
'type' => 'tab',
'entities_id' => 0,
];
$result = $container->add($input);
$this->assertFalse($result);

$container = $this->createFieldContainer([
'label' => 'container ' . $this->getUniqueString(),
'type' => 'tab',
'itemtypes' => [Computer::class],
'is_active' => 1,
'entities_id' => 0,
'is_recursive' => 1,
]);
$result = $container->update(['id' => $container->getID(), 'label' => $label]);
$this->assertFalse($result);
}

/**
* Provides valid container names for testing.
*/
public static function provideSuccessContainerName(): iterable
{
yield 'empty name' => [''];
yield 'valid name' => ['Valid Container Name'];
yield 'another valid name' => ['111'];
yield 'numeric and letter name' => ['d7'];
}

/**
* Tests that adding a container with a valid name succeeds.
*/
#[DataProvider('provideSuccessContainerName')]
public function testSuccessCheckContainerName(string $label): void
{
$container = $this->createFieldContainer([
'label' => $label . $this->getUniqueString(),
'type' => 'tab',
'itemtypes' => [Computer::class],
'is_active' => 1,
'entities_id' => 0,
'is_recursive' => 1,
]);

$this->assertGreaterThan(0, $container->getID());
$container = $this->createFieldContainer([
'label' => 'testSuccessCheckContainerName',
'type' => 'tab',
'itemtypes' => [Computer::class],
'is_active' => 1,
'entities_id' => 0,
'is_recursive' => 1,
]);
$update = $container->update([
'id' => $container->getID(),
'label' => $label . $this->getUniqueString(),
]);
$this->assertTrue($update);
// remove the container because tearsdown is only triggered when another test is run,
// and this test is the last one to be run
$delete = $container->delete([
'id' => $container->getID(),
], true);
$this->assertTrue($delete);
}
Comment thread
stonebuzz marked this conversation as resolved.
}
Loading