├── .github
├── scripts
│ └── compile-templates.php
└── workflows
│ ├── formatting.yaml
│ ├── static-analysis.yml
│ ├── tests.yml
│ └── view-compiling.yaml
├── .gitignore
├── .php-cs-fixer.dist.php
├── LICENSE
├── README.md
├── art
└── banner.png
├── composer.json
├── config
└── editor.php
├── phpstan.neon.dist
├── phpunit.xml
├── resources
├── .gitkeep
├── php
│ ├── bootstrap-five
│ │ ├── attaches.php
│ │ ├── checklist.php
│ │ ├── code.php
│ │ ├── delimiter.php
│ │ ├── embed.php
│ │ ├── header.php
│ │ ├── image.php
│ │ ├── linktool.php
│ │ ├── list.php
│ │ ├── paragraph.php
│ │ ├── personality.php
│ │ ├── quote.php
│ │ ├── table.php
│ │ └── warning.php
│ └── tailwind
│ │ ├── attaches.php
│ │ ├── checklist.php
│ │ ├── code.php
│ │ ├── delimiter.php
│ │ ├── embed.php
│ │ ├── header.php
│ │ ├── image.php
│ │ ├── linktool.php
│ │ ├── list.php
│ │ ├── paragraph.php
│ │ ├── personality.php
│ │ ├── quote.php
│ │ ├── table.php
│ │ └── warning.php
└── views
│ ├── bootstrap-five
│ ├── attaches.blade.php
│ ├── checklist.blade.php
│ ├── code.blade.php
│ ├── delimiter.blade.php
│ ├── embed.blade.php
│ ├── header.blade.php
│ ├── image.blade.php
│ ├── linktool.blade.php
│ ├── list.blade.php
│ ├── paragraph.blade.php
│ ├── personality.blade.php
│ ├── quote.blade.php
│ ├── table.blade.php
│ └── warning.blade.php
│ └── tailwind
│ ├── attaches.blade.php
│ ├── checklist.blade.php
│ ├── code.blade.php
│ ├── delimiter.blade.php
│ ├── embed.blade.php
│ ├── header.blade.php
│ ├── image.blade.php
│ ├── linktool.blade.php
│ ├── list.blade.php
│ ├── paragraph.blade.php
│ ├── personality.blade.php
│ ├── quote.blade.php
│ ├── table.blade.php
│ └── warning.blade.php
├── src
├── Block
│ ├── Block.php
│ └── Data.php
├── Blocks
│ ├── Attaches.php
│ ├── Checklist.php
│ ├── Code.php
│ ├── Delimiter.php
│ ├── Embed.php
│ ├── Header.php
│ ├── Image.php
│ ├── LinkTool.php
│ ├── ListBlock.php
│ ├── Paragraph.php
│ ├── Personality.php
│ ├── Quote.php
│ ├── Raw.php
│ ├── Table.php
│ └── Warning.php
├── Casts
│ └── EditorPhpCast.php
├── Console
│ ├── BlockMakeCommand.php
│ └── stubs
│ │ └── block.stub
├── EditorPhp.php
├── EditorPhpServiceProvider.php
├── Exceptions
│ └── EditorPhpException.php
├── Helpers.php
├── Parser.php
└── Purifier.php
└── tests
├── Block
├── BlockTest.php
└── DataTest.php
├── Datasets
├── Models.php
├── Samples.php
└── samples
│ ├── broken.json
│ ├── unknownType.json
│ ├── unmatchingSchema.json
│ └── valid.json
├── EditorPhpTest.php
├── Laravel
├── Casts
│ └── EditorPhpCastTest.php
├── Console
│ └── BlockMakeCommandTest.php
├── EditorPhpTest.php
└── TestCase.php
├── ParserTest.php
├── Pest.php
└── PurifierTest.php
/.github/scripts/compile-templates.php:
--------------------------------------------------------------------------------
1 | ";
25 | }
26 |
27 | protected function compileEndforeach()
28 | {
29 | return '';
30 | }
31 | }
32 |
33 | $compiler = new Compiler(new Filesystem(), __DIR__, false);
34 |
35 | foreach (['bootstrap-five', 'tailwind'] as $framework)
36 | {
37 | foreach (scandir(__DIR__ . '/../../resources/views/' . $framework) as $template)
38 | {
39 | if (!str_ends_with($template, '.blade.php'))
40 | {
41 | continue;
42 | }
43 |
44 | $bladePath = __DIR__ . '/../../resources/views/' . $framework . '/' . $template;
45 | $compiledPath = __DIR__ . '/../../resources/php/' . $framework . '/' . str_replace('.blade.php', '.php', $template);
46 |
47 | file_put_contents(
48 | $compiledPath,
49 | $compiler->compileString(file_get_contents($bladePath))
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/.github/workflows/formatting.yaml:
--------------------------------------------------------------------------------
1 | name: Fix syling
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - "*.x"
8 | paths:
9 | - "**.php"
10 |
11 | pull_request:
12 | paths:
13 | - "**.php"
14 |
15 | permissions:
16 | contents: write
17 | jobs:
18 | php-cs-fixer:
19 | runs-on: ubuntu-latest
20 | steps:
21 | - name: Checkout code
22 | uses: actions/checkout@v3
23 | with:
24 | ref: ${{ github.head_ref }}
25 |
26 | - name: Install dependencies
27 | run: composer install
28 |
29 | - name: Execute php-cs-fixer
30 | run: ./vendor/bin/php-cs-fixer fix
31 |
32 | - uses: stefanzweifel/git-auto-commit-action@v4
33 | with:
34 | commit_message: Fix styling
35 |
--------------------------------------------------------------------------------
/.github/workflows/static-analysis.yml:
--------------------------------------------------------------------------------
1 | # Taken from `https://github.com/laravel/framework/blob/0b3fae2e8dd094433a760b09eb4366bb139fcf88/.github/workflows/static-analysis.yml
2 | name: static analysis
3 |
4 | on:
5 | push:
6 | branches:
7 | - master
8 | - '*.x'
9 | pull_request:
10 |
11 | jobs:
12 | run:
13 | runs-on: ubuntu-latest
14 | strategy:
15 | fail-fast: true
16 |
17 | steps:
18 | - name: Checkout code
19 | uses: actions/checkout@v3
20 |
21 | - name: Setup PHP
22 | uses: shivammathur/setup-php@v2
23 | with:
24 | php-version: 8.1
25 | tools: composer:v2
26 | coverage: none
27 |
28 | - name: Install dependencies
29 | uses: nick-fields/retry@v2
30 | with:
31 | timeout_minutes: 5
32 | max_attempts: 5
33 | command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress
34 |
35 | - name: Execute PHPStan
36 | run: vendor/bin/phpstan --configuration="phpstan.neon.dist"
37 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: "Tests"
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - '*.x'
8 | pull_request:
9 |
10 |
11 | jobs:
12 | run:
13 | runs-on: ubuntu-latest
14 | strategy:
15 | matrix:
16 | php: [ 8.1, 8.2, 8.3, 8.4 ]
17 | laravel: [ 10, 11, 12 ]
18 | exclude:
19 | - php: '8.1'
20 | laravel: 11
21 | - php: '8.1'
22 | laravel: 12
23 | - php: '8.4'
24 | laravel: 10
25 |
26 | name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }}
27 |
28 | steps:
29 | - name: Setup PHP
30 | uses: shivammathur/setup-php@v2
31 | with:
32 | php-version: ${{ matrix.php }}
33 |
34 | - name: Checkout code
35 | uses: actions/checkout@v3
36 |
37 | - name: Install dependencies
38 | run: |
39 | composer require "illuminate/support=^${{ matrix.laravel }}" --no-update
40 | composer update --prefer-dist --no-interaction --no-progress
41 |
42 | - name: Execute tests
43 | run: ./vendor/bin/pest
44 |
--------------------------------------------------------------------------------
/.github/workflows/view-compiling.yaml:
--------------------------------------------------------------------------------
1 | name: View Compiling
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - "*.x"
8 | paths:
9 | - "**.php"
10 |
11 | pull_request:
12 | paths:
13 | - "**.php"
14 |
15 | permissions:
16 | contents: write
17 | jobs:
18 | run:
19 | runs-on: ubuntu-latest
20 | steps:
21 | - name: Checkout code
22 | uses: actions/checkout@v3
23 | with:
24 | ref: ${{ github.head_ref }}
25 |
26 | - name: Install dependencies
27 | run: composer install
28 |
29 | - name: Execute script
30 | run: php .github/scripts/compile-templates.php
31 |
32 | - uses: stefanzweifel/git-auto-commit-action@v4
33 | with:
34 | commit_message: Compiling Templates
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /build/
3 | composer.lock
4 | .php-cs-fixer.cache
5 | .php-cs-fixer.php
6 | .phpunit.result.cache
7 | .phpunit.cache
8 |
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 | setRules([
13 | 'align_multiline_comment' => true,
14 | 'array_syntax' => true,
15 | 'blank_line_after_namespace' => true,
16 | 'blank_line_after_opening_tag' => true,
17 | 'blank_line_before_statement' => ['statements'=>['case', 'for', 'foreach', 'if', 'phpdoc', 'return', 'switch', 'throw', 'try', 'while']],
18 | 'braces' => ['position_after_anonymous_constructs'=>'next', 'position_after_control_structures'=>'next'],
19 | 'cast_spaces' => true,
20 | 'class_attributes_separation' => true,
21 | 'class_definition' => true,
22 | 'class_reference_name_casing' => true,
23 | 'clean_namespace' => true,
24 | 'combine_consecutive_issets' => true,
25 | 'combine_consecutive_unsets' => true,
26 | 'compact_nullable_typehint' => true,
27 | 'concat_space' => ['spacing'=>'one'],
28 | 'constant_case' => true,
29 | 'control_structure_braces' => true,
30 | 'control_structure_continuation_position' => ['position'=>'next_line'],
31 | 'curly_braces_position' => ['allow_single_line_anonymous_functions'=>false, 'allow_single_line_empty_anonymous_classes'=>false, 'anonymous_classes_opening_brace'=>'next_line_unless_newline_at_signature_end', 'anonymous_functions_opening_brace'=>'same_line', 'control_structures_opening_brace'=>'next_line_unless_newline_at_signature_end', 'functions_opening_brace'=>'next_line_unless_newline_at_signature_end'],
32 | 'declare_equal_normalize' => ['space'=>'single'],
33 | 'declare_parentheses' => true,
34 | 'echo_tag_syntax' => ['format'=>'short'],
35 | 'elseif' => true,
36 | 'encoding' => true,
37 | 'explicit_indirect_variable' => true,
38 | 'explicit_string_variable' => true,
39 | 'full_opening_tag' => true,
40 | 'fully_qualified_strict_types' => true,
41 | 'function_declaration' => ['closure_function_spacing'=>'none'],
42 | 'function_typehint_space' => true,
43 | 'heredoc_indentation' => true,
44 | 'include' => true,
45 | 'increment_style' => ['style'=>'post'],
46 | 'indentation_type' => true,
47 | 'lambda_not_used_import' => true,
48 | 'line_ending' => true,
49 | 'linebreak_after_opening_tag' => true,
50 | 'list_syntax' => true,
51 | 'lowercase_cast' => true,
52 | 'lowercase_keywords' => true,
53 | 'lowercase_static_reference' => true,
54 | 'magic_constant_casing' => true,
55 | 'magic_method_casing' => true,
56 | 'method_argument_space' => true,
57 | 'method_chaining_indentation' => true,
58 | 'multiline_comment_opening_closing' => true,
59 | 'multiline_whitespace_before_semicolons' => true,
60 | 'native_function_casing' => true,
61 | 'native_function_type_declaration_casing' => true,
62 | 'new_with_braces' => true,
63 | 'no_alias_language_construct_call' => true,
64 | 'no_blank_lines_after_class_opening' => true,
65 | 'no_blank_lines_after_phpdoc' => true,
66 | 'no_closing_tag' => true,
67 | 'no_empty_comment' => true,
68 | 'no_empty_phpdoc' => true,
69 | 'no_empty_statement' => true,
70 | 'no_leading_namespace_whitespace' => true,
71 | 'no_multiline_whitespace_around_double_arrow' => true,
72 | 'no_multiple_statements_per_line' => true,
73 | 'no_null_property_initialization' => true,
74 | 'no_short_bool_cast' => true,
75 | 'no_singleline_whitespace_before_semicolons' => true,
76 | 'no_space_around_double_colon' => true,
77 | 'no_spaces_after_function_name' => true,
78 | 'no_spaces_around_offset' => true,
79 | 'no_spaces_inside_parenthesis' => true,
80 | 'no_trailing_comma_in_singleline' => true,
81 | 'no_trailing_whitespace' => true,
82 | 'no_trailing_whitespace_in_comment' => true,
83 | 'no_unneeded_import_alias' => true,
84 | 'no_unused_imports' => true,
85 | 'no_useless_concat_operator' => true,
86 | 'no_useless_else' => true,
87 | 'no_useless_nullsafe_operator' => true,
88 | 'no_useless_return' => true,
89 | 'no_whitespace_before_comma_in_array' => true,
90 | 'no_whitespace_in_blank_line' => true,
91 | 'normalize_index_brace' => true,
92 | 'nullable_type_declaration_for_default_null_value' => true,
93 | 'object_operator_without_whitespace' => true,
94 | 'operator_linebreak' => true,
95 | 'ordered_imports' => true,
96 | 'phpdoc_align' => ['align'=>'left'],
97 | 'phpdoc_indent' => true,
98 | 'phpdoc_line_span' => true,
99 | 'phpdoc_no_access' => true,
100 | 'phpdoc_no_alias_tag' => true,
101 | 'phpdoc_order' => true,
102 | 'phpdoc_separation' => true,
103 | 'phpdoc_single_line_var_spacing' => true,
104 | 'phpdoc_summary' => true,
105 | 'phpdoc_tag_casing' => true,
106 | 'phpdoc_tag_type' => true,
107 | 'phpdoc_trim' => true,
108 | 'phpdoc_trim_consecutive_blank_line_separation' => true,
109 | 'phpdoc_types' => true,
110 | 'phpdoc_types_order' => ['null_adjustment'=>'always_last'],
111 | 'phpdoc_var_annotation_correct_order' => true,
112 | 'phpdoc_var_without_name' => true,
113 | 'return_type_declaration' => true,
114 | 'semicolon_after_instruction' => true,
115 | 'short_scalar_cast' => true,
116 | 'simple_to_complex_string_variable' => true,
117 | 'simplified_null_return' => true,
118 | 'single_blank_line_before_namespace' => true,
119 | 'single_class_element_per_statement' => true,
120 | 'single_import_per_statement' => true,
121 | 'single_line_after_imports' => true,
122 | 'single_line_comment_spacing' => true,
123 | 'single_line_throw' => true,
124 | 'single_space_after_construct' => ['constructs'=>['abstract', 'as', 'attribute', 'break', 'case', 'comment', 'echo', 'if', 'implements', 'namespace', 'new', 'private', 'protected', 'public', 'readonly', 'var']],
125 | 'space_after_semicolon' => true,
126 | 'standardize_increment' => true,
127 | 'standardize_not_equals' => true,
128 | 'statement_indentation' => true,
129 | 'switch_case_space' => true,
130 | 'switch_continue_to_break' => true,
131 | 'ternary_operator_spaces' => true,
132 | 'ternary_to_null_coalescing' => true,
133 | 'trailing_comma_in_multiline' => true,
134 | 'trim_array_spaces' => true,
135 | 'types_spaces' => true,
136 | 'unary_operator_spaces' => true,
137 | 'whitespace_after_comma_in_array' => ['ensure_single_space'=>false],
138 | ])
139 | ->setFinder(
140 | PhpCsFixer\Finder::create()
141 | ->exclude('vendor')
142 | ->in(__DIR__)
143 | );
144 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 BumpCore
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Editor.php
4 |
5 | Editor.php is a package designed to assist in parsing and manipulating the output of [Editor.js](https://editorjs.io/) with ease. It can be used with either vanilla PHP or with Larave. Laravel offers few additional features.
6 |
7 | ## Table Of Contents
8 |
9 | * [Version Table](#version-table)
10 | * [Quick Start](#quick-start)
11 | * [EditorPhp](#editorphp)
12 | + [Creating Instance](#creating-instance)
13 | + [Accessing Blocks](#accessing-blocks)
14 | + [Rendering HTML](#rendering-html)
15 | + [Faking](#faking)
16 | + [Additional](#additional)
17 | + [Converting to an array](#converting-to-an-array)
18 | + [Converting to JSON](#converting-to-json)
19 | + [Time & Version](#time--version)
20 | + [Macros](#macros)
21 | * [Blocks](#blocks)
22 | + [Registering Blocks](#registering-blocks)
23 | + [Extending Blocks](#extending-blocks)
24 | + [Creating Custom Blocks](#creating-custom-blocks)
25 | + [Accessing Block's Data](#accessing-blocks-data)
26 | + [Validating Block Data](#validating-block-data)
27 | + [Sanitizing Block Data](#sanitizing-block-data)
28 | + [Fake Data Generation](#fake-data-generation)
29 | * [Laravel Only Features](#laravel-only-features)
30 | + [Cast](#cast)
31 | + [Response](#response)
32 | + [Views](#views)
33 | + [Publishing Views and Configuration](#publishing-views-and-configuration)
34 | + [Commands](#commands)
35 | * [Contribution](#contribution)
36 |
37 | # Version Table
38 | | Editor.php | Laravel | PHP |
39 | | --- | --- | --- |
40 | | 1.x | 10.x | 8.1 ~ 8.3 |
41 | | 1.x | 11.x | 8.2 ~ 8.4 |
42 | | 1.x | 12.x | 8.2 ~ 8.4 |
43 |
44 | # Quick Start
45 |
46 | Install package by:
47 |
48 | ```bash
49 | composer require bumpcore/editor.php
50 | ```
51 |
52 | Editor.php is really simple to get started;
53 |
54 | ```php
55 | use BumpCore\EditorPhp\EditorPhp;
56 |
57 | // Passing Editor.js's output directly to the `make`.
58 | // This will render blocks into html.
59 | echo EditorPhp::make($json)->render();
60 | ```
61 |
62 | Editor.php supports following blocks;
63 |
64 | * [Attaches](https://github.com/editor-js/attaches)
65 | * [Checklist](https://github.com/editor-js/checklist)
66 | * [Code](https://github.com/editor-js/code)
67 | * [Delimiter](https://github.com/editor-js/delimiter)
68 | * [Embed](https://github.com/editor-js/embed)
69 | * [Header](https://github.com/editor-js/header)
70 | * [Image](https://github.com/editor-js/image)
71 | * [Link Tool](https://github.com/editor-js/link)
72 | * [List](https://github.com/editor-js/list)
73 | * [Paragraph](https://github.com/editor-js/paragraph)
74 | * [Personality](https://github.com/editor-js/personality)
75 | * [Quote](https://github.com/editor-js/quote)
76 | * [Table](https://github.com/editor-js/table)
77 | * [Warning](https://github.com/editor-js/warning)
78 |
79 | All of them have default validation rules and views to render. However, customizing validation and views is highly recommended.
80 |
81 | # EditorPhp
82 |
83 | The `EditorPhp` class is the main class for managing blocks. You can access, render, convert to an array, and convert to JSON through this class.
84 |
85 | ## Creating Instance
86 |
87 | There are two ways to create a new instance of EditorPhp:
88 |
89 | ```php
90 | use BumpCore\EditorPhp\EditorPhp;
91 |
92 | // Using the `new` syntax.
93 | $editor = new EditorPhp($json);
94 |
95 | // Using the `make` syntax.
96 | $editor = EditorPhp::make($json);
97 | ```
98 |
99 | Both syntaxes are equal, and there's almost no difference between them.
100 |
101 | ## Accessing Blocks
102 |
103 | You can access blocks through the blocks property.
104 |
105 | ```php
106 | use BumpCore\EditorPhp\EditorPhp;
107 | use BumpCore\EditorPhp\Block\Block;
108 | use BumpCore\EditorPhp\Blocks\Paragraph;
109 |
110 | $editor = EditorPhp::make($json);
111 |
112 | // Stripping all tags from paragraph block's text.
113 | $editor->blocks->transform(function(Block $block)
114 | {
115 | if($block instanceof Paragraph)
116 | {
117 | $block->set('text', strip_tags($block->get('text')));
118 | }
119 |
120 | return $block;
121 | });
122 | ```
123 |
124 | Blocks are stored as `Illuminate\Support\Collection` . By using collection methods, you can manipulate blocks as you wish. You can learn about collections in [Laravel's documentation](https://laravel.com/docs/10.x/collections).
125 |
126 | ## Rendering HTML
127 |
128 | Rendering HTML is very straightforward. There are multiple ways to render your instance:
129 |
130 | ```php
131 | use BumpCore\EditorPhp\EditorPhp;
132 |
133 | $editor = EditorPhp::make($json);
134 |
135 | // Using the `render` function.
136 | echo $editor->render();
137 |
138 | // Using the `toHtml` function.
139 | echo $editor->toHtml();
140 |
141 | // Or by casting to a string.
142 | echo $editor;
143 | ```
144 |
145 | Again, all three cases are the same, with no one above another. You can use whichever one you like the most.
146 |
147 | By the default, you have two options for the default block's templates; `tailwindcss` and `Bootstrap 5` . Default used template is `tailwindcss` You may switch templates by:
148 |
149 | ```php
150 | use BumpCore\EditorPhp\EditorPhp;
151 |
152 | // Using tailwind.
153 | EditorPhp::useTailwind();
154 |
155 | // Using Bootstrap.
156 | EditorPhp::useBootstrapFive();
157 | ```
158 |
159 | You can learn more about rendering in [creating custom blocks](#creating-custom-blocks) section.
160 |
161 | ## Faking
162 |
163 | You can generate fake data with `EditorPhp` .
164 |
165 | ```php
166 | use BumpCore\EditorPhp\EditorPhp;
167 |
168 | // This will return a generated fake JSON.
169 | $fake = EditorPhp::fake();
170 |
171 | // If we pass first argument true, it will return new `EditorPhp` instance with fake data.
172 | $fakeEditor = EditorPhp::fake(true);
173 |
174 | // You can also pass min lenght and max lenght of blocks.
175 | // Below code will generate blocks between 1 and 3.
176 | $fakeEditor = EditorPhp::fake(true, 1, 3);
177 |
178 | echo $fakeEditor->render();
179 | ```
180 |
181 | You can learn more about generating fake data for the blocks in [fake data generation](#fake-data-generation).
182 |
183 | ## Additional
184 |
185 | ### Converting to an array
186 |
187 | You can convert your instance to an array using the `toArray()` method.
188 |
189 | ```php
190 | use BumpCore\EditorPhp\EditorPhp;
191 |
192 | $editor = EditorPhp::make($json);
193 |
194 | // This will return ['time' => ..., 'blocks' => [...], 'version' => '...']
195 | $array = $editor->toArray();
196 | ```
197 |
198 | ### Converting to JSON
199 |
200 | You can convert your instance to JSON using the `toJson(/** options */)` method. This method is useful when you manipulate your instance.
201 |
202 | ```php
203 | use BumpCore\EditorPhp\EditorPhp;
204 |
205 | $editor = EditorPhp::make($json);
206 |
207 | // This will return encoded JSON.
208 | $json = $editor->toJson(JSON_PRETTY_PRINT);
209 | ```
210 |
211 | ### Time & Version
212 |
213 | You can access time and version:
214 |
215 | ```php
216 | use BumpCore\EditorPhp\EditorPhp;
217 |
218 | $editor = EditorPhp::make($json);
219 |
220 | $editor->time;
221 | $editor->version;
222 | ```
223 |
224 | The `time` property is a `Carbon` instance. You can learn more about it in [Carbon's documentation](https://carbon.nesbot.com/docs/).
225 |
226 | ### Macros
227 |
228 | You can register macros and use them later. Macros are based on Laravel.
229 |
230 | ```php
231 | use BumpCore\EditorPhp\EditorPhp;
232 |
233 | // Registering new macro.
234 | EditorPhp::macro(
235 | 'getParagraphs',
236 | fn () => $this->blocks->filter(fn (Block $block) => $block instanceof Paragraph)
237 | );
238 |
239 | $editor = EditorPhp::make($json);
240 |
241 | // This will return a collection that only contains paragraphs.
242 | $paragraphs = $editor->getParagraphs();
243 | ```
244 |
245 | # Blocks
246 |
247 | Blocks are the main building parts of the `EditorPhp` editor. You can manipulate them as you wish, and the best part is that you can use them to store your block's logic. For example, the [image](https://github.com/editor-js/image) block requires an uploader to work. You can implement the corresponding functionality in the `BumpCore\EditorPhp\Blocks\Image` class.
248 |
249 | ## Registering Blocks
250 |
251 | Before we jump into learning how to customize blocks, here's how you can register your blocks:
252 |
253 | ```php
254 | use BumpCore\EditorPhp\EditorPhp;
255 |
256 | // This will merge without erasing already registered blocks. Other blocks will still remain with the recently registered `image` and `paragraph` blocks.
257 | EditorPhp::register([
258 | 'image' => \Blocks\MyCustomImageBlock::class,
259 | 'paragraph' => \Blocks\MyCustomParagraphBlock::class,
260 | ]);
261 |
262 | // This will override already registered blocks. We now only have `image` and `paragraph` blocks.
263 | EditorPhp::register([
264 | 'image' => \Blocks\MyCustomImageBlock::class,
265 | 'paragraph' => \Blocks\MyCustomParagraphBlock::class,
266 | ], true);
267 | ```
268 |
269 | When registering blocks, it's important to use the correct key. The key must be the same as the `Editor.js` 's `type` key. To clarify:
270 |
271 | ```json
272 | {
273 | "time": 1672852569662,
274 | "blocks": [
275 | {
276 | "type": "paragraph",
277 | "data": {
278 | "text": "..."
279 | }
280 | }
281 | ],
282 | "version": "2.26.4"
283 | }
284 | ```
285 |
286 | In this output, our type key is `paragraph` , so we should register it as `'paragraph' => Paragraph::class` . This might vary depending on how you register your blocks in `Editor.js` . Default blocks in `EditorPhp` are registered using `camelCase` .
287 |
288 | ## Extending Blocks
289 |
290 | As mentioned previously, almost all blocks are supported in `EditorPhp` . However, they mostly handle the validation of block data and rendering. For the `Image` block to work properly, it requires an upload. We can implement this upload logic in the `Image` class:
291 |
292 | ```php
293 | use BumpCore\EditorPhp\Blocks\Image;
294 |
295 | class MyImageBlock extends Image
296 | {
297 | public static function uploadTemp(string $fileName = 'image'): array
298 | {
299 | // ...
300 |
301 | // Temporary upload logic.
302 |
303 | return [
304 | 'success' => ...,
305 | 'file' => [
306 | 'url' => ...,
307 | ],
308 | ];
309 | }
310 |
311 | public function upload(): void
312 | {
313 | $file = $this->get('file.url');
314 |
315 | // Your logic.
316 |
317 | // ...
318 |
319 | // Altering the current block's data.
320 | $this->set('file.url', ...);
321 | }
322 | }
323 |
324 | // ...
325 |
326 | // Registering customized block.
327 | EditorPhp::register([
328 | 'image' => MyImageBlock::class
329 | ]);
330 | ```
331 |
332 | As you can see, we have extended the `Image` block and added two functions to handle our uploads.
333 |
334 | The `uploadTemp` function performs a temporary file upload. This method is static and can be used anywhere using `Image::uploadTemp()` . It returns the data required by the [image](https://github.com/editor-js/image) tool.
335 |
336 | The `upload` function serves a different purpose. It represents the final upload for the block but is not static. This method assumes that the image has already been uploaded temporarily and the `$json` has been loaded and parsed. Therefore, we can use this function as follows:
337 |
338 | ```php
339 | use BumpCore\EditorPhp\EditorPhp;
340 | use Blocks\MyImageBlock;
341 |
342 | $editor = EditorPhp::make($json);
343 |
344 | $editor->blocks->each(function(Block $block)
345 | {
346 | if ($block instanceof MyImageBlock)
347 | {
348 | $block->upload();
349 | }
350 | });
351 |
352 | return $editor->toJson();
353 | ```
354 |
355 | Now the block performs the final upload and is saved as JSON.
356 |
357 | ## Creating Custom Blocks
358 |
359 | It is impossible to support all blocks out there, so we can implement our own blocks in an easy way. A standard block looks like the following:
360 |
361 | ```php
362 | use BumpCore\EditorPhp\Block\Block;
363 |
364 | class MyCustomBlock extends Block
365 | {
366 | public function render(): string
367 | {
368 | return view('blocks.my-custom-block', ['data' => $this->data]);
369 | }
370 | }
371 | ```
372 |
373 | As you can see, by default, we just need to implement the rendering logic. However, there's more than just rendering.
374 |
375 | ## Accessing Block's Data
376 |
377 | There are multiple ways to access a block's data. In the example below, you can see different methods for accessing block data:
378 |
379 | ```php
380 | public function render(): string
381 | {
382 | // ...
383 |
384 | // Method 1: Accessing through the data object.
385 | $data = $this->data;
386 | $data->get('custom.data');
387 | $data->set('custom.data', 'Hello World!');
388 |
389 | // Method 2: Accessing by invoking the data object.
390 | $data('custom.data'); // Hello World!
391 |
392 | // Method 3: Using shortcuts.
393 | $this->get('custom.data');
394 | $this->set('custom.data', 'Nice!');
395 |
396 | // ...
397 | }
398 | ```
399 |
400 | You can choose any of the above methods to access and manipulate the block's data. Additionally, you can also check whether the data exists or not using the following methods:
401 |
402 | ```php
403 | $data->has('custom.data');
404 | // or
405 | $this->has('custom.data');
406 | ```
407 |
408 | ## Validating Block Data
409 |
410 | Validating data is not required, but it can make your data safer. Validating block data is quite easy. We just have to add a `rules` method to our block:
411 |
412 | ```php
413 | use BumpCore\EditorPhp\Block\Block;
414 |
415 | class MyCustomBlock extends Block
416 | {
417 | // ...
418 |
419 | public function rules(): array
420 | {
421 | return [
422 | 'text' => 'required|string|max:255',
423 | 'items' => 'sometimes|array',
424 | 'items.*.name' => 'required|string|max:255',
425 | 'items.*.html' => 'required|string|min:255',
426 | ];
427 | }
428 |
429 | // ...
430 | }
431 | ```
432 |
433 | When validating the block's data fails, the data will be empty. Data validation is performed using Laravel's validation library. You can learn more about it in [Laravel's documentation](https://laravel.com/docs/10.x/validation).
434 |
435 | ## Sanitizing Block Data
436 |
437 | You can purify the HTML of your data if you wish. It's important to prevent injections. Purifying data looks much like validation:
438 |
439 | ```php
440 | use BumpCore\EditorPhp\Block\Block;
441 |
442 | class MyCustomBlock extends Block
443 | {
444 | // ...
445 |
446 | public function allow(): array|string
447 | {
448 | // Specifying one by one.
449 | return [
450 | 'text' => [
451 | 'a:href,target,title', // Will allow `a` tag and href, target, and title attributes.
452 | 'b', // Will only allow `b` tag with no attributes.
453 | ],
454 | 'items.*.name' => 'b:*', // Will allow `b` tag with all attributes.
455 | 'items.*.html' => '*', // Will allow every tag and every attribute.
456 | ];
457 |
458 | // Or just allowing all attributes and tags for all data.
459 | return '*';
460 | }
461 |
462 | // ...
463 | }
464 | ```
465 |
466 | Unlike validation, purifying will only strip unwanted tags and attributes.
467 |
468 | ## Fake Data Generation
469 |
470 | As we mentioned earlier, we can generate fake data with `EditorPhp` . But it requires to generate each block's own fake data. To generate fake data we should add static method to our block:
471 |
472 | ```php
473 | use BumpCore\EditorPhp\Block\Block;
474 |
475 | class MyCustomBlock extends Block
476 | {
477 | // ...
478 |
479 | public static function fake(\Faker\Generator $faker): array
480 | {
481 | $items = [];
482 |
483 | foreach (range(0, $faker->numberBetween(0, 10)) as $index)
484 | {
485 | $items[] = [
486 | 'name' => fake()->name(),
487 | 'html' => $faker->randomHtml(),
488 | ];
489 | }
490 |
491 | return [
492 | 'text' => fake()->text(255),
493 | 'items' => $items,
494 | ];
495 | }
496 |
497 | // ...
498 | }
499 | ```
500 |
501 | By adding `fake` method to our block, now `EditorPhp` will also include `MyCustomBlock` when generating fake data. You can learn more about at [FakerPHP's documentation](https://fakerphp.github.io/).
502 |
503 | # Laravel Only Features
504 |
505 | There's few Laravel features that will make your life little bit easier.
506 |
507 | ## Cast
508 |
509 | You can use `EditorPhpCast` to cast your model's attribute to `EditorPhp` instance.
510 |
511 | ```php
512 | use BumpCore\EditorPhp\Casts\EditorPhpCast;
513 | use Illuminate\Database\Eloquent\Model;
514 |
515 | class Post extends Model
516 | {
517 | protected $casts = [
518 | 'content' => EditorPhpCast::class,
519 | ];
520 | }
521 |
522 | // ...
523 |
524 | $post = Post::find(1);
525 |
526 | // Content is `EditorPhp` instance in here.
527 | echo $post->content->render();
528 | ```
529 |
530 | Also if you are using cast, you may access your model within block instances:
531 |
532 | ```php
533 | use BumpCore\EditorPhp\Block\Block;
534 | use App\Models\Post;
535 |
536 | class MyBlock extends Block
537 | {
538 | // ...
539 |
540 | public static function render(): string
541 | {
542 | if($this->root->model instanceof Post)
543 | {
544 | // Do the other thing.
545 | }
546 | }
547 |
548 | // ...
549 | }
550 | ```
551 |
552 | You can also alter the model from the block.
553 |
554 | ## Response
555 |
556 | `EditorPhp` instance can be returned as response. If request expects JSON it will encode it self to JSON. Otherwise it will be rendered into html.
557 |
558 | ```php
559 | namespace App\Http\Controllers;
560 |
561 | use App\Http\Controllers\Controller;
562 | use App\Models\Post;
563 |
564 | class ShowPostController extends Controller
565 | {
566 | public function __invoke(Post $post)
567 | {
568 | // Depending on the request it will return json or rendered html.
569 | return $post->content;
570 | }
571 | }
572 | ```
573 |
574 | ## Views
575 |
576 | You may also use `EditorPhp` instance to render inside view directly:
577 |
578 | ```blade
579 | {{-- blog.show.blade.php --}}
580 |
581 |
582 | {{ $post->title }}
583 | {{ $post->content }}
584 |
585 | ```
586 |
587 | ## Publishing Views and Configuration
588 |
589 | Got to check this before documenting it.
590 |
591 | ## Commands
592 |
593 | You can create brand new block with `block:make ` command:
594 |
595 | ```bash
596 | php artisan make:block CustomImageBlock
597 | ```
598 |
599 | New block will be placed under `app/Blocks` directory.
600 |
601 | # Contribution
602 |
603 | Contributions are welcome! If you find a bug or have a suggestion for improvement, please open an issue or create a pull request. Below are some guidelines to follow:
604 |
605 | * Fork the repository and clone it to your local machine.
606 | * Create a new branch for your contribution.
607 | * Make your changes and test them thoroughly.
608 | * Ensure that your code adheres to the existing coding style and conventions.
609 | * Commit your changes and push them to your forked repository.
610 | * Submit a pull request to the main repository.
611 |
612 | Please provide a detailed description of your changes and the problem they solve. Your contribution will be reviewed, and feedback may be provided. Thank you for your help in making this project better!
613 |
--------------------------------------------------------------------------------
/art/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bumpcore/editor.php/b0bba499037f12f4f68a71f3be65b75f037a72b6/art/banner.png
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bumpcore/editor.php",
3 | "description": "PHP integration for Editor.js",
4 | "type": "library",
5 | "license": "MIT",
6 | "autoload": {
7 | "psr-4": {
8 | "BumpCore\\EditorPhp\\": "src/"
9 | }
10 | },
11 | "autoload-dev": {
12 | "psr-4": {
13 | "BumpCore\\EditorPhp\\Tests\\": "tests/"
14 | }
15 | },
16 | "authors": [
17 | {
18 | "name": "Abdulkadir Cemiloğlu",
19 | "email": "kadir.cemiloglu1@gmail.com",
20 | "role": "Developer"
21 | }
22 | ],
23 | "require": {
24 | "php": "^8.1",
25 | "illuminate/support": "^10.0|^11.0|^12.0",
26 | "illuminate/validation": "^10.0|^11.0|^12.0",
27 | "fakerphp/faker": "^1.21"
28 | },
29 | "extra": {
30 | "laravel": {
31 | "providers": [
32 | "BumpCore\\EditorPhp\\EditorPhpServiceProvider"
33 | ]
34 | }
35 | },
36 | "require-dev": {
37 | "pestphp/pest": "^1.22.4|^2.0|^3.0",
38 | "orchestra/testbench": "^8.0|^9.0|^10.0",
39 | "friendsofphp/php-cs-fixer": "^3.14.4",
40 | "phpstan/phpstan": "^1.10"
41 | },
42 | "config": {
43 | "allow-plugins": {
44 | "pestphp/pest-plugin": true
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/config/editor.php:
--------------------------------------------------------------------------------
1 | [
5 | 'attaches' => BumpCore\EditorPhp\Blocks\Attaches::class,
6 | 'checklist' => BumpCore\EditorPhp\Blocks\Checklist::class,
7 | 'code' => BumpCore\EditorPhp\Blocks\Code::class,
8 | 'delimiter' => BumpCore\EditorPhp\Blocks\Delimiter::class,
9 | 'embed' => BumpCore\EditorPhp\Blocks\Embed::class,
10 | 'header' => BumpCore\EditorPhp\Blocks\Header::class,
11 | 'image' => BumpCore\EditorPhp\Blocks\Image::class,
12 | 'linkTool' => BumpCore\EditorPhp\Blocks\LinkTool::class,
13 | 'list' => BumpCore\EditorPhp\Blocks\ListBlock::class,
14 | 'paragraph' => BumpCore\EditorPhp\Blocks\Paragraph::class,
15 | 'personality' => BumpCore\EditorPhp\Blocks\Personality::class,
16 | 'quote' => BumpCore\EditorPhp\Blocks\Quote::class,
17 | 'raw' => BumpCore\EditorPhp\Blocks\Raw::class,
18 | 'table' => BumpCore\EditorPhp\Blocks\Table::class,
19 | 'warning' => BumpCore\EditorPhp\Blocks\Warning::class,
20 | ],
21 | ];
22 |
--------------------------------------------------------------------------------
/phpstan.neon.dist:
--------------------------------------------------------------------------------
1 | parameters:
2 | paths:
3 | - config
4 | - resources
5 | - src
6 | - tests
7 | level: 4
8 | ignoreErrors:
9 | - "#Unsafe usage of new static#"
10 | - "#Class BumpCore\\\\EditorPhp\\\\EditorPhp has an uninitialized readonly property \\$model. Assign it in the constructor.#"
11 | - "#Readonly property BumpCore\\\\EditorPhp\\\\EditorPhp::\\$time is already assigned.#"
12 | - "#Readonly property BumpCore\\\\EditorPhp\\\\EditorPhp::\\$model is assigned outside of the constructor.#"
13 | - "#Undefined variable: \\$this#"
14 | - "#Call to an undefined method BumpCore\\\\EditorPhp\\\\EditorPhp::getParagraphs\\(\\).#"
15 | - "#Variable \\$data might not be defined.#"
16 | - "#Unreachable statement - code above always terminates.#"
17 | reportUnmatchedIgnoredErrors: false
18 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | ./tests
10 |
11 |
12 |
13 |
14 | ./app
15 | ./src
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/resources/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bumpcore/editor.php/b0bba499037f12f4f68a71f3be65b75f037a72b6/resources/.gitkeep
--------------------------------------------------------------------------------
/resources/php/bootstrap-five/attaches.php:
--------------------------------------------------------------------------------
1 |
2 |
= $data('file.extension'); ?>
3 |
32 |
33 |
--------------------------------------------------------------------------------
/resources/php/bootstrap-five/checklist.php:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
15 |
18 |
19 |
20 |
21 |
22 | = $item['text']; ?>
23 |
24 | = $item['text']; ?>
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/resources/php/bootstrap-five/code.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
= $data('code'); ?>
4 |
5 |
6 |
--------------------------------------------------------------------------------
/resources/php/bootstrap-five/delimiter.php:
--------------------------------------------------------------------------------
1 | ***
2 |
--------------------------------------------------------------------------------
/resources/php/bootstrap-five/embed.php:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 | = $data('caption'); ?>
12 |
13 |
14 |
15 |
16 |
17 | = $data('source'); ?>
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/resources/php/bootstrap-five/header.php:
--------------------------------------------------------------------------------
1 |
3 | = $data('text'); ?>
4 |
5 |
6 |
7 | = $data('text'); ?>
8 |
9 |
10 |
11 | = $data('text'); ?>
12 |
13 |
14 |
15 | = $data('text'); ?>
16 |
17 |
18 |
19 | = $data('text'); ?>
20 |
21 |
22 |
23 | = $data('text'); ?>
24 |
25 |
26 |
--------------------------------------------------------------------------------
/resources/php/bootstrap-five/image.php:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/resources/php/bootstrap-five/linktool.php:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
= $data('meta.title'); ?>
10 |
11 |
12 | = $data('link'); ?>
17 |
18 |
19 |
= $data('meta.description'); ?>
20 |
21 |
22 |
--------------------------------------------------------------------------------
/resources/php/bootstrap-five/list.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | = $item; ?>
5 |
6 |
7 |
8 |
9 |
10 | = $item; ?>
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/resources/php/bootstrap-five/paragraph.php:
--------------------------------------------------------------------------------
1 | = $data('text'); ?>
2 |
--------------------------------------------------------------------------------
/resources/php/bootstrap-five/personality.php:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
= $data('name'); ?>
10 |
11 |
12 | = $data('link'); ?>
17 |
18 |
19 |
= $data('description'); ?>
20 |
21 |
22 |
--------------------------------------------------------------------------------
/resources/php/bootstrap-five/quote.php:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
= $data('text'); ?>
17 |
= $data('caption'); ?>
18 |
19 |
20 |
--------------------------------------------------------------------------------
/resources/php/bootstrap-five/table.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | = $heading; ?>
8 |
9 |
10 |
11 |
12 |
13 |
14 | $row): ?>
15 |
16 |
17 |
18 |
19 |
20 |
21 | = $cell; ?>
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/resources/php/bootstrap-five/warning.php:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 |
= $data('title'); ?>
21 |
= $data('message'); ?>
22 |
23 |
24 |
--------------------------------------------------------------------------------
/resources/php/tailwind/attaches.php:
--------------------------------------------------------------------------------
1 |
2 |
= $data('file.extension'); ?>
3 |
32 |
33 |
--------------------------------------------------------------------------------
/resources/php/tailwind/checklist.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
16 |
17 |
18 |
19 |
20 | = $item['text']; ?>
21 |
22 | = $item['text']; ?>
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/resources/php/tailwind/code.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
= $data('code'); ?>
4 |
5 |
6 |
--------------------------------------------------------------------------------
/resources/php/tailwind/delimiter.php:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/php/tailwind/embed.php:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 | = $data('caption'); ?>
12 |
13 |
14 |
15 |
16 |
17 | = $data('source'); ?>
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/resources/php/tailwind/header.php:
--------------------------------------------------------------------------------
1 |
3 | = $data('text'); ?>
4 |
5 |
6 |
7 | = $data('text'); ?>
8 |
9 |
10 |
11 | = $data('text'); ?>
12 |
13 |
14 |
15 | = $data('text'); ?>
16 |
17 |
18 |
19 | = $data('text'); ?>
20 |
21 |
22 |
23 | = $data('text'); ?>
24 |
25 |
26 |
--------------------------------------------------------------------------------
/resources/php/tailwind/image.php:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/resources/php/tailwind/linktool.php:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
= $data('meta.title'); ?>
9 |
10 |
11 | = $data('link'); ?>
16 |
17 |
18 |
= $data('meta.description'); ?>
19 |
20 |
21 |
--------------------------------------------------------------------------------
/resources/php/tailwind/list.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | = $item; ?>
5 |
6 |
7 |
8 |
9 |
10 | = $item; ?>
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/resources/php/tailwind/paragraph.php:
--------------------------------------------------------------------------------
1 | = $data('text'); ?>
2 |
--------------------------------------------------------------------------------
/resources/php/tailwind/personality.php:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
= $data('name'); ?>
9 |
10 |
11 | = $data('link'); ?>
16 |
17 |
18 |
= $data('description'); ?>
19 |
20 |
21 |
--------------------------------------------------------------------------------
/resources/php/tailwind/quote.php:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
= $data('text'); ?>
15 |
= $data('caption'); ?>
16 |
17 |
18 |
--------------------------------------------------------------------------------
/resources/php/tailwind/table.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | = $heading; ?>
8 |
9 |
10 |
11 |
12 |
13 |
14 | $row): ?>
15 |
16 |
17 |
18 |
19 |
20 |
21 | = $cell; ?>
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/resources/php/tailwind/warning.php:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
= $data('title'); ?>
17 |
= $data('message'); ?>
18 |
19 |
20 |
--------------------------------------------------------------------------------
/resources/views/bootstrap-five/attaches.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
{!! $data('file.extension') !!}
3 |
32 |
33 |
--------------------------------------------------------------------------------
/resources/views/bootstrap-five/checklist.blade.php:
--------------------------------------------------------------------------------
1 |
5 | @foreach ($data('items', []) as $item)
6 |
7 |
8 |
15 |
18 |
19 |
20 |
21 | @if ($item['checked'])
22 | {!! $item['text'] !!}
23 | @else
24 | {!! $item['text'] !!}
25 | @endif
26 |
27 | @endforeach
28 |
29 |
--------------------------------------------------------------------------------
/resources/views/bootstrap-five/code.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{!! $data('code') !!}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/resources/views/bootstrap-five/delimiter.blade.php:
--------------------------------------------------------------------------------
1 | ***
2 |
--------------------------------------------------------------------------------
/resources/views/bootstrap-five/embed.blade.php:
--------------------------------------------------------------------------------
1 |
25 |
--------------------------------------------------------------------------------
/resources/views/bootstrap-five/header.blade.php:
--------------------------------------------------------------------------------
1 | @switch($data('level'))
2 | @case(1)
3 | {!! $data('text') !!}
4 | @break
5 |
6 | @case(2)
7 | {!! $data('text') !!}
8 | @break
9 |
10 | @case(3)
11 | {!! $data('text') !!}
12 | @break
13 |
14 | @case(4)
15 | {!! $data('text') !!}
16 | @break
17 |
18 | @case(5)
19 | {!! $data('text') !!}
20 | @break
21 |
22 | @case(6)
23 | {!! $data('text') !!}
24 | @break
25 | @endswitch
26 |
--------------------------------------------------------------------------------
/resources/views/bootstrap-five/image.blade.php:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/resources/views/bootstrap-five/linktool.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
{!! $data('meta.title') !!}
10 |
11 |
12 | {!! $data('link') !!}
17 |
18 |
19 |
{!! $data('meta.description') !!}
20 |
21 |
22 |
--------------------------------------------------------------------------------
/resources/views/bootstrap-five/list.blade.php:
--------------------------------------------------------------------------------
1 | @if ($data('style') === 'ordered')
2 |
3 | @foreach ($data('items', []) as $item)
4 | {!! $item !!}
5 | @endforeach
6 |
7 | @else
8 |
9 | @foreach ($data('items', []) as $item)
10 | {!! $item !!}
11 | @endforeach
12 |
13 | @endif
14 |
--------------------------------------------------------------------------------
/resources/views/bootstrap-five/paragraph.blade.php:
--------------------------------------------------------------------------------
1 | {!! $data('text') !!}
2 |
--------------------------------------------------------------------------------
/resources/views/bootstrap-five/personality.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
{!! $data('name') !!}
10 |
11 |
12 | {!! $data('link') !!}
17 |
18 |
19 |
{!! $data('description') !!}
20 |
21 |
22 |
--------------------------------------------------------------------------------
/resources/views/bootstrap-five/quote.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
{!! $data('text') !!}
17 |
{!! $data('caption') !!}
18 |
19 |
20 |
--------------------------------------------------------------------------------
/resources/views/bootstrap-five/table.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | @if ($data('withHeadings') && ($headings = $data('content')[array_key_first($data('content'))]))
4 |
5 |
6 | @foreach ($headings as $heading)
7 | {!! $heading !!}
8 | @endforeach
9 |
10 |
11 | @endif
12 |
13 |
14 | @foreach ($data('content', []) as $index => $row)
15 | @if ($data('withHeadings') && array_key_first($data('content')) === $index)
16 | @continue
17 | @endif
18 |
19 |
20 | @foreach ($row as $cell)
21 | {!! $cell !!}
22 | @endforeach
23 |
24 | @endforeach
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/resources/views/bootstrap-five/warning.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 |
{!! $data('title') !!}
21 |
{!! $data('message') !!}
22 |
23 |
24 |
--------------------------------------------------------------------------------
/resources/views/tailwind/attaches.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
{!! $data('file.extension') !!}
3 |
32 |
33 |
--------------------------------------------------------------------------------
/resources/views/tailwind/checklist.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @foreach ($data('items', []) as $item)
3 |
4 |
5 |
13 |
16 |
17 |
18 |
19 | @if ($item['checked'])
20 | {!! $item['text'] !!}
21 | @else
22 | {!! $item['text'] !!}
23 | @endif
24 |
25 | @endforeach
26 |
27 |
--------------------------------------------------------------------------------
/resources/views/tailwind/code.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{!! $data('code') !!}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/resources/views/tailwind/delimiter.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/views/tailwind/embed.blade.php:
--------------------------------------------------------------------------------
1 |
25 |
--------------------------------------------------------------------------------
/resources/views/tailwind/header.blade.php:
--------------------------------------------------------------------------------
1 | @switch($data('level'))
2 | @case(1)
3 | {!! $data('text') !!}
4 | @break
5 |
6 | @case(2)
7 | {!! $data('text') !!}
8 | @break
9 |
10 | @case(3)
11 | {!! $data('text') !!}
12 | @break
13 |
14 | @case(4)
15 | {!! $data('text') !!}
16 | @break
17 |
18 | @case(5)
19 | {!! $data('text') !!}
20 | @break
21 |
22 | @case(6)
23 | {!! $data('text') !!}
24 | @break
25 | @endswitch
26 |
--------------------------------------------------------------------------------
/resources/views/tailwind/image.blade.php:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/resources/views/tailwind/linktool.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
{!! $data('meta.title') !!}
9 |
10 |
11 | {!! $data('link') !!}
16 |
17 |
18 |
{!! $data('meta.description') !!}
19 |
20 |
21 |
--------------------------------------------------------------------------------
/resources/views/tailwind/list.blade.php:
--------------------------------------------------------------------------------
1 | @if ($data('style') === 'ordered')
2 |
3 | @foreach ($data('items', []) as $item)
4 | {!! $item !!}
5 | @endforeach
6 |
7 | @else
8 |
9 | @foreach ($data('items', []) as $item)
10 | {!! $item !!}
11 | @endforeach
12 |
13 | @endif
14 |
--------------------------------------------------------------------------------
/resources/views/tailwind/paragraph.blade.php:
--------------------------------------------------------------------------------
1 | {!! $data('text') !!}
2 |
--------------------------------------------------------------------------------
/resources/views/tailwind/personality.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
{!! $data('name') !!}
9 |
10 |
11 | {!! $data('link') !!}
16 |
17 |
18 |
{!! $data('description') !!}
19 |
20 |
21 |
--------------------------------------------------------------------------------
/resources/views/tailwind/quote.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
{!! $data('text') !!}
15 |
{!! $data('caption') !!}
16 |
17 |
18 |
--------------------------------------------------------------------------------
/resources/views/tailwind/table.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | @if ($data('withHeadings') && ($headings = $data('content')[array_key_first($data('content'))]))
4 |
5 |
6 | @foreach ($headings as $heading)
7 | {!! $heading !!}
8 | @endforeach
9 |
10 |
11 | @endif
12 |
13 |
14 | @foreach ($data('content', []) as $index => $row)
15 | @if ($data('withHeadings') && array_key_first($data('content')) === $index)
16 | @continue
17 | @endif
18 |
19 |
20 | @foreach ($row as $cell)
21 | {!! $cell !!}
22 | @endforeach
23 |
24 | @endforeach
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/resources/views/tailwind/warning.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
{!! $data('title') !!}
17 |
{!! $data('message') !!}
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/Block/Block.php:
--------------------------------------------------------------------------------
1 | type = array_flip(Parser::$blocks)[static::class];
65 | $this->root = $root;
66 | $this->data = new Data($data, $this->allows(), $this->rules());
67 | }
68 |
69 | /**
70 | * Tag allow list for purifying data.
71 | *
72 | * @return array|string
73 | */
74 | public function allows(): array|string
75 | {
76 | return '*';
77 | }
78 |
79 | /**
80 | * Rules to validate data of the block.
81 | *
82 | * @return array
83 | */
84 | public function rules(): array
85 | {
86 | return [];
87 | }
88 |
89 | /**
90 | * Gets block data by given key.
91 | *
92 | * @param string $key
93 | * @param mixed $default
94 | *
95 | * @return mixed
96 | */
97 | public function get(string $key, mixed $default = null): mixed
98 | {
99 | return $this->data->get($key, $default);
100 | }
101 |
102 | /**
103 | * Sets block data by given key.
104 | *
105 | * @param string $key
106 | * @param mixed $value
107 | *
108 | * @return $this
109 | */
110 | public function set(string $key, mixed $value): self
111 | {
112 | $this->data->set($key, $value);
113 |
114 | return $this;
115 | }
116 |
117 | /**
118 | * Check if an item or items exist in the data.
119 | *
120 | * @param array|string $key
121 | *
122 | * @return bool
123 | */
124 | public function has(string|array $key): bool
125 | {
126 | return $this->data->has($key);
127 | }
128 |
129 | /**
130 | * Converts the `Block` as an array.
131 | *
132 | * @return array
133 | */
134 | public function toArray(): array
135 | {
136 | return [
137 | 'type' => $this->type,
138 | 'data' => $this->data->toArray(),
139 | ];
140 | }
141 |
142 | /**
143 | * Renders block into HTML.
144 | *
145 | * @return string
146 | */
147 | public function toHtml()
148 | {
149 | return $this->render();
150 | }
151 |
152 | /**
153 | * Renders block into HTML.
154 | *
155 | * @return string
156 | */
157 | public function __toString(): string
158 | {
159 | return $this->render();
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/Block/Data.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | protected array $data;
18 |
19 | /**
20 | * Constructor.
21 | *
22 | * @param array $data
23 | * @param array|string $allows
24 | * @param array $rules
25 | *
26 | * @return void
27 | */
28 | public function __construct(array $data = [], array|string $allows = '*', array $rules = [])
29 | {
30 | $this->data = $this->purify(
31 | $this->validate($data, $rules),
32 | $allows,
33 | );
34 | }
35 |
36 | /**
37 | * Gets data by given key.
38 | *
39 | * @param string $key
40 | * @param mixed $default
41 | *
42 | * @return mixed
43 | */
44 | public function __invoke(string $key, mixed $default = null): mixed
45 | {
46 | return $this->get($key, $default);
47 | }
48 |
49 | /**
50 | * Gets data by given key.
51 | *
52 | * @param string $key
53 | * @param mixed $default
54 | *
55 | * @return mixed
56 | */
57 | public function get(string $key, mixed $default = null): mixed
58 | {
59 | return Arr::get($this->data, $key, $default);
60 | }
61 |
62 | /**
63 | * Sets a data by given key.
64 | *
65 | * @param string $key
66 | * @param mixed $value
67 | *
68 | * @return void
69 | */
70 | public function set(string $key, mixed $value): void
71 | {
72 | Arr::set($this->data, $key, $value);
73 | }
74 |
75 | /**
76 | * Check if an item or items exist in the data.
77 | *
78 | * @param array|string $key
79 | *
80 | * @return bool
81 | */
82 | public function has(string|array $key): bool
83 | {
84 | return Arr::has($this->data, $key);
85 | }
86 |
87 | /**
88 | * Validates raw data.
89 | *
90 | * @return array
91 | */
92 | protected function validate(array $data, array $rules): array
93 | {
94 | $validator = Helpers::makeValidator($data, $rules);
95 |
96 | if ($validator->fails())
97 | {
98 | return [];
99 | }
100 |
101 | return $validator->validated();
102 | }
103 |
104 | /**
105 | * Purifies validated data.
106 | *
107 | * @param array $data
108 | * @param array|string $allows
109 | *
110 | * @return array
111 | */
112 | protected function purify(array $data, array|string $allows)
113 | {
114 | // Allows all tags and attributes for all fields.
115 | if ($allows === '*')
116 | {
117 | return $data;
118 | }
119 |
120 | $allows = $this->parseAllows($allows);
121 | $data = Arr::dot($data);
122 |
123 | foreach ($data as $key => $value)
124 | {
125 | if (!is_string($value))
126 | {
127 | continue;
128 | }
129 |
130 | // Find matching key E.g. `content*.*.text` = `content.1.5.text`
131 | $allow = Arr::first(Arr::where(
132 | $allows,
133 | // Below regex taken from `https://github.com/laravel/framework/blob/46ac3ec77ed4b07e3c6e47f97979822696bb7f1d/src/Illuminate/Validation/ValidationData.php#L57`
134 | fn ($tags, $attribute) => (bool) preg_match('/^' . str_replace('\*', '[^\.]+', preg_quote($attribute)) . '/', $key, $matches)
135 | ));
136 |
137 | if ($allow)
138 | {
139 | // Allows all tags and attributes for field.
140 | if ($allow === '*')
141 | {
142 | continue;
143 | }
144 |
145 | $tags = array_keys($allow);
146 |
147 | $purified = Purifier::stripTags($value, $tags);
148 |
149 | foreach ($tags as $tag)
150 | {
151 | $attributes = $allow[$tag];
152 |
153 | // Allows all attributes.
154 | if (Arr::first($attributes) === '*')
155 | {
156 | continue;
157 | }
158 |
159 | $purified = Purifier::stripAttributes($purified, $tag, $attributes);
160 | }
161 |
162 | $data[$key] = $purified;
163 | }
164 | }
165 |
166 | return Arr::undot($data);
167 | }
168 |
169 | /**
170 | * Parses following syntax: `a:href,class,title`.
171 | *
172 | * @param array $allows
173 | *
174 | * @return array
175 | */
176 | protected function parseAllows(array $allows): array
177 | {
178 | return Arr::map($allows, function(array|string $tags) {
179 | if ($tags === '*')
180 | {
181 | return $tags;
182 | }
183 |
184 | $tags = Arr::wrap($tags);
185 |
186 | $parsed = [];
187 |
188 | foreach ($tags as $allow)
189 | {
190 | $allow = explode(':', $allow);
191 |
192 | // First key is tag.
193 | $tag = Arr::first(array_slice($allow, 0, 1));
194 |
195 | // Second key is attributes.
196 | $attributes = array_filter(explode(
197 | ',',
198 | Arr::first(array_slice($allow, 1, 2), null, '')
199 | ));
200 |
201 | $parsed[$tag] = $attributes;
202 | }
203 |
204 | return $parsed;
205 | });
206 | }
207 |
208 | /**
209 | * Converts the `Data` as an array.
210 | *
211 | * @return array
212 | */
213 | public function toArray(): array
214 | {
215 | return $this->data;
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/src/Blocks/Attaches.php:
--------------------------------------------------------------------------------
1 | [],
21 | 'file.url' => [],
22 | 'file.name' => [],
23 | 'file.extension' => [],
24 | ];
25 | }
26 |
27 | /**
28 | * Rules to validate data of the block.
29 | *
30 | * @return array
31 | */
32 | public function rules(): array
33 | {
34 | return [
35 | 'title' => 'string',
36 | 'file.url' => 'url',
37 | 'file.size' => 'numeric',
38 | 'file.name' => 'string',
39 | 'file.extension' => 'string',
40 | ];
41 | }
42 |
43 | /**
44 | * Renderer for the block.
45 | *
46 | * @return string
47 | */
48 | public function render(): string
49 | {
50 | if (View::getFacadeRoot())
51 | {
52 | return view(sprintf('editor.php::%s.attaches', EditorPhp::usingTemplate()))
53 | ->with(['data' => $this->data])
54 | ->render();
55 | }
56 |
57 | return Helpers::renderNative(__DIR__ . sprintf('/../../resources/php/%s/attaches.php', EditorPhp::usingTemplate()), ['data' => $this->data]);
58 | }
59 |
60 | /**
61 | * Generates fake data for the block.
62 | *
63 | * @param \Faker\Generator $faker
64 | *
65 | * @return array
66 | */
67 | public static function fake(\Faker\Generator $faker): array
68 | {
69 | return [
70 | 'title' => $faker->text(64),
71 | 'file' => [
72 | 'url' => $faker->url(),
73 | 'size' => $faker->numberBetween(250000, 10000000),
74 | 'name' => $faker->text(64),
75 | 'extension' => $faker->fileExtension(),
76 | ],
77 | ];
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Blocks/Checklist.php:
--------------------------------------------------------------------------------
1 | [],
21 | ];
22 | }
23 |
24 | /**
25 | * Rules to validate data of the block.
26 | *
27 | * @return array
28 | */
29 | public function rules(): array
30 | {
31 | return [
32 | 'items' => 'array',
33 | 'items.*' => 'array',
34 | 'items.*.text' => 'string',
35 | 'items.*.checked' => 'boolean',
36 | ];
37 | }
38 |
39 | /**
40 | * Renderer for the block.
41 | *
42 | * @return string
43 | */
44 | public function render(): string
45 | {
46 | if (View::getFacadeRoot())
47 | {
48 | return view(sprintf('editor.php::%s.checklist', EditorPhp::usingTemplate()))
49 | ->with(['data' => $this->data])
50 | ->render();
51 | }
52 |
53 | return Helpers::renderNative(__DIR__ . sprintf('/../../resources/php/%s/checklist.php', EditorPhp::usingTemplate()), ['data' => $this->data]);
54 | }
55 |
56 | /**
57 | * Generates fake data for the block.
58 | *
59 | * @param \Faker\Generator $faker
60 | *
61 | * @return array
62 | */
63 | public static function fake(\Faker\Generator $faker): array
64 | {
65 | $items = [];
66 |
67 | foreach (range(0, $faker->numberBetween(1, 10)) as $index)
68 | {
69 | $items[] = [
70 | 'text' => $faker->text(48),
71 | 'checked' => $faker->boolean(),
72 | ];
73 | }
74 |
75 | return ['items' => $items];
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Blocks/Code.php:
--------------------------------------------------------------------------------
1 | '*',
21 | ];
22 | }
23 |
24 | /**
25 | * Rules to validate data of the block.
26 | *
27 | * @return array
28 | */
29 | public function rules(): array
30 | {
31 | return [
32 | 'code' => 'string',
33 | ];
34 | }
35 |
36 | /**
37 | * Renderer for the block.
38 | *
39 | * @return string
40 | */
41 | public function render(): string
42 | {
43 | if (View::getFacadeRoot())
44 | {
45 | return view(sprintf('editor.php::%s.code', EditorPhp::usingTemplate()))
46 | ->with(['data' => $this->data])
47 | ->render();
48 | }
49 |
50 | return Helpers::renderNative(__DIR__ . sprintf('/../../resources/php/%s/code.php', EditorPhp::usingTemplate()), ['data' => $this->data]);
51 | }
52 |
53 | /**
54 | * Generates fake data for the block.
55 | *
56 | * @param \Faker\Generator $faker
57 | *
58 | * @return array
59 | */
60 | public static function fake(\Faker\Generator $faker): array
61 | {
62 | return ['code' => $faker->text()];
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Blocks/Delimiter.php:
--------------------------------------------------------------------------------
1 | with(['data' => $this->data])
43 | ->render();
44 | }
45 |
46 | return Helpers::renderNative(__DIR__ . sprintf('/../../resources/php/%s/delimiter.php', EditorPhp::usingTemplate()), ['data' => $this->data]);
47 | }
48 |
49 | /**
50 | * Generates fake data for the block.
51 | *
52 | * @param \Faker\Generator $faker
53 | *
54 | * @return array
55 | */
56 | public static function fake(\Faker\Generator $faker): array
57 | {
58 | return [];
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Blocks/Embed.php:
--------------------------------------------------------------------------------
1 | [],
21 | 'source' => [],
22 | 'embed' => [],
23 | 'caption' => [],
24 | ];
25 | }
26 |
27 | /**
28 | * Rules to validate data of the block.
29 | *
30 | * @return array
31 | */
32 | public function rules(): array
33 | {
34 | return [
35 | 'service' => 'string',
36 | 'source' => 'url',
37 | 'embed' => 'url',
38 | 'width' => 'numeric',
39 | 'height' => 'numeric',
40 | 'caption' => 'string',
41 | ];
42 | }
43 |
44 | /**
45 | * Renderer for the block.
46 | *
47 | * @return string
48 | */
49 | public function render(): string
50 | {
51 | if (View::getFacadeRoot())
52 | {
53 | return view(sprintf('editor.php::%s.embed', EditorPhp::usingTemplate()))
54 | ->with(['data' => $this->data])
55 | ->render();
56 | }
57 |
58 | return Helpers::renderNative(__DIR__ . sprintf('/../../resources/php/%s/embed.php', EditorPhp::usingTemplate()), ['data' => $this->data]);
59 | }
60 |
61 | /**
62 | * Generates fake data for the block.
63 | *
64 | * @param \Faker\Generator $faker
65 | *
66 | * @return array
67 | */
68 | public static function fake(\Faker\Generator $faker): array
69 | {
70 | return [
71 | 'service' => $faker->text(32),
72 | 'source' => $faker->url(),
73 | 'embed' => $faker->url(),
74 | 'width' => $faker->numberBetween(64, 1024),
75 | 'height' => $faker->numberBetween(64, 1024),
76 | 'caption' => $faker->text(32),
77 | ];
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Blocks/Header.php:
--------------------------------------------------------------------------------
1 | [],
21 | ];
22 | }
23 |
24 | /**
25 | * Rules to validate data of the block.
26 | *
27 | * @return array
28 | */
29 | public function rules(): array
30 | {
31 | return [
32 | 'text' => 'string',
33 | 'level' => 'integer|min:1|max:6',
34 | ];
35 | }
36 |
37 | /**
38 | * Renderer for the block.
39 | *
40 | * @return string
41 | */
42 | public function render(): string
43 | {
44 | if (View::getFacadeRoot())
45 | {
46 | return view(sprintf('editor.php::%s.header', EditorPhp::usingTemplate()))
47 | ->with(['data' => $this->data])
48 | ->render();
49 | }
50 |
51 | return Helpers::renderNative(__DIR__ . sprintf('/../../resources/php/%s/header.php', EditorPhp::usingTemplate()), ['data' => $this->data]);
52 | }
53 |
54 | /**
55 | * Generates fake data for the block.
56 | *
57 | * @param \Faker\Generator $faker
58 | *
59 | * @return array
60 | */
61 | public static function fake(\Faker\Generator $faker): array
62 | {
63 | return [
64 | 'text' => $faker->text(64),
65 | 'level' => $faker->numberBetween(1, 6),
66 | ];
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Blocks/Image.php:
--------------------------------------------------------------------------------
1 | [],
21 | 'caption' => [],
22 | ];
23 | }
24 |
25 | /**
26 | * Rules to validate data of the block.
27 | *
28 | * @return array
29 | */
30 | public function rules(): array
31 | {
32 | return [
33 | 'file.url' => 'url',
34 | 'caption' => 'string',
35 | 'withBorder' => 'boolean',
36 | 'stretched' => 'boolean',
37 | 'withBackground' => 'boolean',
38 | ];
39 | }
40 |
41 | /**
42 | * Renderer for the block.
43 | *
44 | * @return string
45 | */
46 | public function render(): string
47 | {
48 | if (View::getFacadeRoot())
49 | {
50 | return view(sprintf('editor.php::%s.image', EditorPhp::usingTemplate()))
51 | ->with(['data' => $this->data])
52 | ->render();
53 | }
54 |
55 | return Helpers::renderNative(__DIR__ . sprintf('/../../resources/php/%s/image.php', EditorPhp::usingTemplate()), ['data' => $this->data]);
56 | }
57 |
58 | /**
59 | * Generates fake data for the block.
60 | *
61 | * @param \Faker\Generator $faker
62 | *
63 | * @return array
64 | */
65 | public static function fake(\Faker\Generator $faker): array
66 | {
67 | return [
68 | 'file' => ['url' => 'https://picsum.photos/200/300'],
69 | 'caption' => $faker->text(),
70 | 'withBorder' => $faker->boolean(),
71 | 'stretched' => $faker->boolean(),
72 | 'withBackground' => $faker->boolean(),
73 | ];
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Blocks/LinkTool.php:
--------------------------------------------------------------------------------
1 | [],
21 | 'meta.title' => [],
22 | 'meta.site_name' => [],
23 | 'meta.description' => [],
24 | 'meta.image.url' => [],
25 | ];
26 | }
27 |
28 | /**
29 | * Rules to validate data of the block.
30 | *
31 | * @return array
32 | */
33 | public function rules(): array
34 | {
35 | return [
36 | 'link' => 'url',
37 | 'meta.title' => 'string',
38 | 'meta.site_name' => 'string',
39 | 'meta.description' => 'string',
40 | 'meta.image.url' => 'url',
41 | ];
42 | }
43 |
44 | /**
45 | * Renderer for the block.
46 | *
47 | * @return string
48 | */
49 | public function render(): string
50 | {
51 | if (View::getFacadeRoot())
52 | {
53 | return view(sprintf('editor.php::%s.linktool', EditorPhp::usingTemplate()))
54 | ->with(['data' => $this->data])
55 | ->render();
56 | }
57 |
58 | return Helpers::renderNative(__DIR__ . sprintf('/../../resources/php/%s/linktool.php', EditorPhp::usingTemplate()), ['data' => $this->data]);
59 | }
60 |
61 | /**
62 | * Generates fake data for the block.
63 | *
64 | * @param \Faker\Generator $faker
65 | *
66 | * @return array
67 | */
68 | public static function fake(\Faker\Generator $faker): array
69 | {
70 | return [
71 | 'link' => $faker->url(),
72 | 'meta' => [
73 | 'title' => $faker->text(32),
74 | 'site_name' => $faker->text(32),
75 | 'description' => $faker->text(96),
76 | 'image' => ['url' => 'https://picsum.photos/200/300'],
77 | ],
78 | ];
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Blocks/ListBlock.php:
--------------------------------------------------------------------------------
1 | [],
22 | 'item.*' => [],
23 | ];
24 | }
25 |
26 | /**
27 | * Rules to validate data of the block.
28 | *
29 | * @return array
30 | */
31 | public function rules(): array
32 | {
33 | return [
34 | 'style' => ['string', Rule::in(['ordered', 'unordered'])],
35 | 'items' => 'array',
36 | 'item.*' => 'string',
37 | ];
38 | }
39 |
40 | /**
41 | * Renderer for the block.
42 | *
43 | * @return string
44 | */
45 | public function render(): string
46 | {
47 | if (View::getFacadeRoot())
48 | {
49 | return view(sprintf('editor.php::%s.list', EditorPhp::usingTemplate()))
50 | ->with(['data' => $this->data])
51 | ->render();
52 | }
53 |
54 | return Helpers::renderNative(__DIR__ . sprintf('/../../resources/php/%s/list.php', EditorPhp::usingTemplate()), ['data' => $this->data]);
55 | }
56 |
57 | /**
58 | * Generates fake data for the block.
59 | *
60 | * @param \Faker\Generator $faker
61 | *
62 | * @return array
63 | */
64 | public static function fake(\Faker\Generator $faker): array
65 | {
66 | $items = [];
67 |
68 | foreach (range(0, $faker->numberBetween(1, 10)) as $index)
69 | {
70 | $items[] = $faker->text(64);
71 | }
72 |
73 | return [
74 | 'style' => $faker->randomElement(['ordered', 'unordered']),
75 | 'items' => $items,
76 | ];
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Blocks/Paragraph.php:
--------------------------------------------------------------------------------
1 | [
21 | 'a:href,target,title',
22 | 'abbr:title',
23 | 'b',
24 | 'cite',
25 | 'code',
26 | 'em',
27 | 'i',
28 | 'kbd',
29 | 'q',
30 | 'samp',
31 | 'small',
32 | 'strong',
33 | 'sub',
34 | 'sup',
35 | 'time:datetime',
36 | 'var',
37 | 'u',
38 | 's',
39 | 'del',
40 | 'ins',
41 | 'strike',
42 | 'mark',
43 | ],
44 | ];
45 | }
46 |
47 | /**
48 | * Rules to validate data of the block.
49 | *
50 | * @return array
51 | */
52 | public function rules(): array
53 | {
54 | return [
55 | 'text' => 'string',
56 | ];
57 | }
58 |
59 | /**
60 | * Renderer for the block.
61 | *
62 | * @return string
63 | */
64 | public function render(): string
65 | {
66 | if (View::getFacadeRoot())
67 | {
68 | return view(sprintf('editor.php::%s.paragraph', EditorPhp::usingTemplate()))
69 | ->with(['data' => $this->data])
70 | ->render();
71 | }
72 |
73 | return Helpers::renderNative(__DIR__ . sprintf('/../../resources/php/%s/paragraph.php', EditorPhp::usingTemplate()), ['data' => $this->data]);
74 | }
75 |
76 | /**
77 | * Generates fake data for the block.
78 | *
79 | * @param \Faker\Generator $faker
80 | *
81 | * @return array
82 | */
83 | public static function fake(\Faker\Generator $faker): array
84 | {
85 | return ['text' => $faker->text(256)];
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Blocks/Personality.php:
--------------------------------------------------------------------------------
1 | [],
21 | 'description' => [],
22 | 'link' => [],
23 | 'photo' => [],
24 | ];
25 | }
26 |
27 | /**
28 | * Rules to validate data of the block.
29 | *
30 | * @return array
31 | */
32 | public function rules(): array
33 | {
34 | return [
35 | 'name' => 'string',
36 | 'description' => 'string',
37 | 'link' => 'url',
38 | 'photo' => 'url',
39 | ];
40 | }
41 |
42 | /**
43 | * Renderer for the block.
44 | *
45 | * @return string
46 | */
47 | public function render(): string
48 | {
49 | if (View::getFacadeRoot())
50 | {
51 | return view(sprintf('editor.php::%s.personality', EditorPhp::usingTemplate()))
52 | ->with(['data' => $this->data])
53 | ->render();
54 | }
55 |
56 | return Helpers::renderNative(__DIR__ . sprintf('/../../resources/php/%s/personality.php', EditorPhp::usingTemplate()), ['data' => $this->data]);
57 | }
58 |
59 | /**
60 | * Generates fake data for the block.
61 | *
62 | * @param \Faker\Generator $faker
63 | *
64 | * @return array
65 | */
66 | public static function fake(\Faker\Generator $faker): array
67 | {
68 | return [
69 | 'name' => $faker->name(),
70 | 'description' => $faker->text(),
71 | 'link' => $faker->url(),
72 | 'photo' => 'https://picsum.photos/200/300',
73 | ];
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Blocks/Quote.php:
--------------------------------------------------------------------------------
1 | [],
22 | 'caption' => [],
23 | 'alignment' => [],
24 | ];
25 | }
26 |
27 | /**
28 | * Rules to validate data of the block.
29 | *
30 | * @return array
31 | */
32 | public function rules(): array
33 | {
34 | return [
35 | 'text' => 'string',
36 | 'caption' => 'string',
37 | 'alignment' => ['string', Rule::in(['left', 'center'])],
38 | ];
39 | }
40 |
41 | /**
42 | * Renderer for the block.
43 | *
44 | * @return string
45 | */
46 | public function render(): string
47 | {
48 | if (View::getFacadeRoot())
49 | {
50 | return view(sprintf('editor.php::%s.quote', EditorPhp::usingTemplate()))
51 | ->with(['data' => $this->data])
52 | ->render();
53 | }
54 |
55 | return Helpers::renderNative(__DIR__ . sprintf('/../../resources/php/%s/quote.php', EditorPhp::usingTemplate()), ['data' => $this->data]);
56 | }
57 |
58 | /**
59 | * Generates fake data for the block.
60 | *
61 | * @param \Faker\Generator $faker
62 | *
63 | * @return array
64 | */
65 | public static function fake(\Faker\Generator $faker): array
66 | {
67 | return [
68 | 'text' => $faker->text(),
69 | 'caption' => $faker->name(),
70 | 'alignment' => $faker->randomElement(['left', 'center']),
71 | ];
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Blocks/Raw.php:
--------------------------------------------------------------------------------
1 | '*',
18 | ];
19 | }
20 |
21 | /**
22 | * Rules to validate data of the block.
23 | *
24 | * @return array
25 | */
26 | public function rules(): array
27 | {
28 | return [
29 | 'html' => 'string',
30 | ];
31 | }
32 |
33 | /**
34 | * Renderer for the block.
35 | *
36 | * @return string
37 | */
38 | public function render(): string
39 | {
40 | return $this->data->get('html', '');
41 | }
42 |
43 | /**
44 | * Generates fake data for the block.
45 | *
46 | * @param \Faker\Generator $faker
47 | *
48 | * @return array
49 | */
50 | public static function fake(\Faker\Generator $faker): array
51 | {
52 | return ['html' => $faker->randomHtml()];
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Blocks/Table.php:
--------------------------------------------------------------------------------
1 | [],
22 | ];
23 | }
24 |
25 | /**
26 | * Rules to validate data of the block.
27 | *
28 | * @return array
29 | */
30 | public function rules(): array
31 | {
32 | return [
33 | 'withHeadings' => 'boolean',
34 | 'content' => 'array',
35 | 'content.*' => 'array',
36 | 'content.*.*' => 'string',
37 | ];
38 | }
39 |
40 | /**
41 | * Renderer for the block.
42 | *
43 | * @return string
44 | */
45 | public function render(): string
46 | {
47 | if (View::getFacadeRoot())
48 | {
49 | return view(sprintf('editor.php::%s.table', EditorPhp::usingTemplate()))
50 | ->with(['data' => $this->data])
51 | ->render();
52 | }
53 |
54 | return Helpers::renderNative(__DIR__ . sprintf('/../../resources/php/%s/table.php', EditorPhp::usingTemplate()), ['data' => $this->data]);
55 | }
56 |
57 | /**
58 | * Generates fake data for the block.
59 | *
60 | * @param \Faker\Generator $faker
61 | *
62 | * @return array
63 | */
64 | public static function fake(\Faker\Generator $faker): array
65 | {
66 | $content = [];
67 | $width = $faker->numberBetween(2, 8);
68 |
69 | foreach (range(0, $faker->numberBetween(1, 10)) as $index)
70 | {
71 | $row = [];
72 |
73 | foreach (range(0, $width) as $index)
74 | {
75 | $row[] = $faker->text(64);
76 | }
77 |
78 | $content[] = $row;
79 | }
80 |
81 | return [
82 | 'withHeadings' => $faker->boolean(),
83 | 'content' => $content,
84 | ];
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Blocks/Warning.php:
--------------------------------------------------------------------------------
1 | [],
21 | 'message' => [],
22 | ];
23 | }
24 |
25 | /**
26 | * Rules to validate data of the block.
27 | *
28 | * @return array
29 | */
30 | public function rules(): array
31 | {
32 | return [
33 | 'title' => 'string',
34 | 'message' => 'string',
35 | ];
36 | }
37 |
38 | /**
39 | * Renderer for the block.
40 | *
41 | * @return string
42 | */
43 | public function render(): string
44 | {
45 | if (View::getFacadeRoot())
46 | {
47 | return view(sprintf('editor.php::%s.warning', EditorPhp::usingTemplate()))
48 | ->with(['data' => $this->data])
49 | ->render();
50 | }
51 |
52 | return Helpers::renderNative(__DIR__ . sprintf('/../../resources/php/%s/warning.php', EditorPhp::usingTemplate()), ['data' => $this->data]);
53 | }
54 |
55 | /**
56 | * Generates fake data for the block.
57 | *
58 | * @param \Faker\Generator $faker
59 | *
60 | * @return array
61 | */
62 | public static function fake(\Faker\Generator $faker): array
63 | {
64 | return [
65 | 'title' => $faker->text(32),
66 | 'message' => $faker->text(),
67 | ];
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Casts/EditorPhpCast.php:
--------------------------------------------------------------------------------
1 | setModel($model);
26 | }
27 |
28 | /**
29 | * @param Model $model
30 | * @param string $key
31 | * @param mixed $value
32 | * @param array $attributes
33 | *
34 | * @return mixed
35 | */
36 | public function set($model, string $key, $value, array $attributes)
37 | {
38 | if ($value instanceof \BumpCore\EditorPhp\EditorPhp)
39 | {
40 | return $value->setModel($model)->toJson();
41 | }
42 |
43 | return $value;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Console/BlockMakeCommand.php:
--------------------------------------------------------------------------------
1 | with(['data' => $this->data])
30 | ->render();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/EditorPhp.php:
--------------------------------------------------------------------------------
1 |
34 | */
35 | public Collection $blocks;
36 |
37 | /**
38 | * @var string
39 | */
40 | public readonly ?string $version;
41 |
42 | /**
43 | * Belonging model, if casted.
44 | *
45 | * @var Model
46 | */
47 | public readonly Model $model;
48 |
49 | /**
50 | * Fluent method to create new `EditorPhp` instance.
51 | *
52 | * @param string|null $input
53 | *
54 | * @return EditorPhp
55 | */
56 | public static function make(?string $input = null): self
57 | {
58 | return new static($input);
59 | }
60 |
61 | /**
62 | * Constructor.
63 | *
64 | * @param string|null $input
65 | *
66 | * @return void
67 | */
68 | public function __construct(?string $input = null)
69 | {
70 | if (empty($input))
71 | {
72 | $this->time = Carbon::now();
73 | $this->blocks = new Collection();
74 | $this->version = null;
75 | }
76 | else
77 | {
78 | $parser = new Parser($input);
79 | $this->time = $parser->time();
80 | $this->blocks = $parser->blocks($this);
81 | $this->version = $parser->version();
82 | }
83 | }
84 |
85 | /**
86 | * Registers new block.
87 | *
88 | * @param array $blocks
89 | * @param bool $override
90 | *
91 | * @return void
92 | */
93 | public static function register(array $blocks, bool $override = false): void
94 | {
95 | Parser::register($blocks, $override);
96 | }
97 |
98 | /**
99 | * Sets model to be used with casting.
100 | *
101 | * @param Model $model
102 | *
103 | * @return EditorPhp
104 | */
105 | public function setModel(Model &$model): self
106 | {
107 | if (!isset($this->model))
108 | {
109 | $this->model = $model;
110 | }
111 |
112 | return $this;
113 | }
114 |
115 | /**
116 | * Renders with `Bootstrap 5` templates.
117 | *
118 | * @return void
119 | */
120 | public static function useBootstrapFive(): void
121 | {
122 | static::$template = 'bootstrap-five';
123 | }
124 |
125 | /**
126 | * Renders with `tailwindcss` templates.
127 | *
128 | * @return void
129 | */
130 | public static function useTailwind(): void
131 | {
132 | static::$template = 'tailwind';
133 | }
134 |
135 | /**
136 | * Returns used template.
137 | *
138 | * @return string
139 | */
140 | public static function usingTemplate(): string
141 | {
142 | return static::$template;
143 | }
144 |
145 | /**
146 | * Converts the `Editor.php` as an array.
147 | *
148 | * @return array
149 | */
150 | public function toArray(): array
151 | {
152 | return [
153 | 'time' => (int) $this->time->getPreciseTimestamp(3),
154 | 'blocks' => $this->blocks->toArray(),
155 | 'version' => $this->version,
156 | ];
157 | }
158 |
159 | /**
160 | * Converts the `Editor.php` to its JSON representation.
161 | *
162 | * @param int $options
163 | *
164 | * @return string
165 | */
166 | public function toJson($options = 0): string
167 | {
168 | return json_encode($this->toArray(), $options);
169 | }
170 |
171 | /**
172 | * Creates an HTTP response that represents the `Editor.php`.
173 | *
174 | * @param \Illuminate\Http\Request $request
175 | *
176 | * @return \Symfony\Component\HttpFoundation\Response
177 | */
178 | public function toResponse($request)
179 | {
180 | return $request->expectsJson() ? response($this->toArray()) : response($this->render());
181 | }
182 |
183 | /**
184 | * Renders blocks into HTML.
185 | *
186 | * @return string
187 | */
188 | public function render(): string
189 | {
190 | return $this->blocks
191 | ->map(fn (Block $block) => $block->render())
192 | ->implode('');
193 | }
194 |
195 | /**
196 | * Renders blocks into HTML.
197 | *
198 | * @return string
199 | */
200 | public function toHtml()
201 | {
202 | return $this->render();
203 | }
204 |
205 | /**
206 | * Renders blocks into HTML.
207 | *
208 | * @return string
209 | */
210 | public function __toString(): string
211 | {
212 | return $this->render();
213 | }
214 |
215 | /**
216 | * Generates fake instance.
217 | *
218 | * @param bool $instance
219 | * @param int $minLength
220 | * @param int $maxLength
221 | *
222 | * @return EditorPhp|string
223 | */
224 | public static function fake(bool $instance = false, int $minLength = 8, int $maxLength = 30): EditorPhp|string
225 | {
226 | $faker = \Faker\Factory::create();
227 | $blocks = array_filter(Parser::$blocks, fn (string $provider) => method_exists($provider, 'fake'));
228 | $generatedBlocks = [];
229 |
230 | foreach (range(0, fake()->numberBetween($minLength, $maxLength)) as $index)
231 | {
232 | $block = fake()->randomElement($blocks);
233 | $generatedBlocks[] = (new ($block)($block::fake($faker)))->toArray();
234 | }
235 |
236 | $generated = json_encode([
237 | 'time' => (int) Carbon::now()->getPreciseTimestamp(3),
238 | 'blocks' => $generatedBlocks,
239 | 'version' => fake()->semver(),
240 | ]);
241 |
242 | return $instance ? static::make($generated) : $generated;
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/src/EditorPhpServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->runningInConsole())
21 | {
22 | $this->commands(BlockMakeCommand::class);
23 | }
24 |
25 | $this->loadViewsFrom(__DIR__ . '/../resources/views', 'editor.php');
26 |
27 | $this->publishes([
28 | __DIR__ . '/../resources/views' => resource_path('views/vendor/editor.php'),
29 | ], 'editor.php');
30 |
31 | $this->publishes([
32 | __DIR__ . '/../config/editor.php' => config_path('editor.php'),
33 | ]);
34 |
35 | Parser::register(config('editor.blocks') ?? [], !empty(config('editor.blocks')));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Exceptions/EditorPhpException.php:
--------------------------------------------------------------------------------
1 | make($data, $rules);
31 | }
32 |
33 | /**
34 | * Basic renderer to render native templates.
35 | *
36 | * @param string $file
37 | * @param array $data
38 | *
39 | * @return string
40 | */
41 | public static function renderNative(string $file, array $data): string
42 | {
43 | ob_start();
44 | extract($data);
45 | require $file;
46 |
47 | return ob_get_clean();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Parser.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | public static array $blocks = [
20 | 'attaches' => Blocks\Attaches::class,
21 | 'checklist' => Blocks\Checklist::class,
22 | 'code' => Blocks\Code::class,
23 | 'delimiter' => Blocks\Delimiter::class,
24 | 'embed' => Blocks\Embed::class,
25 | 'header' => Blocks\Header::class,
26 | 'image' => Blocks\Image::class,
27 | 'linkTool' => Blocks\LinkTool::class,
28 | 'list' => Blocks\ListBlock::class,
29 | 'paragraph' => Blocks\Paragraph::class,
30 | 'personality' => Blocks\Personality::class,
31 | 'quote' => Blocks\Quote::class,
32 | 'raw' => Blocks\Raw::class,
33 | 'table' => Blocks\Table::class,
34 | 'warning' => Blocks\Warning::class,
35 | ];
36 |
37 | /**
38 | * Converted input JSON.
39 | *
40 | * @var array
41 | */
42 | protected array $input;
43 |
44 | /**
45 | * Constructor.
46 | *
47 | * @param string $input
48 | *
49 | * @return void
50 | */
51 | public function __construct(string $input)
52 | {
53 | $this->input = $this->handleInput($input);
54 | }
55 |
56 | /**
57 | * Registers new block.
58 | *
59 | * @param array $blocks
60 | * @param bool $override
61 | *
62 | * @return void
63 | */
64 | public static function register(array $blocks, bool $override = false): void
65 | {
66 | if ($override)
67 | {
68 | static::$blocks = [];
69 | }
70 |
71 | foreach ($blocks as $type => $block)
72 | {
73 | if (!in_array(Block::class, class_parents($block)))
74 | {
75 | throw new EditorPhpException($block . ' must extend ' . Block::class);
76 | }
77 |
78 | static::$blocks[$type] = $block;
79 | }
80 | }
81 |
82 | /**
83 | * Returns the time of given `Editor.js` output.
84 | *
85 | * @return Carbon
86 | */
87 | public function time(): Carbon
88 | {
89 | return Carbon::parse(Arr::get($this->input, 'time') / 1000);
90 | }
91 |
92 | /**
93 | * Returns parsed blocks of given `Editor.js` output.
94 | *
95 | * @param EditorPhp|null $root
96 | *
97 | * @return Collection
98 | */
99 | public function blocks(?EditorPhp &$root = null): Collection
100 | {
101 | $blocks = new Collection();
102 |
103 | foreach (Arr::get($this->input, 'blocks') as $block)
104 | {
105 | $type = Arr::get($block, 'type');
106 |
107 | if (!key_exists($type, static::$blocks))
108 | {
109 | throw new EditorPhpException('Unknown block type: ' . $type);
110 | }
111 |
112 | $blocks->push(new (static::$blocks[$type])(Arr::get($block, 'data'), $root));
113 | }
114 |
115 | return $blocks;
116 | }
117 |
118 | /**
119 | * Returns the version of given `Editor.js` output.
120 | *
121 | * @return string
122 | */
123 | public function version(): string
124 | {
125 | return Arr::get($this->input, 'version');
126 | }
127 |
128 | /**
129 | * Parses given `Editor.js` output JSON.
130 | *
131 | * @param string $input
132 | *
133 | * @return array
134 | */
135 | protected function handleInput(string $input): array
136 | {
137 | if (!Str::isJson($input))
138 | {
139 | throw new EditorPhpException('Given Editor.js output is not a valid JSON.');
140 | }
141 |
142 | $input = json_decode($input, true);
143 |
144 | if (!$this->validateSchema($input))
145 | {
146 | throw new EditorPhpException('Given Editor.js output is not matching schema.');
147 | }
148 |
149 | return $input;
150 | }
151 |
152 | /**
153 | * Validates given `Editor.js` output.
154 | *
155 | * @param array $input
156 | *
157 | * @return bool
158 | */
159 | public function validateSchema(array $input): bool
160 | {
161 | $validator = Helpers::makeValidator($input, [
162 | 'time' => 'required|numeric',
163 | 'blocks' => 'present|array',
164 | 'blocks.*' => 'present|array',
165 | 'blocks.*.type' => 'required|string',
166 | 'blocks.*.data' => 'present|array',
167 | 'version' => 'required|string',
168 | ]);
169 |
170 | return !$validator->fails();
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/src/Purifier.php:
--------------------------------------------------------------------------------
1 | $allowedTags
14 | *
15 | * @return string
16 | */
17 | public static function stripTags(string $string, array $allowedTags = []): string
18 | {
19 | return strip_tags($string, $allowedTags);
20 | }
21 |
22 | /**
23 | * Strips unwanted attributes from given tag and string.
24 | *
25 | * @param string $string
26 | * @param string $tag
27 | * @param array $allowedAttributes
28 | *
29 | * @return string
30 | */
31 | public static function stripAttributes(string $string, string $tag, array $allowedAttributes = []): string
32 | {
33 | $domDocument = new DOMDocument();
34 | libxml_use_internal_errors(true);
35 | // We're using template tags only for wrapping. In normal case It would be `p`.
36 | $domDocument->loadHTML(implode('', ['', $string, ' ']), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
37 |
38 | /**
39 | * @var \DOMElement $node
40 | */
41 | foreach ($domDocument->getElementsByTagName($tag) as $node)
42 | {
43 | /**
44 | * @var \DOMAttr $attribute
45 | */
46 | foreach ($node->attributes as $attribute)
47 | {
48 | if (!in_array($attribute->name, $allowedAttributes))
49 | {
50 | $node->removeAttribute($attribute->name);
51 | }
52 | }
53 | }
54 |
55 | // DomDocument little bit old, isn't it?
56 | return str_replace(
57 | ['', ' '],
58 | '',
59 | mb_convert_encoding($domDocument->saveHTML($domDocument->documentElement), 'ISO-8859-1', 'UTF-8')
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/Block/BlockTest.php:
--------------------------------------------------------------------------------
1 | expect(new Paragraph(['text' => 'foo']))->toBeInstanceOf(Block::class),
9 | );
10 |
11 | test(
12 | 'Can be converted to array',
13 | fn () => expect(Paragraph::make(['text' => 'foo'])->toArray())->toBeArray(),
14 | );
15 |
16 | test(
17 | 'Can be access data via get method',
18 | fn () => expect(Paragraph::make(['text' => 'foo'])->get('text'))
19 | ->toEqual('foo')
20 | );
21 |
22 | test(
23 | 'Can be set data via get method',
24 | fn () => expect(Paragraph::make(['text' => 'foo'])->set('text', 'baz')->get('text'))
25 | ->toEqual('baz')
26 | );
27 |
28 | test(
29 | 'Can be check data exists via has method',
30 | fn () => expect(Paragraph::make(['text' => 'foo'])->has('text'))->toBeTrue()
31 | );
32 |
33 | test(
34 | 'Can be rendered',
35 | fn () => expect((new Paragraph(['text' => 'foo']))->render())->toBeString(),
36 | );
37 |
38 | test(
39 | 'Can be rendered via casting to string',
40 | fn () => expect((string) (new Paragraph(['text' => 'foo'])))->toBeString(),
41 | );
42 |
43 | test(
44 | 'Can be rendered via toHtml',
45 | fn () => expect(Paragraph::make(['text' => 'foo'])->toHtml())->toBeString(),
46 | );
47 |
--------------------------------------------------------------------------------
/tests/Block/DataTest.php:
--------------------------------------------------------------------------------
1 | expect(new Data(['foo' => 'bar'], '*', ['foo' => 'string']))
8 | ->toBeInstanceOf(Data::class),
9 | );
10 |
11 | test(
12 | 'Can empty on validation fail',
13 | fn () => expect((new Data(['foo' => 'bar'], '*', ['foo' => 'numeric']))->toArray())
14 | ->toBeEmpty()
15 | );
16 |
17 | test(
18 | 'Can be access data by invoking',
19 | fn () => expect((new Data(['foo' => 'bar'], '*', ['foo' => 'string']))('foo'))
20 | ->toEqual('bar'),
21 | );
22 |
23 | test(
24 | 'Can be access data by invoking with fallback',
25 | fn () => expect((new Data(['foo' => 'bar'], '*', ['foo' => 'string']))('bar', 'baz'))
26 | ->toEqual('baz'),
27 | );
28 |
29 | test(
30 | 'Can be access data by get method',
31 | fn () => expect((new Data(['foo' => 'bar'], '*', ['foo' => 'string']))->get('foo'))
32 | ->toEqual('bar'),
33 | );
34 |
35 | test(
36 | 'Can be access data by get method with fallback',
37 | fn () => expect((new Data(['foo' => 'bar'], '*', ['foo' => 'string']))->get('bar', 'baz'))
38 | ->toEqual('baz'),
39 | );
40 |
41 | test(
42 | 'Can be set data by get method',
43 | function() {
44 | $data = new Data(['foo' => 'bar'], '*', ['foo' => 'string']);
45 | $data->set('foo', 'baz');
46 |
47 | expect($data->get('foo'))->toEqual('baz');
48 | },
49 | );
50 |
51 | test(
52 | 'Can be check data exists via has method',
53 | fn () => expect((new Data(['foo' => 'bar'], '*', ['foo' => 'string']))->has('bar'))->toBeFalse()
54 | );
55 |
56 | test(
57 | 'Can be converted to array',
58 | fn () => expect((new Data(['foo' => 'bar'], '*', ['foo' => 'string']))->toArray())->toBeArray(),
59 | );
60 |
61 | test(
62 | 'Can allow all tags',
63 | function() {
64 | $data = new Data(
65 | ['foo' => ' This should be allowed!
'],
66 | '*',
67 | ['foo' => 'string']
68 | );
69 |
70 | expect(str_contains($data->get('foo'), 'div'))->toBeTrue();
71 | expect(str_contains($data->get('foo'), 'script'))->toBeTrue();
72 | }
73 | );
74 |
75 | test(
76 | 'Can allow only anchor with href attribute',
77 | function() {
78 | $data = new Data(
79 | ['foo' => ' This should be not allowed!
This should be allowed '],
80 | ['foo' => ['a:href']],
81 | ['foo' => 'string']
82 | );
83 |
84 | expect(str_contains($data->get('foo'), ''))->toBeFalse();
85 | expect(str_contains($data->get('foo'), '
This should be not allowed!
This should be allowed '],
96 | ['foo' => '*'],
97 | ['foo' => 'string']
98 | );
99 | expect(str_contains($data->get('foo'), '
'))->toBeTrue();
100 | expect(str_contains($data->get('foo'), '