Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 37 additions & 18 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
name: Lint
on: [push, pull_request]
name: Coding standards
on: [push]
jobs:
lint:
name: PHP Lint
runs-on: ubuntu-latest
steps:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.3
lint:
name: Coding standards
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: [ '8.3', '8.4' ]

- name: Cache Composer dependencies
uses: actions/cache@v4
with:
path: /tmp/composer-cache
key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
steps:
- name: Checkout
uses: actions/checkout@v4

- uses: actions/checkout@v4
- name: lint
run: make app:lint
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2

- name: Setup cache
run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV

- name: Cache dependencies installed with composer
uses: actions/cache@v4
with:
path: ${{ env.COMPOSER_CACHE_DIR }}
key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: |
php${{ matrix.php }}-composer-latest-

- name: Update composer
run: composer self-update

- name: Install dependencies with composer
run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi

- name: Run code quality analysis
run: composer app:lint
55 changes: 37 additions & 18 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
name: Test
on: [push, pull_request]
name: Tests
on: [push]
jobs:
lint:
name: PHP Test
runs-on: ubuntu-latest
steps:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.3
test:
name: Tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: [ '8.3', '8.4' ]

- name: Cache Composer dependencies
uses: actions/cache@v4
with:
path: /tmp/composer-cache
key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
steps:
- name: Checkout
uses: actions/checkout@v4

- uses: actions/checkout@v4
- name: test
run: make app:test
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2

- name: Setup cache
run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV

- name: Cache dependencies installed with composer
uses: actions/cache@v4
with:
path: ${{ env.COMPOSER_CACHE_DIR }}
key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: |
php${{ matrix.php }}-composer-latest-
- name: Update composer
run: composer self-update

- name: Install dependencies with composer
run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi

- name: Run tests
run: composer app:test
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
],
"app:lint:fix": [
"vendor/bin/ecs check --fix",
"venodr/bin/rector"
"vendor/bin/rector"
],
"app:test": "vendor/bin/phpunit"
}
Expand Down
12 changes: 6 additions & 6 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 73 additions & 0 deletions src/AbstractBlock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

/*
* This file is part of the EditorJs Sanitizer package.
*
* (c) Bernard Ngandu <bernard@devscast.tech>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Devscast\EditorJs;

/**
* Class AbstractBlock.
*
* @phpstan-consistent-constructor
*
* @author bernard-ng <bernard@devscast.tech>
*/
abstract readonly class AbstractBlock implements BlockInterface
{
public const string NAME = 'abstract';

protected function __construct(
public string $id,
public string $type,
public array $data,
public ?array $tunes = null,
public ?array $allowedTags = null,
) {
Assert::notEmpty($id);
}

public static function create(array $data, array $allowedTags = []): static
{
return new static(
id: $data['id'],
type: $data['type'],
data: $data['data'],
tunes: $data['tunes'] ?? null,
allowedTags: $allowedTags
);
}

public function sanitize(): string
{
$sanitizer = Sanitizer::create($this->allowedTags ?? []);

return $sanitizer->sanitize($this->toHtml());
}

#[\Override]
public function getName(): string
{
return static::NAME;
}

#[\Override]
public function getSchema(): array
{
$filename = sprintf('%s/schemas/%s.schema.json', dirname(__DIR__), $this->getName());
$content = file_get_contents($filename);
Assert::string($content, sprintf('Schema for block type %s not found', $this->getName()));

/** @var array $schema */
$schema = json_decode($content, true);

return $schema;
}
}
60 changes: 29 additions & 31 deletions src/BlockFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
namespace Devscast\EditorJs;

use Devscast\EditorJs\Blocks\Attaches;
use Devscast\EditorJs\Blocks\Block;
use Devscast\EditorJs\Blocks\Code;
use Devscast\EditorJs\Blocks\Delimiter;
use Devscast\EditorJs\Blocks\Embed;
Expand All @@ -27,26 +26,45 @@
use Devscast\EditorJs\Blocks\Table;
use Devscast\EditorJs\Blocks\Warning;
use Devscast\EditorJs\Exception\EditorException;
use Swaggest\JsonSchema\Schema;

/**
* Class BlockFactory.
*
* @author bernard-ng <bernard@devscast.tech>
*/
abstract readonly class BlockFactory
abstract class BlockFactory
{
public const array SUPPORTED_BLOCKS = [
Attaches::NAME,
Code::NAME,
Delimiter::NAME,
Embed::NAME,
Header::NAME,
Image::NAME,
Listing::NAME,
Paragraph::NAME,
Quote::NAME,
Raw::NAME,
Table::NAME,
Warning::NAME,
];

/**
* @throws EditorException
*/
public static function parse(string $data, ?array $tools = null, array $allowedTags = []): array
public static function parse(string $data, ?array $supportedBlocks = self::SUPPORTED_BLOCKS, array $allowedTags = []): array
{
try {
/** @var array{time: int, blocks: array<array{id: string, type: string, data: mixed}>} $blocks */
$blocks = json_decode($data, true, flags: JSON_THROW_ON_ERROR);
$mapper = fn (array $block): Block => self::create($block, self::getAllowedTags($block, $allowedTags));
$mapper = fn (array $block): AbstractBlock => self::create($block, self::getAllowedTags($block, $allowedTags));

if ($supportedBlocks !== null) {
$blocks['blocks'] = self::filter($blocks['blocks'], $supportedBlocks);

if ($tools !== null) {
$blocks = self::filter($blocks['blocks'], $tools);
if (empty($blocks['blocks'])) {
throw new EditorException('No valid blocks found after filtering');
}
}

return array_map($mapper, $blocks['blocks']);
Expand All @@ -55,25 +73,14 @@ public static function parse(string $data, ?array $tools = null, array $allowedT
}
}

private static function filter(array $data, array $tools): array
private static function filter(array $blocks, array $supportedBlocks): array
{
return array_filter($data, fn (array $block): bool => isset($tools[$block['type']]));
return array_filter($blocks, fn (array $block) => in_array($block['type'], $supportedBlocks, true));
}

/**
* @throws EditorException
*/
private static function create(array $data, array $allowedTags = []): Block
private static function create(array $data, array $allowedTags = []): AbstractBlock
{
try {
// TODO: I think this may be very slow for large data need to think of a better way

//dd(self::getSchema($data['type']), $data);

$schema = Schema::import(self::getSchema($data['type']));

dd($schema);

return match ($data['type']) {
'attaches' => Attaches::create($data, $allowedTags),
'code' => Code::create($data, $allowedTags),
Expand All @@ -93,19 +100,10 @@ private static function create(array $data, array $allowedTags = []): Block
}
}

private static function getAllowedTags(mixed $block, array $allowedTags): array
private static function getAllowedTags(array $block, array $allowedTags): array
{
Assert::keyExists($block, 'type');

return $allowedTags[$block['type']] ?? [];
}

private static function getSchema(string $schema): array
{
$filename = sprintf("%s/schemas/%s.schema.json", dirname(__DIR__), $schema);
$content = file_get_contents($filename);
Assert::string($content, sprintf('Schema for block type %s not found', $schema));

return json_decode($content, true);
}
}
19 changes: 19 additions & 0 deletions src/BlockInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Devscast\EditorJs;

/**
* Interface BlockInterface.
*
* @author bernard-ng <bernard@devscast.tech>
*/
interface BlockInterface
{
public function getName(): string;

public function toHtml(): string;

public function getSchema(): array;
}
Loading
Loading