diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea5e363..6149ce7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## 1.2.0
+* Fix: Resolve `Image` block import issues.
+* Feat: Add custom hooks `cbtj_import_block`, `cbtj_export_block`, `cbtj_blocks`.
+* Docs: Update README docs.
+* Tested up to WP 6.8.
+
## 1.1.0
* Feat: Add REST namespace filter `cbtj_rest_namespace`.
* Refactor: Use classes for PHP codebase.
diff --git a/README.md b/README.md
index 37f5d79..5c96263 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,79 @@ https://github.com/user-attachments/assets/9dedf30f-9df0-4307-b634-cecef930a6e5
### Hooks
+#### `cbtj_blocks`
+
+This custom hook (filter) provides the ability to customise the block classes:
+
+```php
+add_filter( 'cbtj_blocks', [ $this, 'custom_blocks' ], 10 );
+
+public function custom_blocks( $blocks ): array {
+ $blocks[] = \YourNameSpace\YourCustomBlock::class;
+
+ return $block;
+}
+```
+
+**Parameters**
+
+- blocks _`{Block[]}`_ By default, this is an array consisting of block classes.
+
+
+#### `cbtj_import_block`
+
+This custom hook (filter) provides the ability to customise any block array during import:
+
+```php
+add_filter( 'cbtj_import_block', [ $this, 'custom_import_block' ], 10 );
+
+public function custom_import_block( $block ): array {
+ if ( 'core/image' !== $block['name'] ) {
+ return $block;
+ }
+
+ // Get block attributes.
+ $block['attributes'] = json_decode( $block['attributes'], true );
+
+ // Set Caption using Post meta.
+ $block['attributes']['caption'] = get_post_meta( get_the_ID(), 'featured_image_caption', true );
+
+ // Encode attributes finally.
+ $block['attributes'] = wp_json_encode( $block['attributes'] );
+
+ return $block;
+}
+```
+
+**Parameters**
+
+- block _`{mixed[]}`_ By default, this would be a block array containing `name`, `originalContent`, `attributes` & `innerBlocks` key/value pairs.
+
+
+#### `cbtj_export_block`
+
+This custom hook (filter) provides the ability to customise any block array during export:
+
+```php
+add_filter( 'cbtj_export_block', [ $this, 'custom_export_block' ], 10 );
+
+public function custom_export_block( $block ): array {
+ if ( 'core/image' !== $block['name'] ) {
+ return $block;
+ }
+
+ // Set Caption using Post meta.
+ $block['attributes']['caption'] = get_post_meta( get_the_ID(), 'featured_image_caption', true );
+
+ return $block;
+}
+```
+
+**Parameters**
+
+- block _`{mixed[]}`_ By default, this would be a block array containing `name`, `content`, `filtered`, `attributes` & `innerBlocks` key/value pairs.
+
+
#### `cbtj_rest_export`
This custom hook (filter) provides the ability to customise the REST response obtained:
diff --git a/inc/Abstracts/Block.php b/inc/Abstracts/Block.php
new file mode 100644
index 0000000..d1cfc51
--- /dev/null
+++ b/inc/Abstracts/Block.php
@@ -0,0 +1,37 @@
+ $block['blockName'] ?? '',
'content' => $block['innerHTML'] ?? '',
'filtered' => wp_strip_all_tags( $block['innerHTML'] ?? '' ),
'attributes' => $block['attrs'] ?? [],
'innerBlocks' => $children,
];
+
+ /**
+ * Filter Export Block.
+ *
+ * @since 1.2.0
+ *
+ * @param mixed[] $export_block Export Block.
+ * @return mixed[]
+ */
+ return apply_filters( 'cbtj_export_block', $export_block );
}
}
diff --git a/inc/Routes/Import.php b/inc/Routes/Import.php
index ea8f156..0b0d609 100644
--- a/inc/Routes/Import.php
+++ b/inc/Routes/Import.php
@@ -76,16 +76,25 @@ public function rest_callback( $request ) {
}
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
- $json = file_get_contents( $json_file );
- $import = $this->get_blocks_import( json_decode( $json, true ), $post_id );
+ $json = json_decode( file_get_contents( $json_file ), true );
+ $blocks = $this->get_blocks_import( $json['content'] ?? [], $post_id );
+
+ // Make sure to weed out empty blocks.
+ $content = array_filter( $blocks, fn( $block ) => ! empty( $block ) );
+
+ // Add title.
+ $import = [
+ 'title' => $json['title'] ?? '',
+ 'content' => $content
+ ];
/**
- * Filter JSON Import.
+ * Filter Import.
*
* @since 1.0.1
*
- * @param mixed[] $response Import Blocks.
- * @param integer $post_id Post ID.
+ * @param mixed[] $import Import data.
+ * @param integer $post_id Post ID.
*
* @return mixed[]
*/
@@ -102,18 +111,13 @@ public function rest_callback( $request ) {
*
* @param 1.0.1
*
- * @param array $json JSON Array of Blocks.
+ * @param array $content JSON content.
* @param integer $post_id Post ID.
*
* @return mixed[]
*/
- public function get_blocks_import( $json, $post_id ): array {
- $import_blocks = array_map(
- [ $this, 'get_import' ],
- $json['content'] ?? []
- );
-
- return $import_blocks;
+ public function get_blocks_import( $content, $post_id ): array {
+ return array_map( [ $this, 'get_import' ], $content );
}
/**
@@ -128,6 +132,11 @@ public function get_blocks_import( $json, $post_id ): array {
* @return mixed[]
*/
public function get_import( $block ): array {
+ // Bail out, if block has no name.
+ if ( '' === ( $block['name'] ?? '' ) ) {
+ return [];
+ }
+
$children = [];
if ( ! empty( $block['innerBlocks'] ) ) {
@@ -138,10 +147,21 @@ public function get_import( $block ): array {
$block['attributes']['content'] = $block['filtered'] ?? '';
- return [
- 'name' => $block['name'] ?? '',
- 'attributes' => wp_json_encode( $block['attributes'] ?? [] ),
- 'innerBlocks' => $children ?? [],
+ $import_block = [
+ 'name' => $block['name'] ?? '',
+ 'originalContent' => $block['content'] ?? '',
+ 'attributes' => wp_json_encode( $block['attributes'] ?? [] ),
+ 'innerBlocks' => $children ?? [],
];
+
+ /**
+ * Filter Import Block.
+ *
+ * @since 1.2.0
+ *
+ * @param mixed[] $import_block Import Block.
+ * @return mixed[]
+ */
+ return apply_filters( 'cbtj_import_block', $import_block );
}
}
diff --git a/inc/Services/Blocks.php b/inc/Services/Blocks.php
new file mode 100644
index 0000000..9b4d891
--- /dev/null
+++ b/inc/Services/Blocks.php
@@ -0,0 +1,55 @@
+init();
+ }
+ }
+}
diff --git a/readme.txt b/readme.txt
index 902806d..e13089d 100644
--- a/readme.txt
+++ b/readme.txt
@@ -64,6 +64,12 @@ Want to add your personal touch? All of our documentation can be found [here](ht
== Changelog ==
+= 1.2.0 =
+* Fix: Resolve `Image` block import issues.
+* Feat: Add custom hooks `cbtj_import_block`, `cbtj_export_block`, `cbtj_blocks`.
+* Docs: Update README docs.
+* Tested up to WP 6.8.
+
= 1.1.0 =
* Feat: Add REST namespace filter `cbtj_rest_namespace`.
* Refactor: Use classes for PHP codebase.
diff --git a/src/components/ImportJSON.tsx b/src/components/ImportJSON.tsx
index a689748..55c7d4b 100644
--- a/src/components/ImportJSON.tsx
+++ b/src/components/ImportJSON.tsx
@@ -2,6 +2,9 @@ import { __ } from '@wordpress/i18n';
import { dispatch } from '@wordpress/data';
import { Button } from '@wordpress/components';
import { createBlock } from '@wordpress/blocks';
+import { store as editorStore } from '@wordpress/editor';
+import { store as noticeStore } from '@wordpress/notices';
+import { store as blockEditorStore } from '@wordpress/block-editor';
import { getModalParams, getImport } from '../utils';
@@ -40,26 +43,38 @@ const ImportJSON = (): JSX.Element => {
*
* @since 1.0.1
*
- * @param {Object} wpMediaModal
- *
+ * @param {any} wpMediaModal
* @return {Promise}
*/
- const handleImport = async ( wpMediaModal ) => {
+ const handleImport = async ( wpMediaModal: any ): Promise< void > => {
const attachment = wpMediaModal
.state()
.get( 'selection' )
.first()
.toJSON();
- const jsonImport = ( await getImport( attachment ) ) as any[];
- jsonImport.forEach( ( { name, attributes, innerBlocks } ) => {
- attributes = JSON.parse( attributes );
- (
- dispatch( 'core/block-editor' ) as { insertBlocks: any }
- ).insertBlocks(
- createBlock( name, { ...attributes }, innerBlocks )
- );
- } );
+ try {
+ // Get data.
+ const { title, content } = await getImport( attachment );
+
+ // Add title.
+ dispatch( editorStore ).editPost( { title, status: 'publish' } );
+
+ // Add content.
+ content.forEach( ( { name, attributes, innerBlocks } ) => {
+ attributes = JSON.parse( attributes );
+ (
+ dispatch( blockEditorStore ) as { insertBlocks: any }
+ ).insertBlocks(
+ createBlock( name, { ...attributes }, innerBlocks )
+ );
+ } );
+
+ // Save Post.
+ await dispatch( editorStore ).savePost();
+ } catch ( e ) {
+ dispatch( noticeStore ).createWarningNotice( e.message );
+ }
};
return (
diff --git a/src/utils.tsx b/src/utils.tsx
index e635ba0..9a2ba0c 100644
--- a/src/utils.tsx
+++ b/src/utils.tsx
@@ -1,6 +1,7 @@
import { __ } from '@wordpress/i18n';
import { select } from '@wordpress/data';
import apiFetch from '@wordpress/api-fetch';
+import { store as editorStore } from '@wordpress/editor';
/**
* Get Blocks.
@@ -13,10 +14,10 @@ import apiFetch from '@wordpress/api-fetch';
*
* @return {Promise} Blocks.
*/
-export const getBlocks = (): Promise< any[] > => {
- const postID = select( 'core/editor' ).getCurrentPostId();
+export const getBlocks = async (): Promise< any[] > => {
+ const postID = select( editorStore ).getCurrentPostId();
- return apiFetch( {
+ return await apiFetch( {
path: `/cbtj/v1/${ postID }`,
} );
};
@@ -27,13 +28,13 @@ export const getBlocks = (): Promise< any[] > => {
* This function reaches out to the import endpoint
* and gets the list of JSON blocks.
*
- * @param attachment
* @since 1.0.1
*
- * @return {Promise} Import.
+ * @param {any} attachment Attachment object.
+ * @return {Promise} Import.
*/
-export const getImport = ( attachment ): Promise< any[] > => {
- return apiFetch( {
+export const getImport = async ( attachment: any ): Promise< any > => {
+ return await apiFetch( {
path: '/cbtj/v1/import',
method: 'POST',
data: {
@@ -51,9 +52,9 @@ export const getImport = ( attachment ): Promise< any[] > => {
*
* @since 1.0.1
*
- * @return {Object} Modal Params.
+ * @return {any} Modal Params.
*/
-export const getModalParams = (): object => {
+export const getModalParams = (): any => {
return {
title: __( 'Select JSON File', 'convert-blocks-to-json' ),
button: {
diff --git a/tests/Args.test.tsx b/tests/Args.test.tsx
index 6ecce25..30ea171 100644
--- a/tests/Args.test.tsx
+++ b/tests/Args.test.tsx
@@ -2,6 +2,10 @@ import '@testing-library/jest-dom';
import { getBlocks, getModalParams, getImport } from '../src/utils';
+jest.mock( '@wordpress/editor', () => ( {
+ store: 'core/editor',
+} ) );
+
jest.mock( '@wordpress/data', () => ( {
select: jest.fn( ( arg ) => {
if ( 'core/editor' === arg ) {
@@ -14,7 +18,7 @@ jest.mock( '@wordpress/data', () => ( {
} ) );
jest.mock( '@wordpress/api-fetch', () => {
- function apiFetchMock( options ) {
+ function apiFetchMock( options: any ) {
const { path, method } = options;
if ( '/cbtj/v1/7' === path ) {
diff --git a/tests/ExportJSON.test.tsx b/tests/ExportJSON.test.tsx
index c604061..6d21ce7 100644
--- a/tests/ExportJSON.test.tsx
+++ b/tests/ExportJSON.test.tsx
@@ -1,9 +1,12 @@
-import React from 'react';
-import { render, screen } from '@testing-library/react';
+import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import ExportJSON from '../src/components/ExportJSON';
+jest.mock( '@wordpress/editor', () => ( {
+ store: 'core/editor',
+} ) );
+
jest.mock( '@wordpress/data', () => ( {
select: jest.fn( ( storeName ) => {
if ( storeName === 'core/editor' ) {
diff --git a/tests/ViewJSON.test.tsx b/tests/ViewJSON.test.tsx
index 2d48d22..5e3931d 100644
--- a/tests/ViewJSON.test.tsx
+++ b/tests/ViewJSON.test.tsx
@@ -1,4 +1,4 @@
-import { render, screen } from '@testing-library/react';
+import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import ViewJSON from '../src/components/ViewJSON';
@@ -26,7 +26,9 @@ jest.mock( '@wordpress/components', () => ( {
describe( 'ViewJSON', () => {
beforeAll( () => {
- global.cbtj = { baseUrl: 'https://example.com' };
+ ( global as unknown as { cbtj: any } ).cbtj = {
+ baseUrl: 'https://example.com',
+ };
} );
it( 'renders the component with correct text and link', () => {