From 9714e4c3b73bd37fca70d8f5d6422d6bc9cf9592 Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Sat, 23 Jan 2021 13:21:02 +0300 Subject: [PATCH 01/47] Fixes overwriting blueprint columns #3095 --- src/Cms/Blueprint.php | 2 + .../BlueprintExtendAndUnsetTest.php | 77 +++++++++++++++---- 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/Cms/Blueprint.php b/src/Cms/Blueprint.php index db3ce551a5..a788e6fa60 100644 --- a/src/Cms/Blueprint.php +++ b/src/Cms/Blueprint.php @@ -375,7 +375,9 @@ public function name(): string protected function normalizeColumns(string $tabName, array $columns): array { foreach ($columns as $columnKey => $columnProps) { + // unset/remove column if its property is not array if (is_array($columnProps) === false) { + unset($columns[$columnKey]); continue; } diff --git a/tests/Cms/Blueprints/BlueprintExtendAndUnsetTest.php b/tests/Cms/Blueprints/BlueprintExtendAndUnsetTest.php index e9dcfe5a7f..5c7e08b168 100644 --- a/tests/Cms/Blueprints/BlueprintExtendAndUnsetTest.php +++ b/tests/Cms/Blueprints/BlueprintExtendAndUnsetTest.php @@ -29,6 +29,28 @@ public function setUp(): void ] ] ], + 'additional' => [ + 'columns' => [ + 'left' => [ + 'width' => '1/2', + 'fields' => [ + 'headline' => [ + 'label' => 'Headline', + 'type' => 'text' + ], + ] + ], + 'right' => [ + 'width' => '1/2', + 'fields' => [ + 'text' => [ + 'label' => 'Text', + 'type' => 'text' + ] + ] + ] + ] + ], 'seo' => [ 'fields' => [ 'seoTitle' => [ @@ -58,10 +80,10 @@ public function testExtendAndUnsetTab() ] ]); - $this->assertEquals('extended', $blueprint->title()); - $this->assertEquals(1, sizeof($blueprint->tabs())); - $this->assertEquals(false, is_array($blueprint->tab('seo'))); - $this->assertEquals(true, is_array($blueprint->tab('content'))); + $this->assertSame('extended', $blueprint->title()); + $this->assertCount(2, $blueprint->tabs()); + $this->assertIsArray($blueprint->tab('content')); + $this->assertIsNotArray($blueprint->tab('seo')); } public function testExtendAndUnsetSection() @@ -85,11 +107,11 @@ public function testExtendAndUnsetSection() $this->assertNull($e->getMessage(), 'Failed to getg sections.'); } - $this->assertEquals('extended', $blueprint->title()); - $this->assertEquals(true, is_array($sections)); - $this->assertEquals(1, sizeof($sections)); - $this->assertEquals(true, array_key_exists('pages', $sections)); - $this->assertEquals(false, array_key_exists('files', $sections)); + $this->assertSame('extended', $blueprint->title()); + $this->assertIsArray($sections); + $this->assertCount(1, $sections); + $this->assertArrayHasKey('pages', $sections); + $this->assertArrayNotHasKey('files', $sections); } public function testExtendAndUnsetFields() @@ -113,10 +135,37 @@ public function testExtendAndUnsetFields() $this->assertNull($e->getMessage(), 'Failed to get fields.'); } - $this->assertEquals('extended', $blueprint->title()); - $this->assertEquals(true, is_array($fields)); - $this->assertEquals(1, sizeof($fields)); - $this->assertEquals(true, array_key_exists('seoTitle', $fields)); - $this->assertEquals(false, array_key_exists('seoDescription', $fields)); + $this->assertSame('extended', $blueprint->title()); + $this->assertIsArray($fields); + $this->assertCount(1, $fields); + $this->assertArrayHasKey('seoTitle', $fields); + $this->assertArrayNotHasKey('seoDescription', $fields); + } + + public function testExtendAndUnsetColumns() + { + $blueprint = new Blueprint([ + 'title' => 'extended', + 'model' => 'page', + 'extends' => 'pages/base', + 'tabs' => [ + 'additional' => [ + 'columns' => [ + 'left' => [ + 'width' => '1/1' + ], + 'right' => false + ] + ] + ] + ]); + + $tab = $blueprint->tab('additional'); + + $this->assertIsArray($tab); + $this->assertCount(1, $tab['columns']); + $this->assertArrayHasKey('left', $tab['columns']); + $this->assertArrayNotHasKey('right', $tab['columns']); + $this->assertSame('1/1', $tab['columns']['left']['width']); } } From 622b7e1bdd605642a68edb233db81a66a4d3ced3 Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Sat, 23 Jan 2021 23:06:04 +0300 Subject: [PATCH 02/47] Fixes overwriting block tabs #3101 --- src/Cms/Fieldset.php | 6 ++++ tests/Cms/Fieldsets/FieldsetTest.php | 17 +++++++++++ tests/Cms/Fieldsets/FieldsetsTest.php | 42 +++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/src/Cms/Fieldset.php b/src/Cms/Fieldset.php index dfd0f22173..5cc3b632f9 100644 --- a/src/Cms/Fieldset.php +++ b/src/Cms/Fieldset.php @@ -105,6 +105,12 @@ protected function createTabs(array $params = []): array // normalize tabs props foreach ($tabs as $name => $tab) { + // unset/remove tab if its property is false + if ($tab === false) { + unset($tabs[$name]); + continue; + } + $tab = Blueprint::extend($tab); $tab['fields'] = $this->createFields($tab['fields'] ?? []); diff --git a/tests/Cms/Fieldsets/FieldsetTest.php b/tests/Cms/Fieldsets/FieldsetTest.php index 2d15abdb9a..fe16a62442 100644 --- a/tests/Cms/Fieldsets/FieldsetTest.php +++ b/tests/Cms/Fieldsets/FieldsetTest.php @@ -18,4 +18,21 @@ public function testConstruct() $this->assertNull($fieldset->icon()); $this->assertTrue($fieldset->translate()); } + + public function testTabsNormalize() + { + $fieldset = new Fieldset([ + 'type' => 'test', + 'fields' => [ + 'foo' => ['type' => 'text'], + 'bar' => ['type' => 'text'] + ] + ]); + + $this->assertIsArray($fieldset->tabs()); + $this->assertArrayHasKey('content', $fieldset->tabs()); + $this->assertArrayHasKey('fields', $fieldset->tabs()['content']); + $this->assertIsArray($fieldset->tabs()['content']['fields']); + $this->assertCount(2, $fieldset->tabs()['content']['fields']); + } } diff --git a/tests/Cms/Fieldsets/FieldsetsTest.php b/tests/Cms/Fieldsets/FieldsetsTest.php index 519909576a..863ae72013 100644 --- a/tests/Cms/Fieldsets/FieldsetsTest.php +++ b/tests/Cms/Fieldsets/FieldsetsTest.php @@ -37,4 +37,46 @@ public function testExtendGroups() $this->assertCount(1, $fieldsets->groups()); $this->assertSame(['heading', 'text'], $fieldsets->groups()['test']['sets']); } + + public function testExtendsTabsOverwrite() + { + new App([ + 'roots' => [ + 'index' => '/dev/null' + ], + 'blueprints' => [ + 'blocks/foo' => [ + 'name' => 'Text', + 'tabs' => [ + 'content' => [ + 'fields' => [ + 'text' => ['type' => 'textarea'], + ] + ], + 'seo' => [ + 'fields' => [ + 'metaTitle' => ['type' => 'text'], + 'metaDescription' => ['type' => 'text'] + ] + ] + ] + ] + ] + ]); + + $fieldsets = Fieldsets::factory([ + 'bar' => [ + 'extends' => 'blocks/foo', + 'tabs' => [ + 'seo' => false + ] + ] + ]); + + $fieldset = $fieldsets->first(); + + $this->assertIsArray($fieldset->tabs()); + $this->assertArrayHasKey('content', $fieldset->tabs()); + $this->assertArrayNotHasKey('seo', $fieldset->tabs()); + } } From 3e756884eae1a082bc86ebf445964c5cb467a1ec Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Thu, 14 Jan 2021 18:20:24 +0300 Subject: [PATCH 03/47] Adds image url label translation for image block #3087 --- config/blocks/image/image.yml | 2 +- i18n/translations/en.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/config/blocks/image/image.yml b/config/blocks/image/image.yml index c76a5c1288..feff6b0652 100644 --- a/config/blocks/image/image.yml +++ b/config/blocks/image/image.yml @@ -21,7 +21,7 @@ fields: when: location: kirby src: - label: Image URL + label: field.blocks.image.url type: url when: location: web diff --git a/i18n/translations/en.json b/i18n/translations/en.json index 8eff6c8d1e..91ab6fdebe 100644 --- a/i18n/translations/en.json +++ b/i18n/translations/en.json @@ -232,6 +232,7 @@ "field.blocks.image.name": "Image", "field.blocks.image.placeholder": "Select an image", "field.blocks.image.ratio": "Ratio", + "field.blocks.image.url": "Image URL", "field.blocks.list.name": "List", "field.blocks.markdown.name": "Markdown", "field.blocks.markdown.label": "Text", From 5c15131ecf9d050c28bbc723fee64783232c901b Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Sun, 24 Jan 2021 00:04:28 +0300 Subject: [PATCH 04/47] Fixes tabs on account page #3098 --- panel/src/config/routes.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/panel/src/config/routes.js b/panel/src/config/routes.js index 99f6d66a35..ff6c1cce0e 100644 --- a/panel/src/config/routes.js +++ b/panel/src/config/routes.js @@ -171,8 +171,9 @@ let routes = [ }, component: Vue.component("k-user-view"), beforeEnter: auth, - props: () => ({ - id: store.state.user.current ? store.state.user.current.id : false + props: route => ({ + id: store.state.user.current ? store.state.user.current.id : false, + tab: route.hash.slice(1) || "main" }) }, { From a3cd5c66bb97598c543973c239c0aa95acf3e656 Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Fri, 20 Nov 2020 14:00:22 +0300 Subject: [PATCH 05/47] Add similarity search feature for Str class getkirby/ideas#140 --- src/Toolkit/Str.php | 75 +++++++++++++++++++++++++++++++++++++++ tests/Toolkit/StrTest.php | 37 +++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/src/Toolkit/Str.php b/src/Toolkit/Str.php index 6ca647d390..10af45cb8f 100644 --- a/src/Toolkit/Str.php +++ b/src/Toolkit/Str.php @@ -803,6 +803,81 @@ public static function slug(string $string = null, string $separator = null, str return static::short($string, $maxlength, false); } + /** + * Calculate the similarity between two strings with multibyte support + * + * @author Antal Áron + * @copyright Copyright (c) 2017, Antal Áron + * @license https://github.com/antalaron/mb-similar-text/blob/master/LICENSE MIT License + * @param string $first + * @param string $second + * @param float|null $percent Optional variable passed by reference for the similarity in percent + * @param bool $caseSensitive If true, compare strings with case sensitive + * @return int Number of matching chars in both strings + */ + public static function similarity(string $first, string $second, float &$percent = null, bool $caseSensitive = false): int + { + if ($caseSensitive === false) { + $first = static::lower($first); + $second = static::lower($second); + } + + if (static::length($first) + static::length($second) === 0) { + $percent = 0.0; + + return 0; + } + + $pos1 = $pos2 = $max = 0; + $len1 = static::length($first); + $len2 = static::length($second); + + for ($p = 0; $p < $len1; ++$p) { + for ($q = 0; $q < $len2; ++$q) { + for ( + $l = 0; + ($p + $l < $len1) && ($q + $l < $len2) && + static::substr($first, $p + $l, 1) === static::substr($second, $q + $l, 1); + ++$l + ) { + // nothing to do + } + + if ($l > $max) { + $max = $l; + $pos1 = $p; + $pos2 = $q; + } + } + } + + $similarity = $max; + + if ($similarity) { + if ($pos1 && $pos2) { + $similarity += static::similarity( + static::substr($first, 0, $pos1), + static::substr($second, 0, $pos2), + $percent, + $caseSensitive + ); + } + + if (($pos1 + $max < $len1) && ($pos2 + $max < $len2)) { + $similarity += static::similarity( + static::substr($first, $pos1 + $max, $len1 - $pos1 - $max), + static::substr($second, $pos2 + $max, $len2 - $pos2 - $max), + $percent, + $caseSensitive + ); + } + } + + $percent = ($similarity * 200.0) / ($len1 + $len2); + + return $similarity; + } + /** * Convert a string to snake case. * diff --git a/tests/Toolkit/StrTest.php b/tests/Toolkit/StrTest.php index 91eeaab0e1..9ca58d7041 100644 --- a/tests/Toolkit/StrTest.php +++ b/tests/Toolkit/StrTest.php @@ -578,6 +578,43 @@ public function testStartsWith() $this->assertTrue(Str::startsWith($string, 'hellö', true)); } + public function testSimilarity() + { + $this->assertSame(0, Str::similarity('foo', 'bar', $percent)); + $this->assertSame(0.0, $percent); + + $this->assertSame(0, Str::similarity('foo', '', $percent)); + $this->assertSame(0.0, $percent); + + $this->assertSame(0, Str::similarity('', '', $percent)); + $this->assertSame(0.0, $percent); + + $this->assertSame(3, Str::similarity('foo', 'fooBar', $percent)); + $this->assertSame(66.66666666666667, $percent); + + $this->assertSame(3, Str::similarity('foo', 'foo', $percent)); + $this->assertSame(100.0, $percent); + + $this->assertSame(4, Str::similarity('tête', 'tête', $percent)); + $this->assertSame(100.0, $percent); + + $this->assertSame(4, Str::similarity('Tête', 'tête', $percent)); + $this->assertSame(100.0, $percent); + + $this->assertSame(5, Str::similarity('Kirby', 'KIRBY', $percent)); + $this->assertSame(100.0, $percent); + + // case sensitives + $this->assertSame(3, Str::similarity('Tête', 'tête', $percent, true)); + $this->assertSame(75.0, $percent); + + $this->assertSame(0, Str::similarity('foo', 'FOO', $percent, true)); + $this->assertSame(0.0, $percent); + + $this->assertSame(1, Str::similarity('Kirby', 'KIRBY', $percent, true)); + $this->assertSame(20.0, $percent); + } + public function testSubstr() { $string = 'äöü'; From 854c5a61e20dd78452c4e08c0412b83add247b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20St=C3=BCckler?= Date: Mon, 25 Jan 2021 15:54:38 +0100 Subject: [PATCH 06/47] fix: make argument for redirect nullable --- src/Http/Response.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Response.php b/src/Http/Response.php index 9279202c46..d5bc719d5c 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -254,7 +254,7 @@ public static function json($body = '', ?int $code = null, ?bool $pretty = null, * @param int $code * @return self */ - public static function redirect(string $location = '/', int $code = 302) + public static function redirect(string $location = '/', ?int $code = 302) { return new static([ 'code' => $code, From 6b3ebad80a1fd63180fd3b6cb6f849ac953653e2 Mon Sep 17 00:00:00 2001 From: Nico Hoffmann Date: Tue, 5 Jan 2021 20:19:26 +0100 Subject: [PATCH 07/47] Fix some return types --- src/Cms/PageActions.php | 2 +- src/Image/Location.php | 2 +- src/Toolkit/F.php | 2 +- src/Toolkit/Iterator.php | 2 +- src/Toolkit/Pagination.php | 2 +- src/Toolkit/Str.php | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Cms/PageActions.php b/src/Cms/PageActions.php index 24b7508cdd..05a60a2f4e 100644 --- a/src/Cms/PageActions.php +++ b/src/Cms/PageActions.php @@ -533,7 +533,7 @@ public function createNum(int $num = null): int $lang = $this->kirby()->defaultLanguage() ?? null; $field = $this->content($lang)->get('date'); $date = $field->isEmpty() ? 'now' : $field; - return date($format, strtotime($date)); + return (int)date($format, strtotime($date)); break; case 'default': diff --git a/src/Image/Location.php b/src/Image/Location.php index d38c79a7c5..8c864b682f 100644 --- a/src/Image/Location.php +++ b/src/Image/Location.php @@ -95,7 +95,7 @@ protected function num(string $part): float $parts = explode('/', $part); if (count($parts) === 1) { - return $parts[0]; + return (float)$parts[0]; } return (float)($parts[0]) / (float)($parts[1]); diff --git a/src/Toolkit/F.php b/src/Toolkit/F.php index 098febec6b..a3a155e508 100644 --- a/src/Toolkit/F.php +++ b/src/Toolkit/F.php @@ -525,7 +525,7 @@ public static function name(string $name): string * Converts an integer size into a human readable format * * @param mixed $size The file size or a file path - * @return string|int + * @return string */ public static function niceSize($size): string { diff --git a/src/Toolkit/Iterator.php b/src/Toolkit/Iterator.php index 81fb2aece4..d30ff99bb0 100644 --- a/src/Toolkit/Iterator.php +++ b/src/Toolkit/Iterator.php @@ -129,7 +129,7 @@ public function count(): int * Tries to find the index number for the given element * * @param mixed $needle the element to search for - * @return string|false the name of the key or false + * @return int|false the index (int) of the element or false */ public function indexOf($needle) { diff --git a/src/Toolkit/Pagination.php b/src/Toolkit/Pagination.php index 7243bb48b5..33a239c28f 100644 --- a/src/Toolkit/Pagination.php +++ b/src/Toolkit/Pagination.php @@ -196,7 +196,7 @@ public function pages(): int return 0; } - return ceil($this->total() / $this->limit()); + return (int)ceil($this->total() / $this->limit()); } /** diff --git a/src/Toolkit/Str.php b/src/Toolkit/Str.php index 10af45cb8f..2874c73601 100644 --- a/src/Toolkit/Str.php +++ b/src/Toolkit/Str.php @@ -410,7 +410,7 @@ public static function from(string $string, string $needle, bool $caseInsensitiv */ public static function isURL(string $string = null): bool { - return filter_var($string, FILTER_VALIDATE_URL); + return filter_var($string, FILTER_VALIDATE_URL) !== false; } /** From e4661e230cbc40a8870d29b7497b4715fd1ef4d6 Mon Sep 17 00:00:00 2001 From: Nico Hoffmann Date: Tue, 5 Jan 2021 20:25:35 +0100 Subject: [PATCH 08/47] Fix Page::$template @var type: object not string --- src/Cms/Page.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cms/Page.php b/src/Cms/Page.php index c4bc90121b..d2c897f6c4 100644 --- a/src/Cms/Page.php +++ b/src/Cms/Page.php @@ -139,7 +139,7 @@ class Page extends ModelWithContent /** * The intended page template * - * @var string + * @var \Kirby\Cms\Template */ protected $template; From d3039eb82336ebed3e913029a730f5916f07f57e Mon Sep 17 00:00:00 2001 From: Nico Hoffmann Date: Tue, 5 Jan 2021 20:25:51 +0100 Subject: [PATCH 09/47] Fix calling implode() with `null` --- src/Toolkit/A.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Toolkit/A.php b/src/Toolkit/A.php index ea30f7cdb2..a57dcd2d87 100644 --- a/src/Toolkit/A.php +++ b/src/Toolkit/A.php @@ -597,7 +597,7 @@ public static function sort(array $array, string $field, string $direction = 'de */ public static function isAssociative(array $array): bool { - return ctype_digit(implode(null, array_keys($array))) === false; + return ctype_digit(implode('', array_keys($array))) === false; } /** From 46f145b80ab018b4b1a41aa3364217e56e310424 Mon Sep 17 00:00:00 2001 From: Nico Hoffmann Date: Mon, 25 Jan 2021 20:31:46 +0100 Subject: [PATCH 10/47] PageActions::createNum(): add TODO for 3.6.0 --- src/Cms/PageActions.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Cms/PageActions.php b/src/Cms/PageActions.php index 05a60a2f4e..b87e1fe4e5 100644 --- a/src/Cms/PageActions.php +++ b/src/Cms/PageActions.php @@ -533,6 +533,8 @@ public function createNum(int $num = null): int $lang = $this->kirby()->defaultLanguage() ?? null; $field = $this->content($lang)->get('date'); $date = $field->isEmpty() ? 'now' : $field; + // TODO: in 3.6.0 throw an error if date() doesn't + // return a number, see https://github.com/getkirby/kirby/pull/3061#discussion_r552783943 return (int)date($format, strtotime($date)); break; case 'default': From 37c68321189a5f3e0ce496afc8e14a61cb16cb68 Mon Sep 17 00:00:00 2001 From: Lukas Bestle Date: Wed, 27 Jan 2021 10:57:58 +0100 Subject: [PATCH 11/47] CI: Use PHP 7.4 for analysis for now This partly reverts commit 6a363829b111a8d5fc65e5f36dadd4101f9d58e0. --- .github/workflows/ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 071776dd29..ff69f84c78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -268,7 +268,8 @@ jobs: runs-on: ubuntu-latest env: - php: "8.0" + # TODO: update to PHP 8.0 when PHP-CS-Fixer fully supports it + php: "7.4" steps: - name: Checkout Kirby @@ -321,8 +322,6 @@ jobs: - name: Check for PHP coding style violations if: always() && steps.finishPrepare.outcome == 'success' - env: - PHP_CS_FIXER_IGNORE_ENV: 1 # Use the --dry-run flag in push builds to get a failed CI status run: > php-cs-fixer fix --diff --diff-format=udiff From 851761b1b481ca795f476dfc09b48409f6254802 Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Tue, 26 Jan 2021 23:11:41 +0300 Subject: [PATCH 12/47] Injects custom language translations #3064 --- src/Cms/AppTranslations.php | 19 ++++++++++++++++++- tests/Cms/App/AppTranslationsTest.php | 25 ++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/Cms/AppTranslations.php b/src/Cms/AppTranslations.php index 59619534d9..d3850494a9 100644 --- a/src/Cms/AppTranslations.php +++ b/src/Cms/AppTranslations.php @@ -188,6 +188,11 @@ public function translation(?string $locale = null) // get injected translation data from plugins etc. $inject = $this->extensions['translations'][$locale] ?? []; + // inject current language translations + if ($language = $this->language($locale)) { + $inject = array_merge($inject, $language->translations()); + } + // load from disk instead return Translation::load($locale, $this->root('i18n:translations') . '/' . $locale . '.json', $inject); } @@ -203,6 +208,18 @@ public function translations() return $this->translations; } - return Translations::load($this->root('i18n:translations'), $this->extensions['translations'] ?? []); + $translations = $this->extensions['translations'] ?? []; + + // inject current language translations + if ($language = $this->language()) { + $translations = array_replace_recursive( + $translations, + [$language->code() => $language->translations()] + ); + } + + $this->translations = Translations::load($this->root('i18n:translations'), $translations); + + return $this->translations; } } diff --git a/tests/Cms/App/AppTranslationsTest.php b/tests/Cms/App/AppTranslationsTest.php index bf12671969..dcabee6a06 100644 --- a/tests/Cms/App/AppTranslationsTest.php +++ b/tests/Cms/App/AppTranslationsTest.php @@ -118,13 +118,36 @@ public function testTranslationFromCurrentLanguage() ], 'translations' => [ 'de' => [ + 'hello' => 'Hallo' ] ] ]); I18n::$locale = 'de'; - $this->assertEquals('Knopf', t('button')); + // translation + $translation = $app->translation('de'); + + $this->assertInstanceOf('Kirby\Cms\Translation', $translation); + $this->assertIsArray($translation->data()); + $this->assertArrayHasKey('button', $translation->data()); + $this->assertArrayHasKey('hello', $translation->data()); + $this->assertSame('Knopf', $translation->data()['button']); + $this->assertSame('Hallo', $translation->data()['hello']); + $this->assertSame('Knopf', t('button')); + $this->assertSame('Hallo', t('hello')); + + // translations + $translation = $app->translations()->find('de'); + + $this->assertInstanceOf('Kirby\Cms\Translation', $translation); + $this->assertIsArray($translation->data()); + $this->assertArrayHasKey('button', $translation->data()); + $this->assertArrayHasKey('hello', $translation->data()); + $this->assertSame('Knopf', $translation->data()['button']); + $this->assertSame('Hallo', $translation->data()['hello']); + $this->assertSame('Knopf', t('button')); + $this->assertSame('Hallo', t('hello')); } public function testTranslationFallback() From f7a6546c0eec1536fd50fddc79cf7655f39a0bcf Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Tue, 26 Jan 2021 19:26:15 +0300 Subject: [PATCH 13/47] Supports list items in info fields #2946 --- panel/src/components/Misc/Text.vue | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/panel/src/components/Misc/Text.vue b/panel/src/components/Misc/Text.vue index ebde8c8b08..c179b2dcbb 100644 --- a/panel/src/components/Misc/Text.vue +++ b/panel/src/components/Misc/Text.vue @@ -23,7 +23,17 @@ export default { .k-text { line-height: 1.5em; } -.k-text p { +.k-text ol, +.k-text ul { + margin-left: 1rem; + list-style-position: inside; +} +.k-text li { + list-style: inherit; +} +.k-text p, +.k-text > ol, +.k-text > ul { margin-bottom: 1.5em; } .k-text a { From 57cceca53a913f1555387420d4fc143775c1f10e Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Tue, 26 Jan 2021 17:28:12 +0300 Subject: [PATCH 14/47] Separates caches for draft mode #3082 --- src/Cms/Pages.php | 34 ++++++++++++----- tests/Cms/Pages/PagesTest.php | 69 +++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 10 deletions(-) diff --git a/src/Cms/Pages.php b/src/Cms/Pages.php index c4e9c133cb..f996f23688 100644 --- a/src/Cms/Pages.php +++ b/src/Cms/Pages.php @@ -23,12 +23,19 @@ class Pages extends Collection { /** - * Cache for the index + * Cache for the index only listed and unlisted pages * * @var \Kirby\Cms\Pages|null */ protected $index = null; + /** + * Cache for the index all statuses also including drafts + * + * @var \Kirby\Cms\Pages|null + */ + protected $indexWithDrafts = null; + /** * All registered pages methods * @@ -331,24 +338,31 @@ public function images() */ public function index(bool $drafts = false) { - if (is_a($this->index, 'Kirby\Cms\Pages') === true) { - return $this->index; + // get object property by cache mode + $index = $drafts === true ? $this->indexWithDrafts : $this->index; + + if (is_a($index, 'Kirby\Cms\Pages') === true) { + return $index; } - $this->index = new Pages([], $this->parent); + $index = new Pages([], $this->parent); foreach ($this->data as $pageKey => $page) { - $this->index->data[$pageKey] = $page; - $index = $page->index($drafts); + $index->data[$pageKey] = $page; + $pageIndex = $page->index($drafts); - if ($index) { - foreach ($index as $childKey => $child) { - $this->index->data[$childKey] = $child; + if ($pageIndex) { + foreach ($pageIndex as $childKey => $child) { + $index->data[$childKey] = $child; } } } - return $this->index; + if ($drafts === true) { + return $this->indexWithDrafts = $index; + } + + return $this->index = $index; } /** diff --git a/tests/Cms/Pages/PagesTest.php b/tests/Cms/Pages/PagesTest.php index 82ab487d92..34e4bb4a1a 100644 --- a/tests/Cms/Pages/PagesTest.php +++ b/tests/Cms/Pages/PagesTest.php @@ -432,6 +432,75 @@ public function testIndexWithDrafts() $this->assertEquals($expected, $pages->index(true)->keys()); } + public function testIndexCacheMode() + { + $pages = Pages::factory([ + [ + 'slug' => 'a', + 'children' => [ + [ + 'slug' => 'aa', + 'children' => [ + ['slug' => 'aaa'], + ['slug' => 'aab'], + ] + ], + [ + 'slug' => 'ab' + ] + ], + 'drafts' => [ + [ + 'slug' => 'ac' + ] + ] + ], + [ + 'slug' => 'b', + 'children' => [ + ['slug' => 'ba'], + ['slug' => 'bb'] + ], + 'drafts' => [ + [ + 'slug' => 'bc' + ] + ] + ] + ]); + + $expectedIndex = [ + 'a', + 'a/aa', + 'a/aa/aaa', + 'a/aa/aab', + 'a/ab', + 'b', + 'b/ba', + 'b/bb', + ]; + + $expectedIndexWithDrafts = [ + 'a', + 'a/aa', + 'a/aa/aaa', + 'a/aa/aab', + 'a/ab', + 'a/ac', + 'b', + 'b/ba', + 'b/bb', + 'b/bc', + ]; + + // first run index method to cache index and with drafts + $pages->index(); + $pages->index(true); + + $this->assertSame($expectedIndex, $pages->index()->keys()); + $this->assertSame($expectedIndexWithDrafts, $pages->index(true)->keys()); + } + public function testNotTemplate() { $pages = Pages::factory([ From b15f2ee924999c050599b8d20b9f18a833dc35f5 Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Tue, 22 Dec 2020 12:19:04 +0300 Subject: [PATCH 15/47] Remove margin for icons #2943 --- panel/src/components/Misc/Icon.vue | 2 -- 1 file changed, 2 deletions(-) diff --git a/panel/src/components/Misc/Icon.vue b/panel/src/components/Misc/Icon.vue index ffb346d811..8c5fe5cc52 100644 --- a/panel/src/components/Misc/Icon.vue +++ b/panel/src/components/Misc/Icon.vue @@ -73,14 +73,12 @@ export default { line-height: 1; font-style: normal; font-size: 1rem; - margin-left: -.3rem; } /* fix emoji alignment on high-res screens */ @media only screen and (-webkit-min-device-pixel-ratio: 2), not all, not all, not all, only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) { .k-icon-emoji { font-size: 1.25rem; - margin-left: -.15rem; } } From f9149ff2e9ece36cb637a9d678e7087ed6bf22f2 Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Wed, 27 Jan 2021 21:11:19 +0300 Subject: [PATCH 16/47] Translatable sections info and text props #3109 --- config/sections/files.php | 10 ++--- config/sections/pages.php | 10 ++--- tests/Cms/Sections/FilesSectionTest.php | 48 +++++++++++++++++++++++ tests/Cms/Sections/PagesSectionTest.php | 52 +++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 10 deletions(-) diff --git a/config/sections/files.php b/config/sections/files.php index 52fe133191..8ea3c5efc7 100644 --- a/config/sections/files.php +++ b/config/sections/files.php @@ -30,8 +30,8 @@ /** * Optional info text setup. Info text is shown on the right (lists) or below (cards) the filename. */ - 'info' => function (string $info = null) { - return $info; + 'info' => function ($info = null) { + return I18n::translate($info, $info); }, /** * The size option controls the size of cards. By default cards are auto-sized and the cards grid will always fill the full width. With a size you can disable auto-sizing. Available sizes: `tiny`, `small`, `medium`, `large`, `huge` @@ -60,8 +60,8 @@ /** * Setup for the main text in the list or cards. By default this will display the filename. */ - 'text' => function (string $text = '{{ file.filename }}') { - return $text; + 'text' => function ($text = '{{ file.filename }}') { + return I18n::translate($text, $text); } ], 'computed' => [ @@ -123,7 +123,7 @@ 'id' => $file->id(), 'icon' => $file->panelIcon($image), 'image' => $image, - 'info' => $file->toString($this->info ?? false), + 'info' => $file->toString($this->info), 'link' => $file->panelUrl(true), 'mime' => $file->mime(), 'parent' => $file->parent()->panelPath(), diff --git a/config/sections/pages.php b/config/sections/pages.php index d924e06ccb..ead872e2a4 100644 --- a/config/sections/pages.php +++ b/config/sections/pages.php @@ -38,8 +38,8 @@ /** * Optional info text setup. Info text is shown on the right (lists) or below (cards) the page title. */ - 'info' => function (string $info = null) { - return $info; + 'info' => function ($info = null) { + return I18n::translate($info, $info); }, /** * The size option controls the size of cards. By default cards are auto-sized and the cards grid will always fill the full width. With a size you can disable auto-sizing. Available sizes: `tiny`, `small`, `medium`, `large`, `huge` @@ -82,8 +82,8 @@ /** * Setup for the main text in the list or cards. By default this will display the page title. */ - 'text' => function (string $text = '{{ page.title }}') { - return $text; + 'text' => function ($text = '{{ page.title }}') { + return I18n::translate($text, $text); } ], 'computed' => [ @@ -157,7 +157,7 @@ 'id' => $item->id(), 'dragText' => $item->dragText(), 'text' => $item->toString($this->text), - 'info' => $item->toString($this->info ?? false), + 'info' => $item->toString($this->info), 'parent' => $item->parentId(), 'icon' => $item->panelIcon($image), 'image' => $image, diff --git a/tests/Cms/Sections/FilesSectionTest.php b/tests/Cms/Sections/FilesSectionTest.php index f6800eb57f..5f35304672 100644 --- a/tests/Cms/Sections/FilesSectionTest.php +++ b/tests/Cms/Sections/FilesSectionTest.php @@ -373,4 +373,52 @@ public function testFlip() $this->assertEquals('b.jpg', $section->data()[1]['filename']); $this->assertEquals('a.jpg', $section->data()[2]['filename']); } + + public function testTranslatedInfo() + { + $model = new Page([ + 'slug' => 'test', + 'files' => [ + ['filename' => 'a.jpg'], + ['filename' => 'b.jpg'] + ] + ]); + + $section = new Section('files', [ + 'name' => 'test', + 'model' => $model, + 'info' => [ + 'en' => 'en: {{ file.page.title }}', + 'de' => 'de: {{ file.page.title }}' + ] + ]); + + $this->assertSame('en: {{ file.page.title }}', $section->info()); + $this->assertSame('en: test', $section->data()[0]['info']); + $this->assertSame('en: test', $section->data()[1]['info']); + } + + public function testTranslatedText() + { + $model = new Page([ + 'slug' => 'test', + 'files' => [ + ['filename' => 'a.jpg'], + ['filename' => 'b.jpg'] + ] + ]); + + $section = new Section('files', [ + 'name' => 'test', + 'model' => $model, + 'text' => [ + 'en' => 'en: {{ file.filename }}', + 'de' => 'de: {{ file.filename }}' + ] + ]); + + $this->assertSame('en: {{ file.filename }}', $section->text()); + $this->assertSame('en: a.jpg', $section->data()[0]['text']); + $this->assertSame('en: b.jpg', $section->data()[1]['text']); + } } diff --git a/tests/Cms/Sections/PagesSectionTest.php b/tests/Cms/Sections/PagesSectionTest.php index f84b8cb648..cef9c0ec1c 100644 --- a/tests/Cms/Sections/PagesSectionTest.php +++ b/tests/Cms/Sections/PagesSectionTest.php @@ -441,4 +441,56 @@ public function testHelp() $this->assertEquals('

Information

', $section->help()); } + + public function testTranslatedInfo() + { + $page = new Page([ + 'slug' => 'test', + 'children' => [ + ['slug' => 'subpage-1', 'content' => ['title' => 'C']], + ['slug' => 'subpage-2', 'content' => ['title' => 'A']], + ['slug' => 'subpage-3', 'content' => ['title' => 'B']] + ] + ]); + + $section = new Section('pages', [ + 'name' => 'test', + 'model' => $page, + 'info' => [ + 'en' => 'en: {{ page.slug }}', + 'de' => 'de: {{ page.slug }}' + ] + ]); + + $this->assertSame('en: {{ page.slug }}', $section->info()); + $this->assertSame('en: subpage-1', $section->data()[0]['info']); + $this->assertSame('en: subpage-2', $section->data()[1]['info']); + $this->assertSame('en: subpage-3', $section->data()[2]['info']); + } + + public function testTranslatedText() + { + $page = new Page([ + 'slug' => 'test', + 'children' => [ + ['slug' => 'subpage-1', 'content' => ['title' => 'C']], + ['slug' => 'subpage-2', 'content' => ['title' => 'A']], + ['slug' => 'subpage-3', 'content' => ['title' => 'B']] + ] + ]); + + $section = new Section('pages', [ + 'name' => 'test', + 'model' => $page, + 'text' => [ + 'en' => 'en: {{ page.title }}', + 'de' => 'de: {{ page.title }}' + ] + ]); + + $this->assertSame('en: {{ page.title }}', $section->text()); + $this->assertSame('en: C', $section->data()[0]['text']); + $this->assertSame('en: A', $section->data()[1]['text']); + $this->assertSame('en: B', $section->data()[2]['text']); + } } From 7fdd492b1965566d1a56edfae74e0a1e0d94868a Mon Sep 17 00:00:00 2001 From: Neil Daniels Date: Wed, 27 Jan 2021 17:26:56 -0800 Subject: [PATCH 17/47] Avoid calling global functions in filter(). Fixes #3117 --- src/Toolkit/Collection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Toolkit/Collection.php b/src/Toolkit/Collection.php index b7ad5fcb56..31d115857b 100644 --- a/src/Toolkit/Collection.php +++ b/src/Toolkit/Collection.php @@ -252,7 +252,7 @@ public function filter($field, ...$args) $split = $args[1] ?? false; // filter by custom filter function - if (is_callable($field) === true) { + if (is_string($field) === false && is_callable($field) === true) { $collection = clone $this; $collection->data = array_filter($this->data, $field); From 46e049a8245edd93cee76ccfb15ac409a6853eba Mon Sep 17 00:00:00 2001 From: Bastian Allgeier Date: Thu, 28 Jan 2021 13:47:34 +0100 Subject: [PATCH 18/47] Remove list-style-position --- panel/src/components/Misc/Text.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/panel/src/components/Misc/Text.vue b/panel/src/components/Misc/Text.vue index c179b2dcbb..392b970ac5 100644 --- a/panel/src/components/Misc/Text.vue +++ b/panel/src/components/Misc/Text.vue @@ -26,7 +26,6 @@ export default { .k-text ol, .k-text ul { margin-left: 1rem; - list-style-position: inside; } .k-text li { list-style: inherit; From 26b305ea6f79deb47bd5600eab9703a3d7f64ed2 Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Thu, 28 Jan 2021 16:21:36 +0300 Subject: [PATCH 19/47] Use `fetch-depth: 2` on actions/checkout@v2 --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff69f84c78..fe6a12fa68 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,8 @@ jobs: steps: - name: Checkout Kirby uses: actions/checkout@v2 + with: + fetch-depth: 2 - name: Install memcached uses: niden/actions-memcached@v7 From 10d8a5172d5eae5ac7a9f2fc608601015fc3b176 Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Thu, 28 Jan 2021 19:26:34 +0300 Subject: [PATCH 20/47] Enables `fail_ci_if_error` argument for codecov --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe6a12fa68..582b127b89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,7 @@ jobs: - name: Upload coverage results to Codecov uses: codecov/codecov-action@v1 with: + fail_ci_if_error: true file: ${{ github.workspace }}/clover.xml flags: backend env_vars: PHP From 817d01ee779f13d5818eb1e672da3b73f89615d1 Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Thu, 28 Jan 2021 23:46:25 +0300 Subject: [PATCH 21/47] Changes files field button to `Select` when uploads disabled https://kirby.nolt.io/167 --- panel/src/components/Forms/Field/FilesField.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/panel/src/components/Forms/Field/FilesField.vue b/panel/src/components/Forms/Field/FilesField.vue index 87c2c59444..368e315df1 100644 --- a/panel/src/components/Forms/Field/FilesField.vue +++ b/panel/src/components/Forms/Field/FilesField.vue @@ -23,8 +23,8 @@ From 9f4430bd3e65089a3f3f1f93f73a09febe496f96 Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Fri, 29 Jan 2021 15:30:27 +0300 Subject: [PATCH 22/47] Injects all languages translations instead current language #3064 --- src/Cms/AppTranslations.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Cms/AppTranslations.php b/src/Cms/AppTranslations.php index d3850494a9..9413f036a0 100644 --- a/src/Cms/AppTranslations.php +++ b/src/Cms/AppTranslations.php @@ -210,12 +210,17 @@ public function translations() $translations = $this->extensions['translations'] ?? []; - // inject current language translations - if ($language = $this->language()) { - $translations = array_replace_recursive( - $translations, - [$language->code() => $language->translations()] - ); + // injects languages translations + if ($languages = $this->languages()) { + foreach ($languages as $language) { + // merges language translations with extension translations + if ($language->translations()) { + $translations[$language->code()] = array_merge( + $translations[$language->code()], + $language->translations() + ); + } + } } $this->translations = Translations::load($this->root('i18n:translations'), $translations); From bbf773ff307ca5aa34c0a613f696caa1606e2d75 Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Fri, 29 Jan 2021 19:34:14 +0300 Subject: [PATCH 23/47] Remove opacity from disabled fields #2807 --- panel/src/components/Blocks/Block.vue | 3 +++ panel/src/components/Blocks/Blocks.vue | 3 +++ panel/src/components/Forms/Field.vue | 1 - panel/src/components/Forms/Field/ListField.vue | 1 + .../components/Forms/Field/StructureField.vue | 16 ++++++++++++++++ panel/src/components/Forms/Field/WriterField.vue | 1 + panel/src/components/Layout/ListItem.vue | 3 +++ 7 files changed, 27 insertions(+), 1 deletion(-) diff --git a/panel/src/components/Blocks/Block.vue b/panel/src/components/Blocks/Block.vue index 43ea660853..407db77cb2 100644 --- a/panel/src/components/Blocks/Block.vue +++ b/panel/src/components/Blocks/Block.vue @@ -260,4 +260,7 @@ export default { vertical-align: middle; display: inline-grid; } +[data-disabled] .k-block-container { + background: $color-background; +} diff --git a/panel/src/components/Blocks/Blocks.vue b/panel/src/components/Blocks/Blocks.vue index eef44d1135..38574fb0ed 100644 --- a/panel/src/components/Blocks/Blocks.vue +++ b/panel/src/components/Blocks/Blocks.vue @@ -428,6 +428,9 @@ export default { box-shadow: $shadow; border-radius: $rounded; } +[data-disabled] .k-blocks { + background: $color-background; +} .k-blocks[data-alt] .k-block-container > * { pointer-events: none; } diff --git a/panel/src/components/Forms/Field.vue b/panel/src/components/Forms/Field.vue index e7e8c35faa..5ba23fb26f 100644 --- a/panel/src/components/Forms/Field.vue +++ b/panel/src/components/Forms/Field.vue @@ -102,7 +102,6 @@ export default { } .k-field[data-disabled] { cursor: not-allowed; - opacity: .4; } .k-field[data-disabled] * { pointer-events: none; diff --git a/panel/src/components/Forms/Field/ListField.vue b/panel/src/components/Forms/Field/ListField.vue index b4f19e75c6..bcec7996d6 100644 --- a/panel/src/components/Forms/Field/ListField.vue +++ b/panel/src/components/Forms/Field/ListField.vue @@ -8,6 +8,7 @@ Date: Fri, 29 Jan 2021 23:33:00 +0300 Subject: [PATCH 24/47] Removes `

` tags from value #3086 --- panel/src/components/Forms/Input/ListInput.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/panel/src/components/Forms/Input/ListInput.vue b/panel/src/components/Forms/Input/ListInput.vue index c3137019d0..18a024a57f 100644 --- a/panel/src/components/Forms/Input/ListInput.vue +++ b/panel/src/components/Forms/Input/ListInput.vue @@ -47,6 +47,9 @@ export default { return; } + // removes

tags from value + html = html.replace(/(

|<\/p>)/gi, ""); + this.$emit("input", html); } } From 5d86029f12b3706c64a94a95eed0f6d9aefc7b17 Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Sat, 30 Jan 2021 01:03:12 +0300 Subject: [PATCH 25/47] Improve field visuals on disabled #2807 --- panel/src/components/Forms/Input.vue | 4 +++ .../components/Forms/Input/CheckboxInput.vue | 4 +++ .../src/components/Forms/Input/ListInput.vue | 3 +- .../src/components/Forms/Input/RadioInput.vue | 4 +++ .../src/components/Forms/Input/RangeInput.vue | 32 +++++++++++++++++++ panel/src/components/Misc/Icon.vue | 9 ++++++ panel/src/components/Navigation/Tag.vue | 3 ++ 7 files changed, 57 insertions(+), 2 deletions(-) diff --git a/panel/src/components/Forms/Input.vue b/panel/src/components/Forms/Input.vue index 2e03b42c9f..ed10e3dbc9 100644 --- a/panel/src/components/Forms/Input.vue +++ b/panel/src/components/Forms/Input.vue @@ -134,6 +134,10 @@ export default { pointer-events: none; } +[data-disabled] .k-input-icon { + color: $color-gray-600; +} + .k-input[data-theme="field"] { line-height: 1; border: $field-input-border; diff --git a/panel/src/components/Forms/Input/CheckboxInput.vue b/panel/src/components/Forms/Input/CheckboxInput.vue index 2586ed39c3..2d65933863 100644 --- a/panel/src/components/Forms/Input/CheckboxInput.vue +++ b/panel/src/components/Forms/Input/CheckboxInput.vue @@ -114,6 +114,10 @@ export default { border-color: $color-gray-900; background: $color-gray-900; } +[data-disabled] .k-checkbox-input-native:checked + .k-checkbox-input-icon { + border-color: $color-gray-600; + background: $color-gray-600; +} .k-checkbox-input-native:checked + .k-checkbox-input-icon svg { display: block; } diff --git a/panel/src/components/Forms/Input/ListInput.vue b/panel/src/components/Forms/Input/ListInput.vue index c3137019d0..7c9bac687b 100644 --- a/panel/src/components/Forms/Input/ListInput.vue +++ b/panel/src/components/Forms/Input/ListInput.vue @@ -1,10 +1,9 @@ - +