From c2a3421d521906c260e201f023854abf61303ea9 Mon Sep 17 00:00:00 2001 From: N0str Date: Thu, 4 Oct 2018 15:54:58 +0300 Subject: [PATCH 01/32] Rename CodexEditor to EditorJS --- CodexEditor/CodexEditorException.php | 16 --------- {CodexEditor => EditorJS}/BlockHandler.php | 30 ++++++++-------- {CodexEditor => EditorJS}/ConfigLoader.php | 14 ++++---- .../CodexEditor.php => EditorJS/EditorJS.php | 26 +++++++------- EditorJS/EditorJSException.php | 16 +++++++++ README.md | 18 +++++----- composer.json | 6 ++-- tests/BlockHandlerTest.php | 14 ++++---- tests/GeneralTest.php | 36 +++++++++---------- tests/TypeTest.php | 22 ++++++------ 10 files changed, 99 insertions(+), 99 deletions(-) delete mode 100644 CodexEditor/CodexEditorException.php rename {CodexEditor => EditorJS}/BlockHandler.php (83%) rename {CodexEditor => EditorJS}/ConfigLoader.php (73%) rename CodexEditor/CodexEditor.php => EditorJS/EditorJS.php (82%) create mode 100644 EditorJS/EditorJSException.php diff --git a/CodexEditor/CodexEditorException.php b/CodexEditor/CodexEditorException.php deleted file mode 100644 index 5049131..0000000 --- a/CodexEditor/CodexEditorException.php +++ /dev/null @@ -1,16 +0,0 @@ -rules->tools)) { - throw new CodexEditorException("Tool `$blockType` not found in the configuration"); + throw new EditorJSException("Tool `$blockType` not found in the configuration"); } $rule = $this->rules->tools[$blockType]; @@ -68,7 +68,7 @@ public function validateBlock($blockType, $blockData) * @param string $blockType * @param array $blockData * - * @throws CodexEditorException + * @throws EditorJSException * * @return array|bool */ @@ -88,7 +88,7 @@ public function sanitizeBlock($blockType, $blockData) * @param array $rules * @param array $blockData * - * @throws CodexEditorException + * @throws EditorJSException * * @return bool */ @@ -100,7 +100,7 @@ private function validate($rules, $blockData) foreach ($rules as $key => $value) { if (($key != BlockHandler::DEFAULT_ARRAY_KEY) && (isset($value['required']) ? $value['required'] : true)) { if (!isset($blockData[$key])) { - throw new CodexEditorException("Not found required param `$key`"); + throw new EditorJSException("Not found required param `$key`"); } } } @@ -110,7 +110,7 @@ private function validate($rules, $blockData) */ foreach ($blockData as $key => $value) { if (!is_integer($key) && !isset($rules[$key])) { - throw new CodexEditorException("Found extra param `$key`"); + throw new EditorJSException("Found extra param `$key`"); } } @@ -133,7 +133,7 @@ private function validate($rules, $blockData) */ if (isset($rule['canBeOnly'])) { if (!in_array($value, $rule['canBeOnly'])) { - throw new CodexEditorException("Option '$key' with value `$value` has invalid value. Check canBeOnly param."); + throw new EditorJSException("Option '$key' with value `$value` has invalid value. Check canBeOnly param."); } } @@ -143,14 +143,14 @@ private function validate($rules, $blockData) switch ($elementType) { case 'string': if (!is_string($value)) { - throw new CodexEditorException("Option '$key' with value `$value` must be string"); + throw new EditorJSException("Option '$key' with value `$value` must be string"); } break; case 'integer': case 'int': if (!is_integer($value)) { - throw new CodexEditorException("Option '$key' with value `$value` must be integer"); + throw new EditorJSException("Option '$key' with value `$value` must be integer"); } break; @@ -161,12 +161,12 @@ private function validate($rules, $blockData) case 'boolean': case 'bool': if (!is_bool($value)) { - throw new CodexEditorException("Option '$key' with value `$value` must be boolean"); + throw new EditorJSException("Option '$key' with value `$value` must be boolean"); } break; default: - throw new CodexEditorException("Unhandled type `$elementType`"); + throw new EditorJSException("Unhandled type `$elementType`"); } } @@ -179,7 +179,7 @@ private function validate($rules, $blockData) * @param array $rules * @param array $blockData * - * @throws CodexEditorException + * @throws EditorJSException * * @return array */ diff --git a/CodexEditor/ConfigLoader.php b/EditorJS/ConfigLoader.php similarity index 73% rename from CodexEditor/ConfigLoader.php rename to EditorJS/ConfigLoader.php index 47adc79..477feb8 100644 --- a/CodexEditor/ConfigLoader.php +++ b/EditorJS/ConfigLoader.php @@ -1,11 +1,11 @@ $toolData) { if (isset($this->tools[$toolName])) { - throw new CodexEditorException("Duplicate tool $toolName in configuration"); + throw new EditorJSException("Duplicate tool $toolName in configuration"); } $this->tools[$toolName] = $this->loadTool($toolData); diff --git a/CodexEditor/CodexEditor.php b/EditorJS/EditorJS.php similarity index 82% rename from CodexEditor/CodexEditor.php rename to EditorJS/EditorJS.php index f4baa94..f095a4e 100644 --- a/CodexEditor/CodexEditor.php +++ b/EditorJS/EditorJS.php @@ -1,13 +1,13 @@ blocks, $blockData); } else { - throw new CodexEditorException('Block must be an Array'); + throw new EditorJSException('Block must be an Array'); } } diff --git a/EditorJS/EditorJSException.php b/EditorJS/EditorJSException.php new file mode 100644 index 0000000..84ecd24 --- /dev/null +++ b/EditorJS/EditorJSException.php @@ -0,0 +1,16 @@ +getBlocks(); -} catch (\CodexEditorException $e) { +} catch (\EditorJSException $e) { // process exception } ``` -CodexEditor constructor has the following arguments: +EditorJS constructor has the following arguments: `$data` — JSON string with data from CodeX Editor frontend. @@ -72,7 +72,7 @@ Sample validation rule set: Where: -`tools` — array of supported CodeX Editor tools. +`tools` — array of supported EditorJS tools. `header` — defines `header` tool settings. @@ -91,7 +91,7 @@ Another configuration example: [/tests/samples/test-config.json](/tests/samples/ If you connect a new Tool on the frontend-side, then you should create a configuration rule for that Tool to validate it on server-side. ## Repository -https://github.com/codex-team/codex.editor.backend/ +https://github.com/codex-editor/editorjs.php/ ## About CodeX diff --git a/composer.json b/composer.json index 1b1c63b..7a9028c 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { - "name": "codex-team/codex.editor", + "name": "codex-team/editor.js", "type": "library", - "description": "Codex Editor server side example", + "description": "PHP backend to the CodeX Editor", "license": "MIT", "version": "2.0.0", "authors": [ @@ -19,7 +19,7 @@ "friendsofphp/php-cs-fixer": "^2.13" }, "autoload": { - "psr-4": {"CodexEditor\\": "CodexEditor"} + "psr-4": {"EditorJS\\": "EditorJS"} }, "scripts": { "test": "vendor/bin/phpunit", diff --git a/tests/BlockHandlerTest.php b/tests/BlockHandlerTest.php index a6aa4c1..4e43074 100644 --- a/tests/BlockHandlerTest.php +++ b/tests/BlockHandlerTest.php @@ -1,7 +1,7 @@ configuration); + new EditorJS(BlockHandlerTest::SAMPLE_VALID_DATA, $this->configuration); } public function testSanitizing() { $data = '{"blocks":[{"type":"header","data":{"text":"CodeX Editor", "level": 2}}]}'; - $editor = new CodexEditor($data, $this->configuration); + $editor = new EditorJS($data, $this->configuration); $result = $editor->getBlocks(); $this->assertEquals('CodeX Editor', $result[0]['data']['text']); @@ -45,7 +45,7 @@ public function testSanitizingAllowedTags() { $data = '{"blocks":[{"type":"paragraph","data":{"text":"CodeX Editor ifmo.su"}}]}'; - $editor = new CodexEditor($data, $this->configuration); + $editor = new EditorJS($data, $this->configuration); $result = $editor->getBlocks(); $this->assertEquals('CodeX Editor ifmo.su', $result[0]['data']['text']); @@ -54,9 +54,9 @@ public function testSanitizingAllowedTags() public function testCanBeOnly() { $callable = function () { - new CodexEditor('{"blocks":[{"type":"header","data":{"text":"test","level":5}}]}', $this->configuration); + new EditorJS('{"blocks":[{"type":"header","data":{"text":"test","level":5}}]}', $this->configuration); }; - $this->assertException($callable, CodexEditorException::class, null, 'Option \'level\' with value `5` has invalid value. Check canBeOnly param.'); + $this->assertException($callable, EditorJSException::class, null, 'Option \'level\' with value `5` has invalid value. Check canBeOnly param.'); } } diff --git a/tests/GeneralTest.php b/tests/GeneralTest.php index 4dc3921..15472c8 100644 --- a/tests/GeneralTest.php +++ b/tests/GeneralTest.php @@ -1,8 +1,8 @@ config); + new EditorJS(GeneralTest::SAMPLE_VALID_DATA, $this->config); } public function testNullInput() { $callable = function () { - new CodexEditor('', $this->config); + new EditorJS('', $this->config); }; - $this->assertException($callable, CodexEditorException::class, null, 'JSON is empty'); + $this->assertException($callable, EditorJSException::class, null, 'JSON is empty'); } public function testEmptyArray() { $callable = function () { - new CodexEditor('{}', $this->config); + new EditorJS('{}', $this->config); }; - $this->assertException($callable, CodexEditorException::class, null, 'Input array is empty'); + $this->assertException($callable, EditorJSException::class, null, 'Input array is empty'); } public function testWrongJson() { $callable = function () { - new CodexEditor('{[{', $this->config); + new EditorJS('{[{', $this->config); }; - $this->assertException($callable, CodexEditorException::class, null, 'Wrong JSON format: Syntax error'); + $this->assertException($callable, EditorJSException::class, null, 'Wrong JSON format: Syntax error'); } public function testValidConfig() @@ -64,36 +64,36 @@ public function testValidConfig() public function testItemsMissed() { $callable = function () { - new CodexEditor('{"s":""}', $this->config); + new EditorJS('{"s":""}', $this->config); }; - $this->assertException($callable, CodexEditorException::class, null, 'Field `blocks` is missing'); + $this->assertException($callable, EditorJSException::class, null, 'Field `blocks` is missing'); } public function testUnicode() { $callable = function () { - new CodexEditor('{"s":"😀"}', $this->config); + new EditorJS('{"s":"😀"}', $this->config); }; - $this->assertException($callable, CodexEditorException::class, null, 'Field `blocks` is missing'); + $this->assertException($callable, EditorJSException::class, null, 'Field `blocks` is missing'); } public function testInvalidBlock() { $callable = function () { - new CodexEditor('{"blocks":""}', $this->config); + new EditorJS('{"blocks":""}', $this->config); }; - $this->assertException($callable, CodexEditorException::class, null, 'Blocks is not an array'); + $this->assertException($callable, EditorJSException::class, null, 'Blocks is not an array'); } public function testBlocksContent() { $callable = function () { - new CodexEditor('{"blocks":["",""]}', $this->config); + new EditorJS('{"blocks":["",""]}', $this->config); }; - $this->assertException($callable, CodexEditorException::class, null, 'Block must be an Array'); + $this->assertException($callable, EditorJSException::class, null, 'Block must be an Array'); } } diff --git a/tests/TypeTest.php b/tests/TypeTest.php index 19e150a..ce18689 100644 --- a/tests/TypeTest.php +++ b/tests/TypeTest.php @@ -1,7 +1,7 @@ configuration); + new EditorJS('{"blocks":[{"type":"test","data":{"bool_test":"not boolean"}}]}', $this->configuration); }; - $this->assertException($callable_not_bool, CodexEditorException::class, null, 'Option \'bool_test\' with value `not boolean` must be boolean'); + $this->assertException($callable_not_bool, EditorJSException::class, null, 'Option \'bool_test\' with value `not boolean` must be boolean'); } public function testBooleanValid() { - new CodexEditor('{"blocks":[{"type":"test","data":{"bool_test":true}}]}', $this->configuration); + new EditorJS('{"blocks":[{"type":"test","data":{"bool_test":true}}]}', $this->configuration); } public function testIntegerValid() { - new CodexEditor('{"blocks":[{"type":"test","data":{"int_test": 5}}]}', $this->configuration); + new EditorJS('{"blocks":[{"type":"test","data":{"int_test": 5}}]}', $this->configuration); } public function testIntegerFailed() { $callable = function () { - new CodexEditor('{"blocks":[{"type":"test","data":{"int_test": "not integer"}}]}', $this->configuration); + new EditorJS('{"blocks":[{"type":"test","data":{"int_test": "not integer"}}]}', $this->configuration); }; - $this->assertException($callable, CodexEditorException::class, null, 'Option \'int_test\' with value `not integer` must be integer'); + $this->assertException($callable, EditorJSException::class, null, 'Option \'int_test\' with value `not integer` must be integer'); } public function testStringValid() { - new CodexEditor('{"blocks":[{"type":"test","data":{"string_test": "string"}}]}', $this->configuration); + new EditorJS('{"blocks":[{"type":"test","data":{"string_test": "string"}}]}', $this->configuration); } public function testStringFailed() { $callable = function () { - new CodexEditor('{"blocks":[{"type":"test","data":{"string_test": 17}}]}', $this->configuration); + new EditorJS('{"blocks":[{"type":"test","data":{"string_test": 17}}]}', $this->configuration); }; - $this->assertException($callable, CodexEditorException::class, null, 'Option \'string_test\' with value `17` must be string'); + $this->assertException($callable, EditorJSException::class, null, 'Option \'string_test\' with value `17` must be string'); } } From c427373749cb543066e34f8b8b28387766bc2c9a Mon Sep 17 00:00:00 2001 From: N0str Date: Thu, 4 Oct 2018 16:28:26 +0300 Subject: [PATCH 02/32] Add possible Exceptions to the readme --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 4ae0509..90a8c1c 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,36 @@ Where: Another configuration example: [/tests/samples/test-config.json](/tests/samples/test-config.json) +# Exceptions + +### EditorJS class +| Exception text | Cause +| ----------------------------- | ------------------------------------------------ +| JSON is empty | EditorJS initiated with empty `$json` argument +| Wrong JSON format: `error` | `json_decode` failed during `$json` processing +| Input is null | `json_decode` returned null `$data` object +| Input array is empty | `$data` is an empty array +| Field \`blocks\` is missing | `$data` doesn't contain 'blocks' key +| Blocks is not an array | `$data['blocks']` is not an array +| Block must be an Array | one element in `$data['blocks']` is not an array + +### BlockHandler class +| Exception text | Cause +| --------------------- | ----------------------------------------------- +| Tool \`**TOOL_NAME**\` not found in the configuration | Configuration file doesn't contain **TOOL_NAME** in `tools{}` dictionary +| Not found required param \`**key**\` | **key** tool param exists in configuration but doesn't exist in input data. *(Params are always required by default unless `required: false` is set)* +| Found extra param \`**key**\` | Param **key** exists in input data but doesn't defined in configuration +| Option \`**key**\` with value \`**value**\` has invalid value. Check canBeOnly param. | Parameter must have one of the values from **canBeOnly** array in tool configuration +| Option \`**key**\` with value \`**value**\` must be **TYPE** | Param must have type which is defined in tool configuration *(string, integer, boolean)* +| Unhandled type \`**elementType**\` | Param type in configuration is invalid + +### ConfigLoader class +| Exception text | Cause +| ----------------------------- | ------------------------------------------------ +| Configuration data is empty | EditorJS initiated with empty `$configuration` argument +| Tools not found in configuration | Configuration file doesn't contain `tools` key +| Duplicate tool \`**toolName**\` in configuration | Configuration file has different tools with the same name + # Make Tools If you connect a new Tool on the frontend-side, then you should create a configuration rule for that Tool to validate it on server-side. From fd981cd694ed19b8fcb45d45a27d5aa2fa923485 Mon Sep 17 00:00:00 2001 From: N0str Date: Fri, 5 Oct 2018 18:57:16 +0300 Subject: [PATCH 03/32] Update EditorJS.php to EditorJS-php --- README.md | 4 ++-- composer.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 90a8c1c..13518f8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# EditorJS PHP server validation +# Editor.js PHP server validation This library allows you to use EditorJS server validation. You can easily make your client plugins for EditorJS and then @@ -121,7 +121,7 @@ Another configuration example: [/tests/samples/test-config.json](/tests/samples/ If you connect a new Tool on the frontend-side, then you should create a configuration rule for that Tool to validate it on server-side. ## Repository -https://github.com/codex-editor/editorjs.php/ +https://github.com/codex-editor/editorjs-php/ ## About CodeX diff --git a/composer.json b/composer.json index 7a9028c..89d1816 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "codex-team/editor.js", "type": "library", - "description": "PHP backend to the CodeX Editor", + "description": "PHP backend implementation for the Editor.js", "license": "MIT", "version": "2.0.0", "authors": [ From 988fd53ffce4373ea27fcd5e94c6c00f3eb48efe Mon Sep 17 00:00:00 2001 From: N0str Date: Fri, 5 Oct 2018 19:04:26 +0300 Subject: [PATCH 04/32] Update main description --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 13518f8..6b23e5c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ -# Editor.js PHP server validation +# Editor.js PHP This library allows you to use EditorJS server validation. -You can easily make your client plugins for EditorJS and then -extend server Tool which will be able to clean dirty data or handle that. +You can manually extend EditorJS validation rules with new tools by modifying configuration file. # Installation From 7704a986d2b14915a797590abed645041e15ad1c Mon Sep 17 00:00:00 2001 From: N0str Date: Fri, 5 Oct 2018 19:12:55 +0300 Subject: [PATCH 05/32] Change Editor.js naming. Update main description --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6b23e5c..c2ef7be 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Editor.js PHP -This library allows you to use EditorJS server validation. -You can manually extend EditorJS validation rules with new tools by modifying configuration file. +Server-side implementation sample for the Editor.js. It contains data validation, HTML sanitization and converts output from Editor.js to the Block objects. # Installation @@ -39,7 +38,7 @@ try { } ``` -EditorJS constructor has the following arguments: +Editor.js constructor has the following arguments: `$data` — JSON string with data from CodeX Editor frontend. @@ -47,7 +46,8 @@ EditorJS constructor has the following arguments: # Configuration file -You can configure validation rules for different types of CodeX Editor tools (header, paragraph, list, quote and other). +You can manually configure validation rules for different types of Editor.js tools (header, paragraph, list, quote and other). +You can also extend configuration with new tools. Sample validation rule set: @@ -71,7 +71,7 @@ Sample validation rule set: Where: -`tools` — array of supported EditorJS tools. +`tools` — array of supported Editor.js tools. `header` — defines `header` tool settings. From db25f96a4224d26fa9134a4b40759238ce3241b0 Mon Sep 17 00:00:00 2001 From: N0str Date: Wed, 10 Oct 2018 00:43:14 +0300 Subject: [PATCH 06/32] Change packagist name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c2ef7be..63bbaca 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Server-side implementation sample for the Editor.js. It contains data validation To install lib use composer: ``` -composer require codex-team/codex.editor:dev-master +composer require codex-team/editor.js:dev-master ``` # Guide From 80a3a942e4626590d47aa58bb4a37d816ddfc215 Mon Sep 17 00:00:00 2001 From: N0str Date: Wed, 10 Oct 2018 18:29:04 +0300 Subject: [PATCH 07/32] Fix undefined key --- EditorJS/BlockHandler.php | 11 +++++++++-- tests/BlockHandlerTest.php | 12 ++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/EditorJS/BlockHandler.php b/EditorJS/BlockHandler.php index 062a72c..fdf0e1e 100644 --- a/EditorJS/BlockHandler.php +++ b/EditorJS/BlockHandler.php @@ -189,7 +189,15 @@ private function sanitize($rules, $blockData) * Sanitize every key in data block */ foreach ($blockData as $key => $value) { - $rule = $rules[$key]; + /** + * PHP Array has integer keys + */ + if (is_integer($key)) { + $rule = $rules[BlockHandler::DEFAULT_ARRAY_KEY]; + } else { + $rule = $rules[$key]; + } + $elementType = $rule['type']; /** @@ -207,7 +215,6 @@ private function sanitize($rules, $blockData) $blockData[$key] = $this->sanitize($rule['data'], $value); } } - return $blockData; } diff --git a/tests/BlockHandlerTest.php b/tests/BlockHandlerTest.php index 4e43074..bdf5d59 100644 --- a/tests/BlockHandlerTest.php +++ b/tests/BlockHandlerTest.php @@ -59,4 +59,16 @@ public function testCanBeOnly() $this->assertException($callable, EditorJSException::class, null, 'Option \'level\' with value `5` has invalid value. Check canBeOnly param.'); } + + public function testListTool() + { + $data = '{"time":1539180803359,"blocks":[{"type":"list","data":{"style":"ordered","items":["first","second","third"]}}],"version":"2.1.1"}'; + $editor = new EditorJS($data, $this->configuration); + $result = $editor->getBlocks(); + + $this->assertEquals(3, count($result[0]['data']['items'])); + $this->assertEquals("first", $result[0]['data']['items'][0]); + $this->assertEquals("second", $result[0]['data']['items'][1]); + $this->assertEquals("third", $result[0]['data']['items'][2]); + } } From 38bcdbe3a49103723af7125940ad5cd383dba6c0 Mon Sep 17 00:00:00 2001 From: N0str Date: Sun, 14 Oct 2018 17:56:41 +0300 Subject: [PATCH 08/32] Csfix --- EditorJS/BlockHandler.php | 1 + 1 file changed, 1 insertion(+) diff --git a/EditorJS/BlockHandler.php b/EditorJS/BlockHandler.php index fdf0e1e..4ed44f4 100644 --- a/EditorJS/BlockHandler.php +++ b/EditorJS/BlockHandler.php @@ -215,6 +215,7 @@ private function sanitize($rules, $blockData) $blockData[$key] = $this->sanitize($rule['data'], $value); } } + return $blockData; } From e73e4207efb5078b37b7fb9bafdcb760340306f3 Mon Sep 17 00:00:00 2001 From: N0str Date: Sun, 14 Oct 2018 18:25:12 +0300 Subject: [PATCH 09/32] Move purifier init to the blockHandler. Provide purifier test. Fix bug #27 --- EditorJS/BlockHandler.php | 34 ++++++++++++++++++++++++---------- EditorJS/EditorJS.php | 26 +------------------------- tests/BlockHandlerTest.php | 11 +++++++++++ tests/samples/test-config.json | 5 +++-- 4 files changed, 39 insertions(+), 37 deletions(-) diff --git a/EditorJS/BlockHandler.php b/EditorJS/BlockHandler.php index 4ed44f4..3c97014 100644 --- a/EditorJS/BlockHandler.php +++ b/EditorJS/BlockHandler.php @@ -19,23 +19,16 @@ class BlockHandler */ private $rules = null; - /** - * @var \HTMLPurifier_Config - */ - private $sanitizer; - /** * BlockHandler constructor * - * @param \HTMLPurifier_Config $sanitizer - * @param string $configuration + * @param string $configuration * * @throws EditorJSException */ - public function __construct($configuration, $sanitizer) + public function __construct($configuration) { $this->rules = new ConfigLoader($configuration); - $this->sanitizer = $sanitizer; } /** @@ -228,11 +221,32 @@ private function sanitize($rules, $blockData) */ private function getPurifier($allowedTags) { - $sanitizer = clone $this->sanitizer; + $sanitizer = $this->getDefaultPurifier(); + $sanitizer->set('HTML.Allowed', $allowedTags); $purifier = new \HTMLPurifier($sanitizer); return $purifier; } + + /** + * Initialize HTML Purifier with default settings + */ + private function getDefaultPurifier() + { + $sanitizer = \HTMLPurifier_Config::createDefault(); + + $sanitizer->set('HTML.TargetBlank', true); + $sanitizer->set('URI.AllowedSchemes', ['http' => true, 'https' => true]); + $sanitizer->set('AutoFormat.RemoveEmpty', true); + + if (!is_dir('/tmp/purifier')) { + mkdir('/tmp/purifier', 0777, true); + } + + $sanitizer->set('Cache.SerializerPath', '/tmp/purifier'); + + return $sanitizer; + } } diff --git a/EditorJS/EditorJS.php b/EditorJS/EditorJS.php index f095a4e..e0a54ee 100644 --- a/EditorJS/EditorJS.php +++ b/EditorJS/EditorJS.php @@ -24,11 +24,6 @@ class EditorJS */ public $handler; - /** - * @var \HTMLPurifier_Config - */ - public $sanitizer; - /** * EditorJS constructor. * Splits JSON string to separate blocks @@ -40,8 +35,7 @@ class EditorJS */ public function __construct($json, $configuration) { - $this->initPurifier(); - $this->handler = new BlockHandler($configuration, $this->sanitizer); + $this->handler = new BlockHandler($configuration); /** * Check if json string is empty @@ -102,24 +96,6 @@ public function __construct($json, $configuration) $this->validateBlocks(); } - /** - * Initialize HTML Purifier with default settings - */ - private function initPurifier() - { - $this->sanitizer = \HTMLPurifier_Config::createDefault(); - - $this->sanitizer->set('HTML.TargetBlank', true); - $this->sanitizer->set('URI.AllowedSchemes', ['http' => true, 'https' => true]); - $this->sanitizer->set('AutoFormat.RemoveEmpty', true); - - if (!is_dir('/tmp/purifier')) { - mkdir('/tmp/purifier', 0777, true); - } - - $this->sanitizer->set('Cache.SerializerPath', '/tmp/purifier'); - } - /** * Sanitize and return array of blocks according to the Handler's rules. * diff --git a/tests/BlockHandlerTest.php b/tests/BlockHandlerTest.php index bdf5d59..1c3ed59 100644 --- a/tests/BlockHandlerTest.php +++ b/tests/BlockHandlerTest.php @@ -71,4 +71,15 @@ public function testListTool() $this->assertEquals("second", $result[0]['data']['items'][1]); $this->assertEquals("third", $result[0]['data']['items'][2]); } + + public function testHtmlPurifier() + { + $data = '{"time":1539180803359,"blocks":[{"type":"header","data":{"text":"test","level":2}}, {"type":"quote","data":{"text":"test","caption":"", "alignment":"left"}}]}'; + $editor = new EditorJS($data, $this->configuration); + $result = $editor->getBlocks(); + + $this->assertEquals(2, count($result)); + $this->assertEquals('test', $result[0]['data']['text']); + $this->assertEquals('test', $result[1]['data']['text']); + } } diff --git a/tests/samples/test-config.json b/tests/samples/test-config.json index ef26367..fdfe75f 100644 --- a/tests/samples/test-config.json +++ b/tests/samples/test-config.json @@ -33,12 +33,13 @@ }, "quote": { "text": { - "type": "string" + "type": "string", + "allowedTags": "i,b,u" }, "caption": { "type": "string" }, - "size": { + "alignment": { "type": "string", "canBeOnly": ["left", "right"] } From b9f57bad7a5291c94a4729401653e783da98b48b Mon Sep 17 00:00:00 2001 From: N0str Date: Sun, 14 Oct 2018 23:20:57 +0300 Subject: [PATCH 10/32] Add custom tag support and mark inline tag --- EditorJS/BlockHandler.php | 8 +++++ tests/BlockHandlerTest.php | 11 ------- tests/PurifierTest.php | 46 +++++++++++++++++++++++++++ tests/samples/purify-test-config.json | 27 ++++++++++++++++ 4 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 tests/PurifierTest.php create mode 100644 tests/samples/purify-test-config.json diff --git a/EditorJS/BlockHandler.php b/EditorJS/BlockHandler.php index 3c97014..f90d883 100644 --- a/EditorJS/BlockHandler.php +++ b/EditorJS/BlockHandler.php @@ -225,6 +225,13 @@ private function getPurifier($allowedTags) $sanitizer->set('HTML.Allowed', $allowedTags); + /** + * Define custom HTML Definition for mark tool + */ + if ($def = $sanitizer->maybeGetRawHTMLDefinition()) { + $def->addElement('mark', 'Inline', 'Inline', 'Common'); + } + $purifier = new \HTMLPurifier($sanitizer); return $purifier; @@ -240,6 +247,7 @@ private function getDefaultPurifier() $sanitizer->set('HTML.TargetBlank', true); $sanitizer->set('URI.AllowedSchemes', ['http' => true, 'https' => true]); $sanitizer->set('AutoFormat.RemoveEmpty', true); + $sanitizer->set('HTML.DefinitionID', 'html5-definitions'); if (!is_dir('/tmp/purifier')) { mkdir('/tmp/purifier', 0777, true); diff --git a/tests/BlockHandlerTest.php b/tests/BlockHandlerTest.php index 1c3ed59..bdf5d59 100644 --- a/tests/BlockHandlerTest.php +++ b/tests/BlockHandlerTest.php @@ -71,15 +71,4 @@ public function testListTool() $this->assertEquals("second", $result[0]['data']['items'][1]); $this->assertEquals("third", $result[0]['data']['items'][2]); } - - public function testHtmlPurifier() - { - $data = '{"time":1539180803359,"blocks":[{"type":"header","data":{"text":"test","level":2}}, {"type":"quote","data":{"text":"test","caption":"", "alignment":"left"}}]}'; - $editor = new EditorJS($data, $this->configuration); - $result = $editor->getBlocks(); - - $this->assertEquals(2, count($result)); - $this->assertEquals('test', $result[0]['data']['text']); - $this->assertEquals('test', $result[1]['data']['text']); - } } diff --git a/tests/PurifierTest.php b/tests/PurifierTest.php new file mode 100644 index 0000000..762bf57 --- /dev/null +++ b/tests/PurifierTest.php @@ -0,0 +1,46 @@ +configuration = file_get_contents(PurifierTest::CONFIGURATION_FILE); + } + + public function testHtmlPurifier() + { + $data = '{"time":1539180803359,"blocks":[{"type":"header","data":{"text":"test","level":2}}, {"type":"quote","data":{"text":"test","caption":"", "alignment":"left"}}]}'; + $editor = new EditorJS($data, $this->configuration); + $result = $editor->getBlocks(); + + $this->assertEquals(2, count($result)); + $this->assertEquals('test', $result[0]['data']['text']); + $this->assertEquals('test', $result[1]['data']['text']); + } + + public function testCustomTagPurifier() + { + $data = '{"time":1539180803359,"blocks":[{"type":"header","data":{"text":"test","level":2}}]}'; + $editor = new EditorJS($data, $this->configuration); + $result = $editor->getBlocks(); + + $this->assertEquals('test', $result[0]['data']['text']); + } +} diff --git a/tests/samples/purify-test-config.json b/tests/samples/purify-test-config.json new file mode 100644 index 0000000..cc690bc --- /dev/null +++ b/tests/samples/purify-test-config.json @@ -0,0 +1,27 @@ +{ + "tools": { + "header": { + "text": { + "type": "string", + "allowedTags": "mark" + }, + "level": { + "type": "int", + "canBeOnly": [2, 3, 4] + } + }, + "quote": { + "text": { + "type": "string", + "allowedTags": "i,b,u" + }, + "caption": { + "type": "string" + }, + "alignment": { + "type": "string", + "canBeOnly": ["left", "right"] + } + } + } +} \ No newline at end of file From 7d2331b87962f4f073283a085986d11858e27dd3 Mon Sep 17 00:00:00 2001 From: N0str Date: Mon, 15 Oct 2018 21:05:12 +0300 Subject: [PATCH 11/32] Add 'all' to allowedTags --- EditorJS/BlockHandler.php | 4 +++- composer.json | 2 +- tests/PurifierTest.php | 9 +++++++++ tests/samples/purify-test-config.json | 6 ++++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/EditorJS/BlockHandler.php b/EditorJS/BlockHandler.php index f90d883..fc6888d 100644 --- a/EditorJS/BlockHandler.php +++ b/EditorJS/BlockHandler.php @@ -198,7 +198,9 @@ private function sanitize($rules, $blockData) */ if ($elementType == 'string') { $allowedTags = isset($rule['allowedTags']) ? $rule['allowedTags'] : ''; - $blockData[$key] = $this->getPurifier($allowedTags)->purify($value); + if ($allowedTags !== 'all') { + $blockData[$key] = $this->getPurifier($allowedTags)->purify($value); + } } /** diff --git a/composer.json b/composer.json index 89d1816..bab3fcc 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "type": "library", "description": "PHP backend implementation for the Editor.js", "license": "MIT", - "version": "2.0.0", + "version": "2.0.1", "authors": [ { "name": "CodeX Team", diff --git a/tests/PurifierTest.php b/tests/PurifierTest.php index 762bf57..98d48f6 100644 --- a/tests/PurifierTest.php +++ b/tests/PurifierTest.php @@ -43,4 +43,13 @@ public function testCustomTagPurifier() $this->assertEquals('test', $result[0]['data']['text']); } + + public function testAllTagsPurifier() + { + $data = '{"time":1539180803359,"blocks":[{"type":"raw","data":{"html": "
Any HTML code
"}}]}'; + $editor = new EditorJS($data, $this->configuration); + $result = $editor->getBlocks(); + + $this->assertEquals('
Any HTML code
', $result[0]['data']['html']); + } } diff --git a/tests/samples/purify-test-config.json b/tests/samples/purify-test-config.json index cc690bc..88b9bcc 100644 --- a/tests/samples/purify-test-config.json +++ b/tests/samples/purify-test-config.json @@ -22,6 +22,12 @@ "type": "string", "canBeOnly": ["left", "right"] } + }, + "raw": { + "html": { + "type": "string", + "allowedTags": "all" + } } } } \ No newline at end of file From ad54b61131882d1be0bfc8934669e6115f30b183 Mon Sep 17 00:00:00 2001 From: N0str Date: Mon, 15 Oct 2018 21:08:23 +0300 Subject: [PATCH 12/32] Change 'all' to '*' --- EditorJS/BlockHandler.php | 2 +- tests/samples/purify-test-config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/EditorJS/BlockHandler.php b/EditorJS/BlockHandler.php index fc6888d..ff5068c 100644 --- a/EditorJS/BlockHandler.php +++ b/EditorJS/BlockHandler.php @@ -198,7 +198,7 @@ private function sanitize($rules, $blockData) */ if ($elementType == 'string') { $allowedTags = isset($rule['allowedTags']) ? $rule['allowedTags'] : ''; - if ($allowedTags !== 'all') { + if ($allowedTags !== '*') { $blockData[$key] = $this->getPurifier($allowedTags)->purify($value); } } diff --git a/tests/samples/purify-test-config.json b/tests/samples/purify-test-config.json index 88b9bcc..01e954a 100644 --- a/tests/samples/purify-test-config.json +++ b/tests/samples/purify-test-config.json @@ -26,7 +26,7 @@ "raw": { "html": { "type": "string", - "allowedTags": "all" + "allowedTags": "*" } } } From 4e32477908611cda8ec43a0a3ff36000617ba3d5 Mon Sep 17 00:00:00 2001 From: N0str Date: Mon, 15 Oct 2018 21:12:55 +0300 Subject: [PATCH 13/32] Fix typos --- tests/samples/purify-test-config.json | 2 +- tests/samples/test-config.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/samples/purify-test-config.json b/tests/samples/purify-test-config.json index 01e954a..8f9dc83 100644 --- a/tests/samples/purify-test-config.json +++ b/tests/samples/purify-test-config.json @@ -20,7 +20,7 @@ }, "alignment": { "type": "string", - "canBeOnly": ["left", "right"] + "canBeOnly": ["left", "center"] } }, "raw": { diff --git a/tests/samples/test-config.json b/tests/samples/test-config.json index fdfe75f..62de0db 100644 --- a/tests/samples/test-config.json +++ b/tests/samples/test-config.json @@ -19,7 +19,7 @@ "list": { "style": { "type": "string", - "canBeOnly": ["ordered", "numbered"] + "canBeOnly": ["ordered", "unordered"] }, "items": { "type": "array", @@ -41,7 +41,7 @@ }, "alignment": { "type": "string", - "canBeOnly": ["left", "right"] + "canBeOnly": ["left", "center"] } } } From 17f509425bcc63b39a9da8e16e46a33f10a1a591 Mon Sep 17 00:00:00 2001 From: N0str Date: Mon, 15 Oct 2018 23:01:30 +0300 Subject: [PATCH 14/32] Add new nested test --- tests/GeneralTest.php | 17 +++++++++++++++++ tests/samples/test-config.json | 26 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/tests/GeneralTest.php b/tests/GeneralTest.php index 15472c8..b5e472f 100644 --- a/tests/GeneralTest.php +++ b/tests/GeneralTest.php @@ -96,4 +96,21 @@ public function testBlocksContent() $this->assertException($callable, EditorJSException::class, null, 'Block must be an Array'); } + + public function testNested() + { + $data = '{"blocks":[{"type":"table","data":{"header": {"description":"a table", "author": "codex"}, "rows": [["name", "age", "sex"],["Paul", "24", "male"],["Ann", "26", "female"]]}}]}'; + $editor = new EditorJS($data, $this->config); + $result = $editor->getBlocks(); + + $valid_rows = [["name", "age", "sex"],["Paul", "24", "male"],["Ann", "26", "female"]]; + + $this->assertEquals('a table', $result[0]['data']['header']['description']); + $this->assertEquals('codex', $result[0]['data']['header']['author']); + $this->assertEquals(3, count($result[0]['data']['rows'])); + + $this->assertEquals('name', $result[0]['data']['rows'][0][0]); + $this->assertEquals('24', $result[0]['data']['rows'][1][1]); + $this->assertEquals('female', $result[0]['data']['rows'][2][2]); + } } diff --git a/tests/samples/test-config.json b/tests/samples/test-config.json index 62de0db..bb00c55 100644 --- a/tests/samples/test-config.json +++ b/tests/samples/test-config.json @@ -43,6 +43,32 @@ "type": "string", "canBeOnly": ["left", "center"] } + }, + "table": { + "header": { + "type": "array", + "data": { + "description": { + "type": "string" + }, + "author": { + "type": "string" + } + } + }, + "rows": { + "type": "array", + "data": { + "-": { + "type": "array", + "data": { + "-": { + "type": "string" + } + } + } + } + } } } } \ No newline at end of file From 0c8a3e7b0f5f595de6ac9e61e89479ee2e9cc2cc Mon Sep 17 00:00:00 2001 From: N0str Date: Mon, 15 Oct 2018 23:12:40 +0300 Subject: [PATCH 15/32] Update readme and nested configuration --- README.md | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/README.md b/README.md index 63bbaca..fd863d0 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,107 @@ Where: `allowedTags` param should follow [HTMLPurifier](https://github.com/ezyang/htmlpurifier]) format. +#### There are three common parameters for every block: + +1. `type` (**required**) — type of the block + +|value|description| +|---|---| +|`string`|field with string value| +|`int`/`integer`|field with integer value| +|`bool`/`boolean`|field with boolean value| +|`array`|field with nested fields| + +2. `allowedTags` (optional) — HTML tags in string that won't be removed + + |value|default|description| +|---|---|---| +|`empty`|yes|all tags will be removed| +|`*`|no|all tags are allowed| + +Other values are allowed according to the [HTMLPurifier](https://github.com/ezyang/htmlpurifier]) format. + +Example: +``` +"paragraph": { + "text": { + "type": "string", + "allowedTags": "i,b,u,a[href]" + } +} +``` + +3. `canBeOnly` (optional) — define set of allowed values +Example: +``` +"quote": { + "text": { + "type": "string" + }, + "caption": { + "type": "string" + }, + "alignment": { + "type": "string", + "canBeOnly": ["left", "center"] + } + } +``` + +#### Nested tools + +Tools can contain nested values. It is possible with the `array` type. + +Let the JSON input be the following: +``` +{ + "blocks": [ + "type": list, + "data": { + "items": [ + "first", "second", "third" + ], + "style": { + "background-color": "red", + "font-color": "black" + } + } + ] +} +``` + +We can define validation rules for this input in the config: +``` +"list": { + "items": { + "type": "array", + "data": { + "-": { + "type": "string", + "allowedTags": "i,b,u" + } + } + }, + "style": { + "type": "array", + "data": { + "background-color": { + "type": "string", + "canBeOnly": ["red", "blue", "green"] + }, + "font-color": { + "type": "string", + "canBeOnly": ["black", "white"] + } + } + } +} +``` + +where `data` is the container for values of the array and `-` is the special shortcut for values if the array is sequential. + + + Another configuration example: [/tests/samples/test-config.json](/tests/samples/test-config.json) # Exceptions From 59d8aced7c64997e0b5acd9d26674b6e411b5026 Mon Sep 17 00:00:00 2001 From: N0str Date: Mon, 15 Oct 2018 23:14:46 +0300 Subject: [PATCH 16/32] Header size --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fd863d0..a6285cd 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ Example: ``` 3. `canBeOnly` (optional) — define set of allowed values + Example: ``` "quote": { @@ -130,7 +131,7 @@ Example: } ``` -#### Nested tools +### Nested tools Tools can contain nested values. It is possible with the `array` type. From 3734340da9fa7fca3f5fbcf004c7b46e0e10d54d Mon Sep 17 00:00:00 2001 From: N0str Date: Wed, 31 Oct 2018 20:12:52 +0300 Subject: [PATCH 17/32] Add simplified tool settings --- EditorJS/BlockHandler.php | 51 ++++++++++++++++++++++++++++ tests/SyntaxSugarTest.php | 59 +++++++++++++++++++++++++++++++++ tests/samples/syntax-sugar.json | 16 +++++++++ 3 files changed, 126 insertions(+) create mode 100644 tests/SyntaxSugarTest.php create mode 100644 tests/samples/syntax-sugar.json diff --git a/EditorJS/BlockHandler.php b/EditorJS/BlockHandler.php index ff5068c..9a25fb2 100644 --- a/EditorJS/BlockHandler.php +++ b/EditorJS/BlockHandler.php @@ -119,6 +119,9 @@ private function validate($rules, $blockData) } $rule = $rules[$key]; + + $rule = $this->expandToolSettings($rule); + $elementType = $rule['type']; /** @@ -128,6 +131,9 @@ private function validate($rules, $blockData) if (!in_array($value, $rule['canBeOnly'])) { throw new EditorJSException("Option '$key' with value `$value` has invalid value. Check canBeOnly param."); } + + // Do not perform additional elements validation in any case + continue; } /** @@ -191,6 +197,7 @@ private function sanitize($rules, $blockData) $rule = $rules[$key]; } + $rule = $this->expandToolSettings($rule); $elementType = $rule['type']; /** @@ -259,4 +266,48 @@ private function getDefaultPurifier() return $sanitizer; } + + /** + * Check whether the array is associative or sequential + * + * @param array $arr – array to check + * + * @return bool – true if the array is associative + */ + private function isAssoc(array $arr) + { + if ([] === $arr) { + return false; + } + + return array_keys($arr) !== range(0, count($arr) - 1); + } + + /** + * Expand shortified tool settings + * + * @param $rule – tool settings + * + * @throws EditorJSException + * + * @return array – expanded tool settings + */ + private function expandToolSettings($rule) + { + if (is_string($rule)) { + // 'blockName': 'string' – tool with string type and default settings + $expandedRule = ["type" => "string"]; + } elseif (is_array($rule)) { + if ($this->isAssoc($rule)) { + $expandedRule = $rule; + } else { + // 'blockName': [] – tool with canBeOnly and default settings + $expandedRule = ["type" => "string", "canBeOnly" => $rule]; + } + } else { + throw new EditorJSException("Cannot determine element type of the rule `$rule`."); + } + + return $expandedRule; + } } diff --git a/tests/SyntaxSugarTest.php b/tests/SyntaxSugarTest.php new file mode 100644 index 0000000..edd9ebd --- /dev/null +++ b/tests/SyntaxSugarTest.php @@ -0,0 +1,59 @@ +configuration = file_get_contents(SyntaxSugarTest::CONFIGURATION_FILE); + } + + public function testShortTypeField() + { + $data = '{"blocks":[{"type":"header","data":{"text":"CodeX Editor", "level": 2}}]}'; + + $editor = new EditorJS($data, $this->configuration); + $result = $editor->getBlocks(); + + $this->assertEquals('CodeX Editor', $result[0]['data']['text']); + $this->assertEquals(2, $result[0]['data']['level']); + } + + public function testShortTypeFieldCanBeOnly() + { + $callable = function () { + new EditorJS('{"blocks":[{"type":"header","data":{"text":"CodeX Editor", "level": 5}}]}', + $this->configuration); + }; + + $this->assertException($callable, EditorJSException::class, null, 'Option \'level\' with value `5` has invalid value. Check canBeOnly param.'); + } + + public function testMixedStructure() + { + $data = '{"time":1539180803359,"blocks":[{"type":"header","data":{"text":"test","level":2}}, {"type":"quote","data":{"text":"test","caption":"", "alignment":"left"}}]}'; + $editor = new EditorJS($data, $this->configuration); + $result = $editor->getBlocks(); + + $this->assertEquals(2, count($result)); + $this->assertEquals('test', $result[0]['data']['text']); + $this->assertEquals('test', $result[1]['data']['text']); + } +} diff --git a/tests/samples/syntax-sugar.json b/tests/samples/syntax-sugar.json new file mode 100644 index 0000000..682ab64 --- /dev/null +++ b/tests/samples/syntax-sugar.json @@ -0,0 +1,16 @@ +{ + "tools": { + "header": { + "text": "string", + "level": [2, 3, 4] + }, + "quote": { + "text": { + "type": "string", + "allowedTags": "i,b" + }, + "caption": "string", + "alignment": ["left", "center"] + } + } +} \ No newline at end of file From 70e897724686b1fd7943c0f7c748de731974d9f7 Mon Sep 17 00:00:00 2001 From: N0str Date: Wed, 31 Oct 2018 20:22:25 +0300 Subject: [PATCH 18/32] Fix types. More tests. Add docs. --- EditorJS/BlockHandler.php | 2 +- README.md | 22 ++++++++++++++++++++++ tests/SyntaxSugarTest.php | 14 ++++++++++++++ tests/samples/syntax-sugar.json | 4 ++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/EditorJS/BlockHandler.php b/EditorJS/BlockHandler.php index 9a25fb2..d217ad0 100644 --- a/EditorJS/BlockHandler.php +++ b/EditorJS/BlockHandler.php @@ -296,7 +296,7 @@ private function expandToolSettings($rule) { if (is_string($rule)) { // 'blockName': 'string' – tool with string type and default settings - $expandedRule = ["type" => "string"]; + $expandedRule = ["type" => $rule]; } elseif (is_array($rule)) { if ($this->isAssoc($rule)) { $expandedRule = $rule; diff --git a/README.md b/README.md index a6285cd..80e4eab 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,28 @@ Example: } ``` +### Short settings syntax + +Some syntax sugar has been added. + +Tool settings can be a `string`. It defines tool's type with default settings. +```json +"header": { + "text": "string", + "level": "int" +} +``` + +Tool settings can be an `array`. It defines set of allowed valus without sanitizing. +```json +"quote": { + "alignment": ["left", "center"], + "caption": "string" +} +``` + +Another configuration example: [/tests/samples/syntax-sugar.json](/tests/samples/syntax-sugar.json) + ### Nested tools Tools can contain nested values. It is possible with the `array` type. diff --git a/tests/SyntaxSugarTest.php b/tests/SyntaxSugarTest.php index edd9ebd..54d3f84 100644 --- a/tests/SyntaxSugarTest.php +++ b/tests/SyntaxSugarTest.php @@ -46,6 +46,20 @@ public function testShortTypeFieldCanBeOnly() $this->assertException($callable, EditorJSException::class, null, 'Option \'level\' with value `5` has invalid value. Check canBeOnly param.'); } + public function testShortIntValid() + { + new EditorJS('{"blocks":[{"type":"subtitle","data":{"text": "string", "level": 1337}}]}', $this->configuration); + } + + public function testShortIntNotValid() + { + $callable = function () { + new EditorJS('{"blocks":[{"type":"subtitle","data":{"text": "string", "level": "string"}}]}', $this->configuration); + }; + + $this->assertException($callable, EditorJSException::class, null, 'Option \'level\' with value `string` must be integer'); + } + public function testMixedStructure() { $data = '{"time":1539180803359,"blocks":[{"type":"header","data":{"text":"test","level":2}}, {"type":"quote","data":{"text":"test","caption":"", "alignment":"left"}}]}'; diff --git a/tests/samples/syntax-sugar.json b/tests/samples/syntax-sugar.json index 682ab64..cb1f8a3 100644 --- a/tests/samples/syntax-sugar.json +++ b/tests/samples/syntax-sugar.json @@ -4,6 +4,10 @@ "text": "string", "level": [2, 3, 4] }, + "subtitle": { + "text": "string", + "level": "int" + }, "quote": { "text": { "type": "string", From 60f28f15441abc0bf2c8fa0b53b16fc189b83cdf Mon Sep 17 00:00:00 2001 From: N0str Date: Wed, 31 Oct 2018 21:15:24 +0300 Subject: [PATCH 19/32] Add example of full config to README --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index 80e4eab..cfd078e 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,22 @@ Tool settings can be a `string`. It defines tool's type with default settings. } ``` +It evaluates to: +```json +"header": { + "text": { + "type": "string", + "allowedTags": "", + "required": true + }, + "level": { + "type": "int", + "allowedTags": "", + "required": true + } +} +``` + Tool settings can be an `array`. It defines set of allowed valus without sanitizing. ```json "quote": { @@ -151,6 +167,21 @@ Tool settings can be an `array`. It defines set of allowed valus without sanitiz } ``` +It evaluates to: +```json +"quote": { + "alignment": { + "type": "string", + "canBeOnly": ["left", "center"] + }, + "caption": { + "type": "string", + "allowedTags": "", + "required": true + } +} +``` + Another configuration example: [/tests/samples/syntax-sugar.json](/tests/samples/syntax-sugar.json) ### Nested tools From 947f1dc9be1f2600b13d145b5ba790e2a7f7d682 Mon Sep 17 00:00:00 2001 From: N0str Date: Thu, 1 Nov 2018 11:22:15 +0300 Subject: [PATCH 20/32] Add test InvalidType --- tests/SyntaxSugarTest.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/SyntaxSugarTest.php b/tests/SyntaxSugarTest.php index 54d3f84..d4f3a3e 100644 --- a/tests/SyntaxSugarTest.php +++ b/tests/SyntaxSugarTest.php @@ -54,12 +54,22 @@ public function testShortIntValid() public function testShortIntNotValid() { $callable = function () { - new EditorJS('{"blocks":[{"type":"subtitle","data":{"text": "string", "level": "string"}}]}', $this->configuration); + new EditorJS('{"blocks":[{"type":"subtitle","data":{"text": "test", "level": "string"}}]}', $this->configuration); }; $this->assertException($callable, EditorJSException::class, null, 'Option \'level\' with value `string` must be integer'); } + public function testInvalidType() + { + $callable = function () { + $invalid_configuration = '{"tools": {"header": {"title": "invalid_type"}}}'; + new EditorJS('{"blocks":[{"type":"header","data":{"title": "test"}}]}', $invalid_configuration); + }; + + $this->assertException($callable, EditorJSException::class, null, 'Unhandled type `invalid_type`'); + } + public function testMixedStructure() { $data = '{"time":1539180803359,"blocks":[{"type":"header","data":{"text":"test","level":2}}, {"type":"quote","data":{"text":"test","caption":"", "alignment":"left"}}]}'; From 5b59050b7f76e7f9e6b0c589bc4444a9644cbc3e Mon Sep 17 00:00:00 2001 From: N0str Date: Thu, 1 Nov 2018 11:24:28 +0300 Subject: [PATCH 21/32] Fix typo in docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cfd078e..fc8dd27 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ It evaluates to: } ``` -Tool settings can be an `array`. It defines set of allowed valus without sanitizing. +Tool settings can be an `array`. It defines a set of allowed values without sanitizing. ```json "quote": { "alignment": ["left", "center"], From b061fc64cfd788727deac2641634cf1b512c58cf Mon Sep 17 00:00:00 2001 From: N0str Date: Sat, 17 Nov 2018 01:30:57 +0300 Subject: [PATCH 22/32] Remove static version from composer --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index bab3fcc..0ac1795 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,6 @@ "type": "library", "description": "PHP backend implementation for the Editor.js", "license": "MIT", - "version": "2.0.1", "authors": [ { "name": "CodeX Team", From 50afd94fab47097837a57dc17fd42fba7dda9e73 Mon Sep 17 00:00:00 2001 From: Rushan <34035978+auchanhub@users.noreply.github.com> Date: Tue, 7 May 2019 15:06:19 +0300 Subject: [PATCH 23/32] Fix HTMLPurifier cache directory --- EditorJS/BlockHandler.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/EditorJS/BlockHandler.php b/EditorJS/BlockHandler.php index d217ad0..887789c 100644 --- a/EditorJS/BlockHandler.php +++ b/EditorJS/BlockHandler.php @@ -258,11 +258,12 @@ private function getDefaultPurifier() $sanitizer->set('AutoFormat.RemoveEmpty', true); $sanitizer->set('HTML.DefinitionID', 'html5-definitions'); - if (!is_dir('/tmp/purifier')) { - mkdir('/tmp/purifier', 0777, true); + $cacheDirectory = sys_get_temp_dir(). DIRECTORY_SEPARATOR.'purifier'; + if (!is_dir($cacheDirectory)) { + mkdir($cacheDirectory, 0777, true); } - $sanitizer->set('Cache.SerializerPath', '/tmp/purifier'); + $sanitizer->set('Cache.SerializerPath', $cacheDirectory); return $sanitizer; } From 330ee3ddd6e32e7c7b19616bcfc39ce3e34281a2 Mon Sep 17 00:00:00 2001 From: N0str Date: Tue, 7 May 2019 18:37:35 +0300 Subject: [PATCH 24/32] Apply csfix --- EditorJS/BlockHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EditorJS/BlockHandler.php b/EditorJS/BlockHandler.php index 887789c..e70566e 100644 --- a/EditorJS/BlockHandler.php +++ b/EditorJS/BlockHandler.php @@ -258,7 +258,7 @@ private function getDefaultPurifier() $sanitizer->set('AutoFormat.RemoveEmpty', true); $sanitizer->set('HTML.DefinitionID', 'html5-definitions'); - $cacheDirectory = sys_get_temp_dir(). DIRECTORY_SEPARATOR.'purifier'; + $cacheDirectory = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'purifier'; if (!is_dir($cacheDirectory)) { mkdir($cacheDirectory, 0777, true); } From c1462c96fdfeaf2e0cad646a0bf0967f0122e45d Mon Sep 17 00:00:00 2001 From: Alexander Menshikov Date: Wed, 12 Jun 2019 17:42:59 +0300 Subject: [PATCH 25/32] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc8dd27..4e87587 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Where: `level` is an **optional** *integer* that can be only 0, 1 or 2. -`allowedTags` param should follow [HTMLPurifier](https://github.com/ezyang/htmlpurifier]) format. +`allowedTags` param should follow [HTMLPurifier](https://github.com/ezyang/htmlpurifier) format. #### There are three common parameters for every block: From c711c24d4985f4bbfed80592e8f673a66ca227ed Mon Sep 17 00:00:00 2001 From: Ryan Chandler Date: Fri, 19 Jul 2019 18:27:53 +0100 Subject: [PATCH 26/32] Fixed HTMLPurifier link in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e87587..196b3f3 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ Where: |`empty`|yes|all tags will be removed| |`*`|no|all tags are allowed| -Other values are allowed according to the [HTMLPurifier](https://github.com/ezyang/htmlpurifier]) format. +Other values are allowed according to the [HTMLPurifier](https://github.com/ezyang/htmlpurifier) format. Example: ``` From 222a9247ab89c7d4ed8e21d6c46eedf57e9b00df Mon Sep 17 00:00:00 2001 From: N0str Date: Mon, 27 Apr 2020 10:15:23 +0300 Subject: [PATCH 27/32] Allow null as typed value for any not required field --- EditorJS/BlockHandler.php | 7 +++++++ tests/TypeTest.php | 16 ++++++++++++++++ tests/samples/type-test-config-required.json | 10 ++++++++++ 3 files changed, 33 insertions(+) create mode 100644 tests/samples/type-test-config-required.json diff --git a/EditorJS/BlockHandler.php b/EditorJS/BlockHandler.php index e70566e..86737c6 100644 --- a/EditorJS/BlockHandler.php +++ b/EditorJS/BlockHandler.php @@ -136,6 +136,13 @@ private function validate($rules, $blockData) continue; } + /** + * Do not check element type if it is not required and null + */ + if (isset($rule['required']) && $rule['required'] === false && $value === null) { + continue; + } + /** * Validate element types */ diff --git a/tests/TypeTest.php b/tests/TypeTest.php index ce18689..5bede33 100644 --- a/tests/TypeTest.php +++ b/tests/TypeTest.php @@ -11,6 +11,7 @@ class TypeTest extends TestCase { const CONFIGURATION_FILE = TESTS_DIR . "/samples/type-test-config.json"; + const CONFIGURATION_FILE_REQUIRED = TESTS_DIR . "/samples/type-test-config-required.json"; /** * Sample configuration @@ -66,4 +67,19 @@ public function testStringFailed() $this->assertException($callable, EditorJSException::class, null, 'Option \'string_test\' with value `17` must be string'); } + + public function testNullNotRequired() + { + new EditorJS('{"blocks":[{"type":"test","data":{"string_test": null}}]}', $this->configuration); + } + + public function testNullRequired() + { + new EditorJS('{"blocks":[{"type":"test","data":{"string_test": "qwe"}}]}', file_get_contents(TypeTest::CONFIGURATION_FILE_REQUIRED)); + + $callable = function () { + new EditorJS('{"blocks":[{"type":"test","data":{"string_test": null}}]}', file_get_contents(TypeTest::CONFIGURATION_FILE_REQUIRED)); + }; + $this->assertException($callable, EditorJSException::class, null, 'Not found required param `string_test`'); + } } diff --git a/tests/samples/type-test-config-required.json b/tests/samples/type-test-config-required.json new file mode 100644 index 0000000..be3a5a0 --- /dev/null +++ b/tests/samples/type-test-config-required.json @@ -0,0 +1,10 @@ +{ + "tools": { + "test": { + "string_test": { + "type": "string", + "required": true + } + } + } +} \ No newline at end of file From f32c6810ece7fd64cdeccc54254147332cab38fb Mon Sep 17 00:00:00 2001 From: N0str Date: Mon, 27 Apr 2020 10:21:23 +0300 Subject: [PATCH 28/32] Introduce allow_null option --- EditorJS/BlockHandler.php | 3 ++- tests/TypeTest.php | 13 +++++++++++-- tests/samples/type-test-config-required.json | 3 ++- tests/samples/type-test-config.json | 6 ++++-- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/EditorJS/BlockHandler.php b/EditorJS/BlockHandler.php index 86737c6..f7b9c08 100644 --- a/EditorJS/BlockHandler.php +++ b/EditorJS/BlockHandler.php @@ -139,7 +139,8 @@ private function validate($rules, $blockData) /** * Do not check element type if it is not required and null */ - if (isset($rule['required']) && $rule['required'] === false && $value === null) { + if (isset($rule['required']) && $rule['required'] === false && + isset($rule['allow_null']) && $rule['allow_null'] === true && $value === null) { continue; } diff --git a/tests/TypeTest.php b/tests/TypeTest.php index 5bede33..7074067 100644 --- a/tests/TypeTest.php +++ b/tests/TypeTest.php @@ -68,9 +68,18 @@ public function testStringFailed() $this->assertException($callable, EditorJSException::class, null, 'Option \'string_test\' with value `17` must be string'); } - public function testNullNotRequired() + public function testAllowedNullNotRequired() { - new EditorJS('{"blocks":[{"type":"test","data":{"string_test": null}}]}', $this->configuration); + new EditorJS('{"blocks":[{"type":"test","data":{"int_test": null}}]}', $this->configuration); + } + + public function testDisallowedNullNotRequired() + { + $callable = function () { + new EditorJS('{"blocks":[{"type":"test","data":{"string_test": null}}]}', $this->configuration); + }; + + $this->assertException($callable, EditorJSException::class, null, 'string_test\' with value `` must be string'); } public function testNullRequired() diff --git a/tests/samples/type-test-config-required.json b/tests/samples/type-test-config-required.json index be3a5a0..c92aec7 100644 --- a/tests/samples/type-test-config-required.json +++ b/tests/samples/type-test-config-required.json @@ -3,7 +3,8 @@ "test": { "string_test": { "type": "string", - "required": true + "required": true, + "allow_null": true } } } diff --git a/tests/samples/type-test-config.json b/tests/samples/type-test-config.json index 286e834..e2b1fed 100644 --- a/tests/samples/type-test-config.json +++ b/tests/samples/type-test-config.json @@ -7,11 +7,13 @@ }, "int_test": { "type": "integer", - "required": false + "required": false, + "allow_null": true }, "string_test": { "type": "string", - "required": false + "required": false, + "allow_null": false } } } From 6d611e110765129470e393c907036f39ee508503 Mon Sep 17 00:00:00 2001 From: rendom Date: Thu, 4 Jun 2020 10:47:47 +0200 Subject: [PATCH 29/32] Allow more URI schemes Allow mailto and tel. --- EditorJS/BlockHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EditorJS/BlockHandler.php b/EditorJS/BlockHandler.php index e70566e..82f514b 100644 --- a/EditorJS/BlockHandler.php +++ b/EditorJS/BlockHandler.php @@ -254,7 +254,7 @@ private function getDefaultPurifier() $sanitizer = \HTMLPurifier_Config::createDefault(); $sanitizer->set('HTML.TargetBlank', true); - $sanitizer->set('URI.AllowedSchemes', ['http' => true, 'https' => true]); + $sanitizer->set('URI.AllowedSchemes', ['http' => true, 'https' => true, 'mailto' => true, 'tel' => true]); $sanitizer->set('AutoFormat.RemoveEmpty', true); $sanitizer->set('HTML.DefinitionID', 'html5-definitions'); From 2e15976a225305aeaecda3cf2f5341dc5795e1ae Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 28 Oct 2020 18:11:13 +0300 Subject: [PATCH 30/32] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 196b3f3..f3a5e01 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Sample validation rule set: }, "level": { "type": "int", - "canBeOnly: [2, 3, 4] + "canBeOnly": [2, 3, 4] } } } From 7e3a8dd4f065d4f7182aa0a4293fcad0feab721d Mon Sep 17 00:00:00 2001 From: John Puddephatt Date: Wed, 24 Aug 2022 21:25:27 +0100 Subject: [PATCH 31/32] Update EditorJS.php Pass tunes through to sanitizeBlock if present. --- EditorJS/EditorJS.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/EditorJS/EditorJS.php b/EditorJS/EditorJS.php index e0a54ee..0daaec8 100644 --- a/EditorJS/EditorJS.php +++ b/EditorJS/EditorJS.php @@ -106,7 +106,11 @@ public function getBlocks() $sanitizedBlocks = []; foreach ($this->blocks as $block) { - $sanitizedBlock = $this->handler->sanitizeBlock($block['type'], $block['data']); + $sanitizedBlock = $this->handler->sanitizeBlock( + $block['type'], + $block['data'], + $block["tunes"] ?? [] + ); if (!empty($sanitizedBlock)) { array_push($sanitizedBlocks, $sanitizedBlock); } From f2452cbde4bb0727f0884aa44b28468acf92cf98 Mon Sep 17 00:00:00 2001 From: John Puddephatt Date: Wed, 24 Aug 2022 21:26:39 +0100 Subject: [PATCH 32/32] Update BlockHandler.php Return block tunes as part of sanitised block data. --- EditorJS/BlockHandler.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/EditorJS/BlockHandler.php b/EditorJS/BlockHandler.php index 587907f..5a55151 100644 --- a/EditorJS/BlockHandler.php +++ b/EditorJS/BlockHandler.php @@ -65,13 +65,14 @@ public function validateBlock($blockType, $blockData) * * @return array|bool */ - public function sanitizeBlock($blockType, $blockData) + public function sanitizeBlock($blockType, $blockData, $blockTunes) { $rule = $this->rules->tools[$blockType]; return [ 'type' => $blockType, - 'data' => $this->sanitize($rule, $blockData) + 'data' => $this->sanitize($rule, $blockData), + 'tunes' => $blockTunes ]; }