diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ab7b6f5..a3e99bd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fix fields values updates not applying the expected checks in some cases - Fix ticket observers being able to edit and save additional fields they are not allowed to modify - Fix `Yes/No` field values returned as strings instead of integers in search results +- Fix fatal error during plugin update caused by outdated generated dropdown classes ## [1.25.0] - 2026-10-06 diff --git a/hook.php b/hook.php index 61c34778..59ef72bb 100644 --- a/hook.php +++ b/hook.php @@ -51,6 +51,10 @@ function plugin_fields_install() $version = $plugin_fields->fields['version']; + // Remove generated classes before migration, they may be incompatible with current GLPI version + // and would trigger a fatal error if autoloaded before being regenerated. + // They will be regenerated by `installUserData()` methods. + array_map(unlink(...), glob(PLUGINFIELDS_CLASS_PATH . '/*.class.php') ?: []); $migration = new Migration($version); if (isCommandLine()) { diff --git a/tests/Units/InstallOutdatedGeneratedFilesTest.php b/tests/Units/InstallOutdatedGeneratedFilesTest.php new file mode 100644 index 00000000..07c04264 --- /dev/null +++ b/tests/Units/InstallOutdatedGeneratedFilesTest.php @@ -0,0 +1,117 @@ +. + * ------------------------------------------------------------------------- + * @copyright Copyright (C) 2013-2023 by Fields plugin team. + * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + * @link https://github.com/pluginsGLPI/fields + * ------------------------------------------------------------------------- + */ + +declare(strict_types=1); + +namespace GlpiPlugin\Field\Tests\Units; + +use Computer; +use DBmysql; +use Glpi\Tests\DbTestCase; +use Glpi\Tests\GLPITestCase; +use GlpiPlugin\Field\Tests\FieldTestTrait; +use PluginFieldsContainer; +use PluginFieldsField; + +require_once __DIR__ . '/../FieldTestCase.php'; +require_once __DIR__ . '/../../hook.php'; + +final class InstallOutdatedGeneratedFilesTest extends DbTestCase +{ + use FieldTestTrait; + + public function setUp(): void + { + GLPITestCase::setUp(); + $this->login(); + } + + public function tearDown(): void + { + $this->tearDownFieldTest(); + GLPITestCase::tearDown(); + } + + public function testInstallDoesNotLoadOutdatedGeneratedDropdownClass(): void + { + /** @var DBmysql $DB */ + global $DB; + + $container = $this->createFieldContainer([ + 'label' => 'Outdated files container', + 'type' => 'tab', + 'itemtypes' => [Computer::class], + 'is_active' => 1, + 'entities_id' => 0, + ]); + + $field = $this->createField([ + 'label' => 'Outdated dropdown', + 'type' => 'dropdown', + PluginFieldsContainer::getForeignKeyField() => $container->getID(), + 'ranking' => 1, + 'is_active' => 1, + 'is_readonly' => 0, + ]); + $this->assertSame('outdateddropdownfield', $field->fields['name']); + + // Simulate a dropdown generated by a previous plugin version, with a system name that does not match + // its label anymore (it will be renamed by the migration) and a class that has not been loaded yet. + $old_name = 'notloadedyetfield'; + $container_classname = PluginFieldsContainer::getClassname(Computer::class, $container->fields['name']); + $container_table = getTableForItemType($container_classname); + $container_class_file = PLUGINFIELDS_CLASS_PATH . '/' . strtolower(substr($container_classname, strlen('PluginFields'))) . '.class.php'; + $DB->update(PluginFieldsField::getTable(), ['name' => $old_name], ['id' => $field->getID()]); + $DB->doQuery('RENAME TABLE `glpi_plugin_fields_outdateddropdownfielddropdowns` TO `glpi_plugin_fields_notloadedyetfielddropdowns`'); + $DB->doQuery( + "ALTER TABLE `{$container_table}` CHANGE `plugin_fields_outdateddropdownfielddropdowns_id` `plugin_fields_notloadedyetfielddropdowns_id` INT UNSIGNED DEFAULT NULL", + ); + + // Outdated class file, that would trigger a fatal error if loaded (e.g. incompatible property declaration). + $outdated_file = PLUGINFIELDS_CLASS_PATH . '/' . $old_name . 'dropdown.class.php'; + file_put_contents($outdated_file, 'assertFileExists($container_class_file); + + ob_start(); + $result = plugin_fields_install(); + ob_end_clean(); + $DB->clearSchemaCache(); + + $this->assertTrue($result); + + // Outdated file has been removed, dropdown has been renamed and its files regenerated + $this->assertFileDoesNotExist($outdated_file); + $this->assertTrue($field->getFromDB($field->getID())); + $this->assertSame('outdateddropdownfield', $field->fields['name']); + $this->assertTrue($DB->tableExists('glpi_plugin_fields_outdateddropdownfielddropdowns')); + $this->assertFileExists(PLUGINFIELDS_CLASS_PATH . '/outdateddropdownfielddropdown.class.php'); + $this->assertFileExists($container_class_file); + } +}