diff --git a/app/Liquid/Filters/Data.php b/app/Liquid/Filters/Data.php new file mode 100644 index 0000000..5b1f92f --- /dev/null +++ b/app/Liquid/Filters/Data.php @@ -0,0 +1,22 @@ +locale($locale); + } + + return $carbon->translatedFormat($format); + } + + /** + * Translate a common word to another language + * + * @param string $word The word to translate + * @param string $locale The locale to translate to + * @return string The translated word + */ + public function l_word(string $word, string $locale): string + { + $translation = trans('custom_plugins.'.mb_strtolower($word), locale: $locale); + + if ($translation === 'custom_plugins.'.mb_strtolower($word)) { + return $word; + } + + return $translation; + } +} diff --git a/app/Liquid/Filters/Numbers.php b/app/Liquid/Filters/Numbers.php new file mode 100644 index 0000000..53d1973 --- /dev/null +++ b/app/Liquid/Filters/Numbers.php @@ -0,0 +1,54 @@ +convert($markdown); + } catch (CommonMarkException $e) { + Log::error('Markdown conversion error: '.$e->getMessage()); + } + + return null; + } + + /** + * Strip HTML tags from a string + * + * @param string $html The HTML string to strip + * @return string The string without HTML tags + */ + public function strip_html(string $html): string + { + return strip_tags($html); + } +} diff --git a/app/Liquid/Filters/Uniqueness.php b/app/Liquid/Filters/Uniqueness.php new file mode 100644 index 0000000..89148c4 --- /dev/null +++ b/app/Liquid/Filters/Uniqueness.php @@ -0,0 +1,43 @@ +generateRandomString(); + } + + /** + * Generate a random string + * + * @param int $length The length of the random string + * @return string A random string + */ + private function generateRandomString(int $length = 4): string + { + $characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; + $randomString = ''; + + for ($i = 0; $i < $length; ++$i) { + $randomString .= $characters[rand(0, mb_strlen($characters) - 1)]; + } + + return $randomString; + } +} diff --git a/app/Models/Plugin.php b/app/Models/Plugin.php index 1d07251..6c17101 100644 --- a/app/Models/Plugin.php +++ b/app/Models/Plugin.php @@ -2,6 +2,11 @@ namespace App\Models; +use App\Liquid\Filters\Data; +use App\Liquid\Filters\Localization; +use App\Liquid\Filters\Numbers; +use App\Liquid\Filters\StringMarkup; +use App\Liquid\Filters\Uniqueness; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\App; @@ -95,6 +100,14 @@ class Plugin extends Model if ($this->markup_language === 'liquid') { $environment = App::make('liquid.environment'); + + // Register all custom filters + $environment->filterRegistry->register(Numbers::class); + $environment->filterRegistry->register(Data::class); + $environment->filterRegistry->register(StringMarkup::class); + $environment->filterRegistry->register(Uniqueness::class); + $environment->filterRegistry->register(Localization::class); + $template = $environment->parseString($this->render_markup); $context = $environment->newRenderContext(data: ['size' => $size, 'data' => $this->data_payload]); $renderedContent = $template->render($context); diff --git a/lang/de/custom_plugins.php b/lang/de/custom_plugins.php new file mode 100644 index 0000000..3fd8785 --- /dev/null +++ b/lang/de/custom_plugins.php @@ -0,0 +1,7 @@ + 'heute', + 'tomorrow' => 'morgen', + 'yesterday' => 'gestern', +]; diff --git a/tests/Unit/Liquid/Filters/DataTest.php b/tests/Unit/Liquid/Filters/DataTest.php new file mode 100644 index 0000000..ffb4088 --- /dev/null +++ b/tests/Unit/Liquid/Filters/DataTest.php @@ -0,0 +1,55 @@ + 'bar', 'baz' => 'qux']; + + expect($filter->json($array))->toBe('{"foo":"bar","baz":"qux"}'); +}); + +test('json filter converts objects to JSON', function () { + $filter = new Data(); + $object = new stdClass(); + $object->foo = 'bar'; + $object->baz = 'qux'; + + expect($filter->json($object))->toBe('{"foo":"bar","baz":"qux"}'); +}); + +test('json filter handles nested structures', function () { + $filter = new Data(); + $nested = [ + 'foo' => 'bar', + 'nested' => [ + 'baz' => 'qux', + 'items' => [1, 2, 3], + ], + ]; + + expect($filter->json($nested))->toBe('{"foo":"bar","nested":{"baz":"qux","items":[1,2,3]}}'); +}); + +test('json filter handles scalar values', function () { + $filter = new Data(); + + expect($filter->json('string'))->toBe('"string"'); + expect($filter->json(123))->toBe('123'); + expect($filter->json(true))->toBe('true'); + expect($filter->json(null))->toBe('null'); +}); + +test('json filter preserves unicode characters', function () { + $filter = new Data(); + $data = ['message' => 'Hello, 世界']; + + expect($filter->json($data))->toBe('{"message":"Hello, 世界"}'); +}); + +test('json filter does not escape slashes', function () { + $filter = new Data(); + $data = ['url' => 'https://example.com/path']; + + expect($filter->json($data))->toBe('{"url":"https://example.com/path"}'); +}); diff --git a/tests/Unit/Liquid/Filters/LocalizationTest.php b/tests/Unit/Liquid/Filters/LocalizationTest.php new file mode 100644 index 0000000..384c837 --- /dev/null +++ b/tests/Unit/Liquid/Filters/LocalizationTest.php @@ -0,0 +1,62 @@ +l_date($date); + + // Default format is 'Y-m-d', which should output something like '2025-01-11' + // The exact output might vary depending on the locale, but it should contain the year, month, and day + expect($result)->toContain('2025'); + expect($result)->toContain('01'); + expect($result)->toContain('11'); +}); + +test('l_date formats date with custom format', function () { + $filter = new Localization(); + $date = '2025-01-11'; + + $result = $filter->l_date($date, '%y %b'); + + // Format '%y %b' should output something like '25 Jan' + // The month name might vary depending on the locale + expect($result)->toContain('25'); + // We can't check for 'Jan' specifically as it might be localized +}); + +test('l_date handles DateTime objects', function () { + $filter = new Localization(); + $date = new DateTimeImmutable('2025-01-11'); + + $result = $filter->l_date($date, 'Y-m-d'); + + expect($result)->toContain('2025-01-11'); +}); + +test('l_word translates common words', function () { + $filter = new Localization(); + + expect($filter->l_word('today', 'de'))->toBe('heute'); +}); + +test('l_word returns original word if no translation exists', function () { + $filter = new Localization(); + + expect($filter->l_word('hello', 'es-ES'))->toBe('hello'); + expect($filter->l_word('world', 'ko'))->toBe('world'); +}); + +test('l_word is case-insensitive', function () { + $filter = new Localization(); + + expect($filter->l_word('TODAY', 'de'))->toBe('heute'); +}); + +test('l_word returns original word for unknown locales', function () { + $filter = new Localization(); + + expect($filter->l_word('today', 'unknown-locale'))->toBe('today'); +}); diff --git a/tests/Unit/Liquid/Filters/NumbersTest.php b/tests/Unit/Liquid/Filters/NumbersTest.php new file mode 100644 index 0000000..8ea73bf --- /dev/null +++ b/tests/Unit/Liquid/Filters/NumbersTest.php @@ -0,0 +1,47 @@ +number_with_delimiter(1234))->toBe('1,234'); + expect($filter->number_with_delimiter(1000000))->toBe('1,000,000'); + expect($filter->number_with_delimiter(0))->toBe('0'); +}); + +test('number_with_delimiter handles custom delimiters', function () { + $filter = new Numbers(); + + expect($filter->number_with_delimiter(1234, '.'))->toBe('1.234'); + expect($filter->number_with_delimiter(1000000, ' '))->toBe('1 000 000'); +}); + +test('number_with_delimiter handles decimal values with custom separators', function () { + $filter = new Numbers(); + + expect($filter->number_with_delimiter(1234.57, ' ', ','))->toBe('1 234,57'); + expect($filter->number_with_delimiter(1234.5, '.', ','))->toBe('1.234,50'); +}); + +test('number_to_currency formats numbers with dollar sign by default', function () { + $filter = new Numbers(); + + expect($filter->number_to_currency(1234))->toBe('$1,234'); + expect($filter->number_to_currency(1234.5))->toBe('$1,234.50'); + expect($filter->number_to_currency(0))->toBe('$0'); +}); + +test('number_to_currency handles custom currency symbols', function () { + $filter = new Numbers(); + + expect($filter->number_to_currency(1234, '£'))->toBe('£1,234'); + expect($filter->number_to_currency(152350.69, '€'))->toBe('€152,350.69'); +}); + +test('number_to_currency handles custom delimiters and separators', function () { + $filter = new Numbers(); + + expect($filter->number_to_currency(1234.57, '£', '.', ','))->toBe('1.234,57 £'); + expect($filter->number_to_currency(1234.57, '€', ',', '.'))->toBe('€1,234.57'); +}); diff --git a/tests/Unit/Liquid/Filters/StringMarkupTest.php b/tests/Unit/Liquid/Filters/StringMarkupTest.php new file mode 100644 index 0000000..4021a07 --- /dev/null +++ b/tests/Unit/Liquid/Filters/StringMarkupTest.php @@ -0,0 +1,90 @@ +pluralize('book', 1))->toBe('1 book'); + expect($filter->pluralize('person', 1))->toBe('1 person'); +}); + +test('pluralize returns plural form with count greater than 1', function () { + $filter = new StringMarkup(); + + expect($filter->pluralize('book', 2))->toBe('2 books'); + expect($filter->pluralize('person', 4))->toBe('4 people'); +}); + +test('pluralize handles irregular plurals correctly', function () { + $filter = new StringMarkup(); + + expect($filter->pluralize('child', 3))->toBe('3 children'); + expect($filter->pluralize('sheep', 5))->toBe('5 sheep'); +}); + +test('pluralize uses default count of 2 when not specified', function () { + $filter = new StringMarkup(); + + expect($filter->pluralize('book'))->toBe('2 books'); + expect($filter->pluralize('person'))->toBe('2 people'); +}); + +test('markdown_to_html converts basic markdown to HTML', function () { + $filter = new StringMarkup(); + $markdown = 'This is *italic* and **bold**.'; + + // The exact HTML output might vary depending on the Parsedown implementation + // So we'll check for the presence of HTML tags rather than the exact output + $result = $filter->markdown_to_html($markdown); + + expect($result)->toContain('italic'); + expect($result)->toContain('bold'); +}); + +test('markdown_to_html converts links correctly', function () { + $filter = new StringMarkup(); + $markdown = 'This is [a link](https://example.com).'; + + $result = $filter->markdown_to_html($markdown); + + expect($result)->toContain('a link'); +}); + +test('markdown_to_html handles fallback when Parsedown is not available', function () { + // Create a mock that simulates Parsedown not being available + $filter = new class extends StringMarkup + { + public function markdown_to_html(string $markdown): string + { + // Force the fallback path + return nl2br(htmlspecialchars($markdown)); + } + }; + + $markdown = 'This is *italic* and [a link](https://example.com).'; + $result = $filter->markdown_to_html($markdown); + + expect($result)->toBe('This is *italic* and [a link](https://example.com).'); +}); + +test('strip_html removes HTML tags', function () { + $filter = new StringMarkup(); + $html = '
This is bold and italic.
'; + + expect($filter->strip_html($html))->toBe('This is bold and italic.'); +}); + +test('strip_html preserves text content', function () { + $filter = new StringMarkup(); + $html = 'Paragraph with nested tags.