Skip to content

Commit

Permalink
feat: add iframe block (#859)
Browse files Browse the repository at this point in the history
* feat: add iframe block

add an iframe block that will let publishers to embed an iframe either by its URL or by uploading
the iframe archive

* chore(iframe block): minor UI changes

* style(iframe): fix label text

change label text form from a question to normal text

* refactor(iframe): show iframe fullscreen notice on editor

when adding a fullscreen iframe to a post we'll show a notice saying that the iframe will take over
on the content on the post page (we don't want to preview this behavior as it will not let the user
user the editor)

* fix: rebase onto master

* refactor(iframe): change archive folder name to be Newspack related

* refactor(iframe): improve iframe archive folder name readability

* refactor(iframe): improve editor iframe title

* refactor(iframe): edit functions docblock

* feat: update sidebar controls

* feat: move support link to block description

* feat: update preview icon and title

* feat(iframe-block): switch UI to follow image Upload block UI

set the UI as the Image Upload Block UI and enable getting archive assets from media library

* refactor(iframe-block): disable preview by default

* feat(iframe-block): enable setting iframe size unit

* feat(iframe): display preview button only when src is available

* feat: move unit-controls to same row

* refactor(iframe-block): add newspack- prefix to class name

* style(iframe): wording change

change Upload to Embed since we do embed a URL not upload it

* fix(iframe): ignore experimental component eslint warning

* docs(iframe): separate internal WordPress dependencies from externals

* style(iframe): fix indentation

* fix(iframe): better error messages when using iframe archive from media

* fix(iframe): do not render anything on a blank block

* fix(iframe): fix preview toggle

Toggle preview display only the first time when we upload an archive zip or embed a URL

* fix(iframe): fix error handling on form submit

handle only errors with a valid `message` key, else fallback on a generic error message

* fix(iframe): better error message when we can't unzip the assets archive

* fix(iframe): fix endpoint method

delete method should be set as Delete not as a Post/Put/Patch

* refactor(iframe): remove unnecessary functions from useEffect deps list

* fix(iframe): fix deleting archive folder when needed

* fix(iframe): do no render popups when the iframe is fullscreen

the iframe will take over the popup prompt so we don't need to render it

* refactor(iframe): clean edit from unnecessary states

* refactor(iframe): manage block attributes from shared json file

* fix(iframe): fix filter callback

* style(iframe): remove commented code

* fix(iframe): fix typing Iframe URL issue

A bug was introduced in 76f9021 where when we type the first letter in the URL field it's submitted,
moved the field state to the right place.

* style(iframe): format json file for readability

Co-authored-by: Thomas Guillot <info@thomasguillot.com>
  • Loading branch information
kariae and thomasguillot authored Oct 19, 2021
1 parent 4daf27d commit 08a2712
Show file tree
Hide file tree
Showing 14 changed files with 981 additions and 1 deletion.
2 changes: 1 addition & 1 deletion block-list.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"production": [ "author-profile", "carousel", "donate", "homepage-articles", "video-playlist" ]
"production": [ "author-profile", "carousel", "donate", "homepage-articles", "video-playlist", "iframe" ]
}
36 changes: 36 additions & 0 deletions includes/class-newspack-blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public static function init() {
add_post_type_support( 'page', 'newspack_blocks' );
add_filter( 'script_loader_tag', [ __CLASS__, 'mark_view_script_as_amp_plus_allowed' ], 10, 2 );
add_action( 'jetpack_register_gutenberg_extensions', [ __CLASS__, 'disable_jetpack_donate' ], 99 );
add_filter( 'the_content', [ __CLASS__, 'hide_post_content_when_iframe_block_is_fullscreen' ] );
}

/**
Expand All @@ -39,6 +40,41 @@ public static function mark_view_script_as_amp_plus_allowed( $tag, $handle ) {
return $tag;
}

/**
* Hide the post content when it contains an iframe block that is set to fullscreen mode.
*
* @param string $content post content from the_content hook.
* @return string the post content.
*/
public static function hide_post_content_when_iframe_block_is_fullscreen( $content ) {
if ( has_block( 'newspack-blocks/iframe' ) ) {
$blocks = parse_blocks( get_post()->post_content );

foreach ( $blocks as $block ) {
if ( 'newspack-blocks/iframe' === $block['blockName']
&& array_key_exists( 'isFullScreen', $block['attrs'] )
&& $block['attrs']['isFullScreen']
) {
// we don't need the post content since the iframe will be fullscreen.
$content = render_block( $block );

add_filter(
'body_class',
function( $classes ) {
$classes[] = 'newspack-post-with-fullscreen-iframe';
return $classes;
}
);

// we don't need to show Newspack popups since the iframe will take over them.
add_filter( 'newspack_popups_assess_has_disabled_popups', '__return_true' );
}
}
}

return $content;
}

/**
* Gather dependencies and paths needed for script enqueuing.
*
Expand Down
12 changes: 12 additions & 0 deletions newspack-blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
// REST Controller for Donate Block.
require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'src/blocks/donate/class-wp-rest-newspack-donate-controller.php';

// REST Controller for Iframe Block.
require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'src/blocks/iframe/class-wp-rest-newspack-iframe-controller.php';

/**
* Registers Articles block routes.
*/
Expand Down Expand Up @@ -57,6 +60,15 @@ function newspack_donate_block_register_rest_routes() { // phpcs:ignore WordPres
}
add_action( 'rest_api_init', 'newspack_donate_block_register_rest_routes' );

/**
* Registers Iframe block routes.
*/
function newspack_iframe_block_register_rest_routes() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound
$iframe_controller = new WP_REST_Newspack_Iframe_Controller();
$iframe_controller->register_routes();
}
add_action( 'rest_api_init', 'newspack_iframe_block_register_rest_routes' );

Newspack_Blocks::manage_view_scripts();
add_action( 'enqueue_block_editor_assets', array( 'Newspack_Blocks', 'enqueue_block_editor_assets' ) );
add_action( 'wp_enqueue_scripts', array( 'Newspack_Blocks', 'enqueue_block_styles_assets' ) );
Expand Down
26 changes: 26 additions & 0 deletions src/blocks/iframe/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "iframe",
"category": "newspack",
"attributes": {
"src": {
"type": "string",
"default": ""
},
"archiveFolder": {
"type": "string",
"default": ""
},
"height": {
"type": "string",
"default": "600px"
},
"width": {
"type": "string",
"default": "100%"
},
"isFullScreen": {
"type": "boolean",
"default": false
}
}
}
252 changes: 252 additions & 0 deletions src/blocks/iframe/class-wp-rest-newspack-iframe-controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
<?php
/**
* WP_REST_Newspack_Iframe_Controller file.
*
* @package WordPress
*/

// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound

/**
* Class WP_REST_Newspack_Iframe_Controller.
*/
class WP_REST_Newspack_Iframe_Controller extends WP_REST_Controller {
const IFRAME_UPLOAD_DIR = '/newspack_iframes/';
const IFRAME_ENTRY_FILE = 'index.html';

/**
* Constructs the controller.
*
* @access public
*/
public function __construct() {
$this->namespace = 'newspack-blocks/v1';
$this->rest_base = 'iframe';
}

/**
* Registers the necessary REST API routes.
*
* @access public
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/newspack-blocks-iframe-archive',
[
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [ $this, 'import_iframe_archive_from_uploaded_file' ],
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
],
]
);

register_rest_route(
$this->namespace,
'/newspack-blocks-iframe-archive-from-media',
[
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [ $this, 'import_iframe_archive_from_media_library' ],
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
],
]
);

register_rest_route(
$this->namespace,
'/newspack-blocks-remove-iframe-archive',
[
[
'methods' => WP_REST_Server::DELETABLE,
'callback' => [ $this, 'remove_iframe_archive' ],
'permission_callback' => function() {
return current_user_can( 'edit_posts' );
},
],
]
);
}

/**
* Receive the iframe archive, unzip it, and returns the URL and the folder name.
*
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response
*/
public function import_iframe_archive_from_uploaded_file( $request ) {
$response = [];
$files = $request->get_file_params();

if ( count( $files ) > 0 && array_key_exists( 'archive_file', $files ) ) {
$archive_file = $files['archive_file'];
$iframe_folder = pathinfo( $archive_file['name'] )['filename'] . '-' . wp_generate_password( 8, false );

$response = $this->process_iframe_archive( $request, $iframe_folder, $archive_file['tmp_name'] );
} else {
$response = new WP_Error(
'newspack_blocks',
__( 'Could not find the iframe archive on your request.', 'newspack-blocks' ),
[ 'status' => '400' ]
);
}

return rest_ensure_response( $response );
}

/**
* Receive the iframe archive, unzip it, and returns the URL and the folder name.
*
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response
*/
public function import_iframe_archive_from_media_library( $request ) {
$response = [];
$data = $request->get_body_params();

if ( array_key_exists( 'media_id', $data ) ) {
$media = get_post( $data['media_id'] );
$media_path = get_attached_file( $data['media_id'] );

if ( $media_path && 'application/zip' === $media->post_mime_type ) {
$iframe_folder = $media->post_title . '-' . wp_generate_password( 8, false );

$response = $this->process_iframe_archive( $request, $iframe_folder, $media_path );
} else {
$response = new WP_Error(
'newspack_blocks',
__( 'Please choose a valid (.zip) assets archive.', 'newspack-blocks' ),
[ 'status' => '400' ]
);
}
} else {
$response = new WP_Error(
'newspack_blocks',
__( 'Please choose a valid (.zip) assets archive.', 'newspack-blocks' ),
[ 'status' => '400' ]
);
}

return rest_ensure_response( $response );
}

/**
* Process iframe archive from uploaded zip or from media file.
*
* @param WP_REST_REQUEST $request Request object.
* @param string $iframe_folder where to extract the iframe assets.
* @param string $media_path iframe assets zip file path.
* @return WP_REST_Response
*/
private function process_iframe_archive( $request, $iframe_folder, $media_path ) {
$wp_upload_dir = wp_upload_dir();
$iframe_upload_dir = $wp_upload_dir['path'] . self::IFRAME_UPLOAD_DIR;
$iframe_path = $iframe_upload_dir . $iframe_folder;
$data = $request->get_body_params();

// create iframe directory if not existing.
if ( ! file_exists( $iframe_upload_dir ) ) {
wp_mkdir_p( $iframe_upload_dir );
}

// unzip iframe archive.
$zip = new ZipArchive();
$res = $zip->open( $media_path );

if ( true === $res ) {
$zip->extractTo( $iframe_path );
$zip->close();

// check if iframe entry file is there.
if ( file_exists( path_join( $iframe_path, self::IFRAME_ENTRY_FILE ) ) ) {
$response = [
'src' => $wp_upload_dir['url'] . self::IFRAME_UPLOAD_DIR . $iframe_folder,
'dir' => path_join( $wp_upload_dir['subdir'] . self::IFRAME_UPLOAD_DIR, $iframe_folder ),
];

// if we need to remove the previous archive, it's a good place to do it here.
if ( array_key_exists( 'archive_folder', $data ) && ! empty( $data['archive_folder'] ) ) {
$this->remove_folder( $wp_upload_dir['basedir'] . $data['archive_folder'] );
}
} else {
// check if its not a one folder archive: archive.zip > archive > index.html.
$archive_folders = array_values(
array_filter(
glob( "$iframe_path/*", GLOB_ONLYDIR ),
function( $file ) {
return false === strpos( $file, '__MACOSX' );
}
)
);

if ( 1 === count( $archive_folders ) && file_exists( path_join( $archive_folders[0], self::IFRAME_ENTRY_FILE ) ) ) {
$response = [
'src' => $wp_upload_dir['url'] . self::IFRAME_UPLOAD_DIR . $iframe_folder . DIRECTORY_SEPARATOR . basename( $archive_folders[0] ),
'dir' => path_join( $wp_upload_dir['subdir'] . self::IFRAME_UPLOAD_DIR, $iframe_folder . DIRECTORY_SEPARATOR ),
];

// if we need to remove the previous archive, it's a good place to do it here.
if ( array_key_exists( 'archive_folder', $data ) && ! empty( $data['archive_folder'] ) ) {
$this->remove_folder( $wp_upload_dir['basedir'] . $data['archive_folder'] );
}
} else {
// if not, remove the uploaded archive data and raise an error.
$this->remove_folder( $iframe_path );

$response = new WP_Error(
'newspack_blocks',
/* translators: %s: iframe entry file (e.g. index,html) */
sprintf( __( '%s was not found in the iframe archive.', 'newspack-blocks' ), self::IFRAME_ENTRY_FILE ),
[ 'status' => '400' ]
);
}
}
} else {
$response = new WP_Error(
'newspack_blocks',
__( "Could not unzip the iframe archive, make sure it's a valid .zip archive file.", 'newspack-blocks' ),
[ 'status' => '400' ]
);
}

return $response;
}

/**
* Delete iframe archive, this is called when changing the iframe source.
*
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response
*/
public function remove_iframe_archive( $request ) {
$wp_upload_dir = wp_upload_dir();
$data = $request->get_json_params();

if ( array_key_exists( 'archive_folder', $data ) && ! empty( $data['archive_folder'] ) ) {
$this->remove_folder( $wp_upload_dir['basedir'] . $data['archive_folder'] );
}

return rest_ensure_response( [] );
}

/**
* Remove a folder. Called to remove the iframe archive folder.
*
* @param string $folder folder path to remove.
* @return void
*/
private function remove_folder( $folder ) {
if ( ! class_exists( 'WP_Filesystem_Direct' ) ) {
require_once ABSPATH . '/wp-admin/includes/class-wp-filesystem-base.php';
require_once ABSPATH . '/wp-admin/includes/class-wp-filesystem-direct.php';
}

$file_system = new WP_Filesystem_Direct( false );
$file_system->rmdir( $folder, true );
}
}
Loading

0 comments on commit 08a2712

Please sign in to comment.