diff --git a/EMS/admin-ui-bundle/src/Resources/views/bootstrap5/form/fields.html.twig b/EMS/admin-ui-bundle/src/Resources/views/bootstrap5/form/fields.html.twig index 56bf81a6a..6109d0aeb 100644 --- a/EMS/admin-ui-bundle/src/Resources/views/bootstrap5/form/fields.html.twig +++ b/EMS/admin-ui-bundle/src/Resources/views/bootstrap5/form/fields.html.twig @@ -485,15 +485,6 @@ {{ parent() }} {% endblock form_errors %} -{% block choice_errors %} - {% if form.parent is defined and form.parent.vars.helptext is defined and form.parent.vars.helptext %} -
{{ form.parent.vars.helptext|raw }}
- {% endif %} - {{ block('form_errors') }} -{% endblock choice_errors %} - - - {% block tabsfieldtype_row -%} {#
#} {#
#} diff --git a/EMS/core-bundle/src/Resources/views/form/fields.html.twig b/EMS/core-bundle/src/Resources/views/form/fields.html.twig index e424292d6..d9e865321 100644 --- a/EMS/core-bundle/src/Resources/views/form/fields.html.twig +++ b/EMS/core-bundle/src/Resources/views/form/fields.html.twig @@ -457,15 +457,6 @@ {{ parent() }} {% endblock form_errors %} -{% block choice_errors %} - {% if form.parent is defined and form.parent.vars.helptext is defined and form.parent.vars.helptext %} -
{{ form.parent.vars.helptext|raw }}
- {% endif %} - {{ block('form_errors') }} -{% endblock choice_errors %} - - - {% block tabsfieldtype_row -%} {#
#} {#
#} diff --git a/EMS/helpers/src/Html/Sanitizer/HtmlSanitizerClass.php b/EMS/helpers/src/Html/Sanitizer/HtmlSanitizerClass.php index 11c3ad386..789b30a59 100644 --- a/EMS/helpers/src/Html/Sanitizer/HtmlSanitizerClass.php +++ b/EMS/helpers/src/Html/Sanitizer/HtmlSanitizerClass.php @@ -31,6 +31,10 @@ public function sanitizeAttribute(string $element, string $attribute, string $va $classes = \explode(' ', $value); $classNames = \array_filter($classes, 'trim'); + if (\count($this->settings['replace']) > 0) { + $classNames = \array_map(fn (string $className) => $this->settings['replace'][$className] ?? $className, $classNames); + } + if (\count($this->settings['allow']) > 0) { $classNames = \array_filter($classNames, fn (string $className) => \in_array($className, $this->settings['allow'])); } @@ -39,10 +43,6 @@ public function sanitizeAttribute(string $element, string $attribute, string $va $classNames = \array_filter($classNames, fn (string $className) => !\in_array($className, $this->settings['drop'])); } - if (\count($this->settings['replace']) > 0) { - $classNames = \array_map(fn (string $className) => $this->settings['replace'][$className] ?? $className, $classNames); - } - return \count($classNames) > 0 ? \implode(' ', $classNames) : null; } } diff --git a/EMS/helpers/src/Image/SmartCrop.php b/EMS/helpers/src/Image/SmartCrop.php index da6756085..3bfea1609 100644 --- a/EMS/helpers/src/Image/SmartCrop.php +++ b/EMS/helpers/src/Image/SmartCrop.php @@ -316,7 +316,7 @@ private function importance(array $crop, int $x, int $y): float private function thirds(float $x): float { - $x = (($x - (1 / 3) + 1.0) % 2.0 * 0.5 - 0.5) * 16; + $x = ((int) ($x - (1 / 3) + 1.0) % 2.0 * 0.5 - 0.5) * 16; return \max(1.0 - $x * $x, 0.0); } diff --git a/EMS/helpers/tests/Unit/File/FileAiTest.php b/EMS/helpers/tests/Unit/File/FileAiTest.php new file mode 100644 index 000000000..d7b6163a2 --- /dev/null +++ b/EMS/helpers/tests/Unit/File/FileAiTest.php @@ -0,0 +1,49 @@ +assertInstanceOf(File::class, $file); + $this->assertEquals('file.txt', $file->name); + $this->assertEquals('txt', $file->extension); + $this->assertIsInt($file->size); + $this->assertEquals('text/plain', $file->mimeType); + } + + public function testFromFilename() + { + $file = File::fromFilename(self::TEST_FILE_PATH); + $this->assertInstanceOf(File::class, $file); + } + + public function testChunk() + { + $file = new File(new \SplFileInfo(self::TEST_FILE_PATH)); + $chunks = $file->chunk(0, 1024); + + foreach ($chunks as $chunk) { + $this->assertLessThanOrEqual(1024, \strlen($chunk)); + } + } +} diff --git a/EMS/helpers/tests/Unit/File/FolderAiTest.php b/EMS/helpers/tests/Unit/File/FolderAiTest.php new file mode 100644 index 000000000..2a578b0ea --- /dev/null +++ b/EMS/helpers/tests/Unit/File/FolderAiTest.php @@ -0,0 +1,36 @@ +testFolderPath = \sys_get_temp_dir().DIRECTORY_SEPARATOR.'/path/to/test/folder'; + } + + public function testGetRealPathWithExistingDirectory() + { + \mkdir($this->testFolderPath, 0777, true); + $realPath = Folder::getRealPath($this->testFolderPath); + $this->assertEquals(\realpath($this->testFolderPath), $realPath); + } + + public function testGetRealPathWithNonExistingDirectory() + { + $realPath = Folder::getRealPath($this->testFolderPath); + $this->assertEquals(\realpath($this->testFolderPath), $realPath); + } + + protected function tearDown(): void + { + \rmdir($this->testFolderPath); + } +} diff --git a/EMS/helpers/tests/Unit/File/TempFileAiTest.php b/EMS/helpers/tests/Unit/File/TempFileAiTest.php new file mode 100644 index 000000000..e67a264b1 --- /dev/null +++ b/EMS/helpers/tests/Unit/File/TempFileAiTest.php @@ -0,0 +1,46 @@ +assertInstanceOf(TempFile::class, $tempFile); + $this->assertTrue(\file_exists($tempFile->path)); + } + + public function testCreateNamed() + { + $name = 'testfile.txt'; + $tempFile = TempFile::createNamed($name); + $this->assertInstanceOf(TempFile::class, $tempFile); + $this->assertEquals(\sys_get_temp_dir().DIRECTORY_SEPARATOR.'EMS_temp_file_'.$name, $tempFile->path); + } + + public function testLoadFromStream() + { + $tempFile = TempFile::create(); + $stream = $this->createMock(StreamInterface::class); + $stream->method('eof')->willReturnOnConsecutiveCalls(false, true); + $stream->method('read')->willReturn('Test content'); + + $tempFile->loadFromStream($stream); + $this->assertTrue(\file_exists($tempFile->path)); + $this->assertEquals('Test content', \file_get_contents($tempFile->path)); + } + + public function testClean() + { + $tempFile = TempFile::create(); + $tempFile->clean(); + $this->assertFalse(\file_exists($tempFile->path)); + } +} diff --git a/EMS/helpers/tests/Unit/Html/Sanitizer/HtmlSanitizerClassAiTest.php b/EMS/helpers/tests/Unit/Html/Sanitizer/HtmlSanitizerClassAiTest.php new file mode 100644 index 000000000..7a7372f92 --- /dev/null +++ b/EMS/helpers/tests/Unit/Html/Sanitizer/HtmlSanitizerClassAiTest.php @@ -0,0 +1,57 @@ +sanitizer = new HtmlSanitizerClass([ + 'allow' => ['allowed-class', 'new-class'], + 'drop' => ['dropped-class'], + 'replace' => ['old-class' => 'new-class'], + ]); + } + + public function testGetSupportedElements(): void + { + $this->assertNull($this->sanitizer->getSupportedElements()); + } + + public function testGetSupportedAttributes(): void + { + $expectedAttributes = ['class']; + $this->assertEquals($expectedAttributes, $this->sanitizer->getSupportedAttributes()); + } + + public function testSanitizeAttribute(): void + { + $config = $this->createMock(HtmlSanitizerConfig::class); + $element = 'div'; + $attribute = 'class'; + + // Test allow + $value = 'allowed-class'; + $this->assertEquals('allowed-class', $this->sanitizer->sanitizeAttribute($element, $attribute, $value, $config)); + + // Test drop + $value = 'dropped-class'; + $this->assertNull($this->sanitizer->sanitizeAttribute($element, $attribute, $value, $config)); + + // Test replace + $value = 'old-class'; + $this->assertEquals('new-class', $this->sanitizer->sanitizeAttribute($element, $attribute, $value, $config)); + + // Test multiple classes + $value = 'allowed-class dropped-class old-class'; + $this->assertEquals('allowed-class new-class', $this->sanitizer->sanitizeAttribute($element, $attribute, $value, $config)); + } +} diff --git a/EMS/helpers/tests/Unit/Html/Sanitizer/HtmlSanitizerLinkAiTest.php b/EMS/helpers/tests/Unit/Html/Sanitizer/HtmlSanitizerLinkAiTest.php new file mode 100644 index 000000000..2b626be31 --- /dev/null +++ b/EMS/helpers/tests/Unit/Html/Sanitizer/HtmlSanitizerLinkAiTest.php @@ -0,0 +1,62 @@ +sanitizer = new HtmlSanitizerLink(); + } + + public function testSanitizeAttributeWithEmsScheme() + { + $element = 'a'; + $attribute = 'href'; + $value = 'ems://some-resource'; + $config = new HtmlSanitizerConfig(); + + $sanitizedValue = $this->sanitizer->sanitizeAttribute($element, $attribute, $value, $config); + $this->assertEquals('ems://some-resource', $sanitizedValue); + } + + public function testSanitizeAttributeWithHttpScheme() + { + $element = 'a'; + $attribute = 'href'; + $value = 'http://example.com'; + $config = new HtmlSanitizerConfig(); + + $sanitizedValue = $this->sanitizer->sanitizeAttribute($element, $attribute, $value, $config); + $this->assertEquals('http://example.com', $sanitizedValue); + } + + public function testSanitizeAttributeWithJavascriptScheme() + { + $element = 'a'; + $attribute = 'href'; + $value = 'javascript:alert(1)'; + $config = new HtmlSanitizerConfig(); + + $sanitizedValue = $this->sanitizer->sanitizeAttribute($element, $attribute, $value, $config); + $this->assertNull($sanitizedValue); + } + + public function testGetSupportedElements() + { + $this->assertNull($this->sanitizer->getSupportedElements()); + } + + public function testGetSupportedAttributes() + { + $this->assertEquals(['src', 'href', 'lowsrc', 'background', 'ping'], $this->sanitizer->getSupportedAttributes()); + } +} diff --git a/EMS/helpers/tests/Unit/Image/SmartCropAiTest.php b/EMS/helpers/tests/Unit/Image/SmartCropAiTest.php new file mode 100644 index 000000000..d12ce5f10 --- /dev/null +++ b/EMS/helpers/tests/Unit/Image/SmartCropAiTest.php @@ -0,0 +1,53 @@ +image = \imagecreatetruecolor(200, 200); + } + + protected function tearDown(): void + { + \imagedestroy($this->image); + } + + public function testConstructor() + { + $smartCrop = new SmartCrop($this->image, $this->cropWidth, $this->cropHeight); + $this->assertInstanceOf(SmartCrop::class, $smartCrop); + } + + public function testAnalyse() + { + $smartCrop = new SmartCrop($this->image, $this->cropWidth, $this->cropHeight); + $result = $smartCrop->analyse(); + $this->assertIsArray($result); + $this->assertArrayHasKey('topCrop', $result); + } + + public function testCrop() + { + $smartCrop = new SmartCrop($this->image, $this->cropWidth, $this->cropHeight); + $cropped = $smartCrop->crop(50, 50, $this->cropWidth, $this->cropHeight); + $this->assertInstanceOf(SmartCrop::class, $cropped); + } + + public function testGet() + { + $smartCrop = new SmartCrop($this->image, $this->cropWidth, $this->cropHeight); + $resultImage = $smartCrop->get(); + $this->assertInstanceOf(\GdImage::class, $resultImage); + } +} diff --git a/EMS/helpers/tests/Unit/Standard/AccessorAiTest.php b/EMS/helpers/tests/Unit/Standard/AccessorAiTest.php new file mode 100644 index 000000000..4df3af692 --- /dev/null +++ b/EMS/helpers/tests/Unit/Standard/AccessorAiTest.php @@ -0,0 +1,19 @@ +assertSame('[field][subfield]', Accessor::fieldPathToPropertyPath('field.subfield')); + $this->assertSame('[field][0]', Accessor::fieldPathToPropertyPath('field[0]')); + $this->assertSame('[field][subfield][0]', Accessor::fieldPathToPropertyPath('field.subfield[0]')); + $this->assertSame('[field][sub][0]', Accessor::fieldPathToPropertyPath('field.sub[0]')); + } +} diff --git a/EMS/helpers/tests/Unit/Standard/Base64AiTest.php b/EMS/helpers/tests/Unit/Standard/Base64AiTest.php new file mode 100644 index 000000000..8d593755e --- /dev/null +++ b/EMS/helpers/tests/Unit/Standard/Base64AiTest.php @@ -0,0 +1,31 @@ +assertEquals(\base64_encode($originalString), $encodedString); + } + + public function testDecode() + { + $encodedString = \base64_encode('Test String'); + $decodedString = Base64::decode($encodedString); + $this->assertEquals('Test String', $decodedString); + } + + public function testDecodeThrowsRuntimeExceptionOnInvalidBase64() + { + $this->expectException(\RuntimeException::class); + Base64::decode('@'); + } +} diff --git a/EMS/helpers/tests/Unit/Standard/ColorAiTest.php b/EMS/helpers/tests/Unit/Standard/ColorAiTest.php new file mode 100644 index 000000000..bf63197b3 --- /dev/null +++ b/EMS/helpers/tests/Unit/Standard/ColorAiTest.php @@ -0,0 +1,107 @@ +assertEquals(60, $color->getRed()); + $this->assertEquals(141, $color->getGreen()); + $this->assertEquals(188, $color->getBlue()); + } + + public function testConstructorWithStandardHtmlColor() + { + $color = new Color('blue'); + $this->assertEquals(0, $color->getRed()); + $this->assertEquals(0, $color->getGreen()); + $this->assertEquals(255, $color->getBlue()); + } + + public function testConstructorWithHexColor() + { + $color = new Color('#FF5733'); + $this->assertEquals(255, $color->getRed()); + $this->assertEquals(87, $color->getGreen()); + $this->assertEquals(51, $color->getBlue()); + } + + public function testGetSetRed() + { + $color = new Color('#000000'); + $color->setRed(123); + $this->assertEquals(123, $color->getRed()); + } + + public function testGetSetGreen() + { + $color = new Color('#000000'); + $color->setGreen(123); + $this->assertEquals(123, $color->getGreen()); + } + + public function testGetSetBlue() + { + $color = new Color('#000000'); + $color->setBlue(123); + $this->assertEquals(123, $color->getBlue()); + } + + public function testGetSetAlpha() + { + $color = new Color('#000000'); + $color->setAlpha(123); + $this->assertEquals(123, $color->getAlpha()); + } + + public function testGetColorId() + { + $color = new Color('#000000'); + $image = \imagecreatetruecolor(100, 100); + $colorId = $color->getColorId($image); + $this->assertIsInt($colorId); + \imagedestroy($image); + } + + public function testRelativeLuminance() + { + $color = new Color('#FFFFFF'); + $this->assertEquals(1.0, $color->relativeLuminance(), '', 0.01); + } + + public function testContrastRatio() + { + $color1 = new Color('#FFFFFF'); + $color2 = new Color('#000000'); + $this->assertEquals(21, $color1->contrastRatio($color2), '', 0.01); + } + + public function testGetComplementary() + { + $color = new Color('#FFFFFF'); + $complementary = $color->getComplementary(); + $this->assertEquals(0, $complementary->getRed()); + $this->assertEquals(0, $complementary->getGreen()); + $this->assertEquals(0, $complementary->getBlue()); + } + + public function testGetRGB() + { + $color = new Color('#FF5733'); + $this->assertEquals('#FF5733', $color->getRGB()); + } + + public function testGetRGBA() + { + $color = new Color('#FF5733'); + $color->setAlpha(127); + $this->assertEquals('#FF57337F', $color->getRGBA()); + } +} diff --git a/EMS/helpers/tests/Unit/Standard/DateTimeAiTest.php b/EMS/helpers/tests/Unit/Standard/DateTimeAiTest.php new file mode 100644 index 000000000..d088cf2e0 --- /dev/null +++ b/EMS/helpers/tests/Unit/Standard/DateTimeAiTest.php @@ -0,0 +1,39 @@ +assertInstanceOf(\DateTimeImmutable::class, $dateTime); + $this->assertEquals('2024-01-01T00:00:00+00:00', $dateTime->format(\DateTimeInterface::ATOM)); + } + + public function testCreateThrowsRuntimeExceptionOnInvalidTime() + { + $this->expectException(\RuntimeException::class); + DateTime::create('invalid-time-string'); + } + + public function testCreateFromFormat() + { + $timeString = '2024-01-01T00:00:00+00:00'; + $dateTime = DateTime::createFromFormat($timeString, \DateTimeInterface::ATOM); + $this->assertInstanceOf(\DateTimeImmutable::class, $dateTime); + $this->assertEquals($timeString, $dateTime->format(\DateTimeInterface::ATOM)); + } + + public function testCreateFromFormatThrowsRuntimeExceptionOnInvalidFormat() + { + $this->expectException(\RuntimeException::class); + DateTime::createFromFormat('2024-01-01 00:00:00', 'invalid-format'); + } +} diff --git a/EMS/helpers/tests/Unit/Standard/HashAiTest.php b/EMS/helpers/tests/Unit/Standard/HashAiTest.php new file mode 100644 index 000000000..31e23f54f --- /dev/null +++ b/EMS/helpers/tests/Unit/Standard/HashAiTest.php @@ -0,0 +1,41 @@ +assertEquals(\sha1($originalString), $hashedString); + } + + public function testStringHashWithPrefix() + { + $originalString = 'Test String'; + $prefix = 'prefix_'; + $hashedString = Hash::string($originalString, $prefix); + $this->assertEquals($prefix.\sha1($originalString), $hashedString); + } + + public function testArrayHash() + { + $array = ['key' => 'value']; + $hashedArray = Hash::array($array); + $this->assertEquals(\sha1(\json_encode($array)), $hashedArray); + } + + public function testArrayHashWithPrefix() + { + $array = ['key' => 'value']; + $prefix = 'prefix_'; + $hashedArray = Hash::array($array, $prefix); + $this->assertEquals($prefix.\sha1(\json_encode($array)), $hashedArray); + } +} diff --git a/EMS/helpers/tests/Unit/Standard/TypeAiTest.php b/EMS/helpers/tests/Unit/Standard/TypeAiTest.php index a90d74189..15c24a576 100644 --- a/EMS/helpers/tests/Unit/Standard/TypeAiTest.php +++ b/EMS/helpers/tests/Unit/Standard/TypeAiTest.php @@ -9,31 +9,49 @@ class TypeAiTest extends TestCase { - public function testStringReturnsString(): void + public function testStringWithValidString() { - $input = 'test'; - $result = Type::string($input); - $this->assertSame($input, $result); + $this->assertEquals('test', Type::string('test')); } - public function testStringThrowsExceptionForNonString(): void + public function testStringWithInvalidType() { $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage("Expect a string got 'integer'"); Type::string(123); } - public function testIntegerReturnsInteger(): void + public function testIntegerWithValidInteger() { - $input = 123; - $result = Type::integer($input); - $this->assertSame($input, $result); + $this->assertEquals(123, Type::integer(123)); } - public function testIntegerThrowsExceptionForNonInteger(): void + public function testIntegerWithInvalidType() { $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage("Expect an integer got 'string'"); Type::integer('test'); } + + public function testArrayWithValidArray() + { + $this->assertEquals(['key' => 'value'], Type::array(['key' => 'value'])); + } + + public function testArrayWithInvalidType() + { + $this->expectException(\RuntimeException::class); + Type::array('not an array'); + } + + public function testGdImageWithValidGdImage() + { + $image = \imagecreatetruecolor(100, 100); + $this->assertInstanceOf(\GdImage::class, Type::gdImage($image)); + \imagedestroy($image); + } + + public function testGdImageWithInvalidType() + { + $this->expectException(\RuntimeException::class); + Type::gdImage('not a gd image'); + } } diff --git a/docs/dev/helpers/standard.md b/docs/dev/helpers/standard.md index f5074b466..a6978f386 100644 --- a/docs/dev/helpers/standard.md +++ b/docs/dev/helpers/standard.md @@ -101,9 +101,9 @@ $settings = [ ], 'drop_elements' => [ 'iframe' ], // remove all `iframe` elements and content 'classes' => [ - 'allow' => ['my-class', 'my-second-class'], + 'allow' => ['my-class', 'my-second-class', 'new-class'], 'drop' => ['delete', 'remove'], - 'replace' => ['test' => 'example'], + 'replace' => ['old-class' => 'new-class'], // IMPORTANT: add new-class in the 'allow' array ] ] ```