From 802a6f069fdc9d2399620a85aa03904fc6329e5d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= ' );
$html->next_tag();
if ( $node->getUrl() ) {
- $html->set_attribute( 'src', $node->getUrl() );
+ $html->set_attribute( 'src', urldecode($node->getUrl()) );
}
if ( $node->getTitle() ) {
$html->set_attribute( 'title', $node->getTitle() );
@@ -235,7 +235,26 @@ private function convert_markdown_to_blocks() {
$children[0]->setLiteral( '' );
}
- $this->append_content( $html->get_updated_html() );
+ $image_tag = $html->get_updated_html();
+ // @TODO: Decide between inline image and the image block
+ $in_paragraph = $this->current_block()->block_name === 'paragraph';
+ if ( $in_paragraph ) {
+ $this->append_content( '
' );
+ }
break;
case ExtensionInline\Link::class:
diff --git a/packages/playground/data-liberation-static-files-editor/plugin.php b/packages/playground/data-liberation-static-files-editor/plugin.php
index d6f318c82a..e7d4bbfbad 100644
--- a/packages/playground/data-liberation-static-files-editor/plugin.php
+++ b/packages/playground/data-liberation-static-files-editor/plugin.php
@@ -3,8 +3,6 @@
* Plugin Name: Data Liberation – WordPress Static files editor
*
* @TODO: Page metadata editor in Gutenberg
- * @TODO: A special "filename" field in wp-admin and in Gutenberg. Either source from the page title or
- * pin it to a specific, user-defined value.
* @TODO: Choose the local file storage format (MD, HTML, etc.) in Gutenberg page options.
* @TODO: HTML, XHTML, and Blocks renderers
* @TODO: Integrity check – is the database still in sync with the files?
@@ -12,11 +10,6 @@
* * Overwrite the database with the local files? This is a local files editor after all.
* * Display a warning in wp-admin and let the user decide what to do?
* @TODO: Consider tricky scenarios – moving a parent to trash and then restoring it.
- * @TODO: Consider using hierarchical taxonomy to model the directory/file structure – instead of
- * using the post_parent field. Could be more flexible (no need for index.md files) and require
- * less complex operations in the code (no need to update a subtree of posts when moving a post,
- * no need to periodically "flatten" the parent directory).
- * @TODO: Maybe use Playground's FilePickerTree React component? Or re-implement it with interactivity API?
*/
use WordPress\Filesystem\WP_Local_Filesystem;
@@ -143,6 +136,7 @@ static public function initialize() {
// Register hooks
register_activation_hook( __FILE__, array(self::class, 'import_static_pages') );
+
add_action('init', function() {
self::get_fs();
self::register_post_type();
@@ -254,7 +248,11 @@ function() {
'methods' => 'GET',
'callback' => array(self::class, 'download_file_endpoint'),
'permission_callback' => function() {
- return current_user_can('edit_posts');
+ // @TODO: Restrict access to this endpoint to editors, but
+ // don't require a nonce. Nonces are troublesome for
+ // static assets that don't have a dynamic URL.
+ // return current_user_can('edit_posts');
+ return true;
},
'args' => array(
'path' => array(
@@ -478,9 +476,10 @@ static private function refresh_post_from_local_file($post) {
$converter = new WP_HTML_To_Blocks( WP_HTML_Processor::create_fragment( $content ) );
break;
case 'md':
- default:
$converter = new WP_Markdown_To_Blocks( $content );
break;
+ default:
+ return false;
}
$converter->convert();
@@ -489,6 +488,7 @@ static private function refresh_post_from_local_file($post) {
$metadata[$key] = $value[0];
}
$new_content = $converter->get_block_markup();
+ $new_content = self::wordpressify_static_assets_urls($new_content);
$updated = wp_update_post(array(
'ID' => $post_id,
@@ -527,9 +527,10 @@ static private function convert_post_to_string($path, $post) {
case 'html':
case 'xhtml':
case 'md':
- default:
$converter = new WP_Blocks_To_Markdown( $content, $metadata );
break;
+ default:
+ return '';
}
$converter->convert();
return $converter->get_result();
@@ -577,6 +578,35 @@ static private function unwordpressify_static_assets_urls($content) {
return $p->get_updated_html();
}
+ /**
+ * Convert references to files served via path to the
+ * corresponding download_file_endpoint references.
+ */
+ static private function wordpressify_static_assets_urls($content) {
+ $site_url = WP_URL::parse(get_site_url());
+ $expected_endpoint_path = '/wp-json/static-files-editor/v1/download-file';
+ $p = WP_Block_Markup_Url_Processor::create_from_html($content, $site_url);
+ while($p->next_url()) {
+ $url = $p->get_parsed_url();
+ if(!is_child_url_of($url, get_site_url())) {
+ continue;
+ }
+
+ // @TODO: Also work with tags, account
+ // for .md and directory links etc.
+ if($p->get_tag() !== 'IMG') {
+ continue;
+ }
+
+ $new_url = WP_URL::parse($url->pathname, $site_url);
+ $new_url->pathname = $expected_endpoint_path;
+ $new_url->searchParams->set('path', $p->get_raw_url());
+ $p->set_raw_url($new_url->__toString());
+ }
+
+ return $p->get_updated_html();
+ }
+
static public function get_local_files_tree($subdirectory = '') {
$tree = [];
$fs = self::get_fs();
@@ -589,21 +619,33 @@ static public function get_local_files_tree($subdirectory = '') {
'fields' => 'id=>meta'
));
- $path_to_post_id = array();
+ $path_to_post = array();
foreach($file_posts as $post) {
$file_path = get_post_meta($post->ID, 'local_file_path', true);
if ($file_path) {
- $path_to_post_id[$file_path] = $post->ID;
+ $path_to_post[$file_path] = $post;
+ }
+ }
+
+ $attachments = get_posts(array(
+ 'post_type' => 'attachment',
+ 'posts_per_page' => -1,
+ 'meta_key' => 'local_file_path',
+ ));
+ foreach($attachments as $attachment) {
+ $attachment_path = get_post_meta($attachment->ID, 'local_file_path', true);
+ if ($attachment_path) {
+ $path_to_post[$attachment_path] = $attachment;
}
}
$base_dir = $subdirectory ? $subdirectory : '/';
- self::build_local_file_tree_recursive($fs, $base_dir, $tree, $path_to_post_id);
+ self::build_local_file_tree_recursive($fs, $base_dir, $tree, $path_to_post);
return $tree;
}
- static private function build_local_file_tree_recursive($fs, $dir, &$tree, $path_to_post_id) {
+ static private function build_local_file_tree_recursive($fs, $dir, &$tree, $path_to_post) {
$items = $fs->ls($dir);
if ($items === false) {
return;
@@ -633,15 +675,16 @@ static private function build_local_file_tree_recursive($fs, $dir, &$tree, $path
// Recursively build children
$last_index = count($tree) - 1;
- self::build_local_file_tree_recursive($fs, $path, $tree[$last_index]['children'], $path_to_post_id);
+ self::build_local_file_tree_recursive($fs, $path, $tree[$last_index]['children'], $path_to_post);
} else {
$node = array(
'type' => 'file',
'name' => $item,
);
- if (isset($path_to_post_id[$path])) {
- $node['post_id'] = $path_to_post_id[$path];
+ if (isset($path_to_post[$path])) {
+ $node['post_id'] = $path_to_post[$path]->ID;
+ $node['post_type'] = $path_to_post[$path]->post_type;
}
$tree[] = $node;
@@ -655,10 +698,6 @@ static private function build_local_file_tree_recursive($fs, $dir, &$tree, $path
* @TODO: Error handling
*/
static public function import_static_pages() {
- if ( ! is_dir( WP_STATIC_PAGES_DIR ) ) {
- return;
- }
-
if ( defined('WP_IMPORTING') && WP_IMPORTING ) {
return;
}
@@ -671,20 +710,26 @@ static public function import_static_pages() {
// Prevent ID conflicts
self::reset_db_data();
- self::do_import_static_pages();
+ return self::do_import_static_pages();
}
static private function do_import_static_pages($options = array()) {
+ $fs = $options['filesystem'] ?? self::get_fs();
$importer = WP_Stream_Importer::create(
- function () use ($options) {
+ function () use ($fs, $options) {
return new WP_Filesystem_Entity_Reader(
- self::get_fs(),
+ $fs,
array(
'post_type' => WP_LOCAL_FILE_POST_TYPE,
'post_tree_options' => $options['post_tree_options'] ?? array(),
)
);
- }
+ },
+ array(
+ 'attachment_downloader_options' => array(
+ 'source_from_filesystem' => $fs,
+ ),
+ )
);
$import_session = WP_Import_Session::create(
@@ -693,7 +738,7 @@ function () use ($options) {
)
);
- data_liberation_import_step( $import_session, $importer );
+ return data_liberation_import_step( $import_session, $importer );
}
static private function register_post_type() {
@@ -910,33 +955,13 @@ static public function create_files_endpoint($request) {
}
}
- $importer = WP_Stream_Importer::create(
- function () use ($parent_id, $uploaded_fs) {
- return new WP_Filesystem_Entity_Reader(
- $uploaded_fs,
- array(
- 'post_type' => WP_LOCAL_FILE_POST_TYPE,
- 'post_tree_options' => array(
- 'root_parent_id' => $parent_id,
- 'create_index_pages' => false,
- ),
- )
- );
- },
- array(
- 'attachment_downloader_options' => array(
- 'source_from_filesystem' => $uploaded_fs,
- ),
- )
- );
-
- $import_session = WP_Import_Session::create(
- array (
- 'data_source' => 'static_pages'
- )
- );
-
- $result = data_liberation_import_step( $import_session, $importer );
+ $result = self::do_import_static_pages(array(
+ 'filesystem' => $uploaded_fs,
+ 'post_tree_options' => array(
+ 'root_parent_id' => $parent_id,
+ 'create_index_pages' => false,
+ ),
+ ));
if(is_wp_error($result)) {
return $result;
}
diff --git a/packages/playground/data-liberation-static-files-editor/ui/src/components/FilePickerTree/index.tsx b/packages/playground/data-liberation-static-files-editor/ui/src/components/FilePickerTree/index.tsx
index dbde7e7f65..35a3f3ff3a 100644
--- a/packages/playground/data-liberation-static-files-editor/ui/src/components/FilePickerTree/index.tsx
+++ b/packages/playground/data-liberation-static-files-editor/ui/src/components/FilePickerTree/index.tsx
@@ -97,17 +97,9 @@ type FilePickerContextType = {
onEditedNodeCancel: () => void;
onNodeDeleted: (path: string) => void;
startRenaming: (path: string) => void;
- onDragStart: (
- e: React.DragEvent,
- path: string,
- type: 'file' | 'folder'
- ) => void;
- onDragOver: (
- e: React.DragEvent,
- path: string,
- type: 'file' | 'folder'
- ) => void;
- onDrop: (e: React.DragEvent, path: string, type: 'file' | 'folder') => void;
+ onDragStart: (e: React.DragEvent, path: string, node: FileNode) => void;
+ onDragOver: (e: React.DragEvent, path: string, node: FileNode) => void;
+ onDrop: (e: React.DragEvent, path: string, node: FileNode) => void;
onDragEnd: () => void;
dragState: DragState | null;
};
@@ -301,6 +293,7 @@ export const FilePickerTree: React.FC`
- );
+ if ('post_type' in node && node.post_type === 'attachment') {
+ // Create DOM elements to safely construct HTML
+
+ const figure = document.createElement('figure');
+ figure.className = 'wp-block-image size-full';
+
+ const img = document.createElement('img');
+ img.src = url;
+ img.alt = '';
+ img.className = `wp-image-${node.post_id}`;
+
+ figure.appendChild(img);
+
+ // Wrap in WordPress block comments
+ // For dragging & dropping into the editor canvas
+ e.dataTransfer.setData(
+ 'text/html',
+ `',
+ ''
+ )},"sizeSlug":"full","linkDestination":"none"} -->
+${figure.outerHTML}
+`
+ );
+ } else if (isStaticAssetPath(path)) {
+ const img = document.createElement('img');
+ img.src = url;
+ img.alt = filename;
+ e.dataTransfer.setData('text/html', img.outerHTML);
+ }
}
};
diff --git a/packages/playground/data-liberation/src/entity-readers/WP_Filesystem_Entity_Reader.php b/packages/playground/data-liberation/src/entity-readers/WP_Filesystem_Entity_Reader.php
index 4fe8860e16..50c10799c0 100644
--- a/packages/playground/data-liberation/src/entity-readers/WP_Filesystem_Entity_Reader.php
+++ b/packages/playground/data-liberation/src/entity-readers/WP_Filesystem_Entity_Reader.php
@@ -97,6 +97,14 @@ public function next_entity(): bool {
'attachment_url' => 'file://' . $post_tree_node['local_file_path'],
)
);
+ $this->entities[] = new WP_Imported_Entity(
+ 'post_meta',
+ array(
+ 'post_id' => $post_tree_node['post_id'],
+ 'key' => 'local_file_path',
+ 'value' => $post_tree_node['local_file_path'],
+ )
+ );
// We're done emiting the entity.
// wp_generate_attachment_metadata() et al. will be called by the
// importer at the database insertion step.
diff --git a/packages/playground/data-liberation/src/import/WP_Entity_Importer.php b/packages/playground/data-liberation/src/import/WP_Entity_Importer.php
index 23fd1f1756..649b777323 100644
--- a/packages/playground/data-liberation/src/import/WP_Entity_Importer.php
+++ b/packages/playground/data-liberation/src/import/WP_Entity_Importer.php
@@ -451,6 +451,8 @@ public function import_post( $data ) {
return false;
}
+ $meta = array();
+
$original_id = isset( $data['post_id'] ) ? (int) $data['post_id'] : 0;
$parent_id = isset( $data['post_parent'] ) ? (int) $data['post_parent'] : 0;
@@ -548,6 +550,12 @@ public function import_post( $data ) {
$postdata[ $key ] = $data[ $key ];
}
+ if(!isset($postdata['post_date'])) {
+ $postdata['post_date'] = date('Y-m-d H:i:s');
+ }
+ if(!isset($postdata['post_date_gmt'])) {
+ $postdata['post_date_gmt'] = date('Y-m-d H:i:s');
+ }
$postdata = apply_filters( 'wp_import_post_data_processed', $postdata, $data );
@@ -782,7 +790,6 @@ protected function process_attachment( $post, $meta ) {
* @TODO: Explore other interfaces for attachment import.
*/
public function import_attachment( $filepath, $post_id ) {
-
$filename = basename( $filepath );
// Check if attachment with this guid already exists
$existing_attachment = get_posts(
@@ -863,7 +870,7 @@ public function import_post_meta( $meta_item, $post_id ) {
$value = maybe_unserialize( $meta_item['value'] );
}
- add_post_meta( $post_id, $key, $value );
+ update_post_meta( $post_id, $key, $value );
do_action( 'import_post_meta', $post_id, $key, $value );
// if the post has a featured image, take note of this in case of remap