release: v1.0.2
- Fix WordPress.org plugin review issues (nonce verification, input sanitization, output escaping) - Embed page uses wp_enqueue_style/wp_enqueue_script with wp_head/wp_footer - Update plugin author to NoSchmarrn.dev - Shorten readme.txt short description to ≤150 chars - Add GitHub Actions release workflow - Add .gitignore
This commit is contained in:
parent
fb206850d5
commit
066414724b
17 changed files with 166 additions and 132 deletions
32
.github/workflows/release.yml
vendored
Normal file
32
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version from tag
|
||||
id: version
|
||||
run: |
|
||||
VERSION="${GITHUB_REF#refs/tags/v}"
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create ZIP
|
||||
run: zip -r breznflow.zip breznflow/
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
name: "BreznFlow v${{ steps.version.outputs.version }}"
|
||||
files: breznflow.zip
|
||||
generate_release_notes: true
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
vendor/
|
||||
*.zip
|
||||
/node_modules/
|
||||
.claude/
|
||||
*.log
|
||||
|
|
@ -3,7 +3,7 @@
|
|||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
🇬🇧 [English version → README.md](README.md)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
🇩🇪 [Deutsche Version → README.de.md](README.de.md)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
Theme Name: Brezn
|
||||
Theme ID: brezn
|
||||
Description: Biergarten bei Nacht – dark amber canvas, Bavarian gold nodes, royal blue connections, state-seal red logic accents.
|
||||
Author: BreznFlow
|
||||
*/
|
||||
|
||||
.breznflow-wrap[data-theme="brezn"],
|
||||
.breznflow-modal-overlay[data-theme="brezn"],
|
||||
.breznflow-fs-portal[data-theme="brezn"] {
|
||||
--breznflow-canvas-bg: #0d0800;
|
||||
--breznflow-node-bg: #1e1300;
|
||||
--breznflow-node-text: #f5c800;
|
||||
--breznflow-node-sub: #8a6c00;
|
||||
--breznflow-node-border: #3a2a00;
|
||||
--breznflow-connection: #0066b3;
|
||||
--breznflow-connection-hover: #3399ff;
|
||||
--breznflow-toolbar-bg: #080500;
|
||||
--breznflow-toolbar-text: #f5c800;
|
||||
--breznflow-toolbar-border: #2a1f00;
|
||||
--breznflow-panel-bg: #080500;
|
||||
--breznflow-panel-text: #e8d070;
|
||||
--breznflow-panel-border: #2a1f00;
|
||||
--breznflow-btn-bg: #0066b3;
|
||||
--breznflow-btn-text: #ffffff;
|
||||
--breznflow-btn-border: #0077cc;
|
||||
--breznflow-btn-hover-bg: #0077cc;
|
||||
--breznflow-action-bar-bg: #080500;
|
||||
--breznflow-action-bar-border: #2a1f00;
|
||||
--breznflow-modal-overlay-bg: rgba(5, 3, 0, 0.88);
|
||||
--breznflow-modal-bg: #100c00;
|
||||
--breznflow-modal-border: #3a2a00;
|
||||
--breznflow-modal-title: #f5c800;
|
||||
--breznflow-modal-text: #e8d070;
|
||||
--breznflow-modal-sub: #8a6c00;
|
||||
--breznflow-modal-close: #8a6c00;
|
||||
--breznflow-modal-secondary-bg: #0d0800;
|
||||
--breznflow-modal-secondary-border: #3a2a00;
|
||||
--breznflow-modal-code-bg: #050300;
|
||||
--breznflow-tooltip-bg: rgba(5, 3, 0, 0.95);
|
||||
--breznflow-tooltip-text: #f5c800;
|
||||
--breznflow-fullscreen-overlay-bg: rgba(0, 0, 0, 0.92);
|
||||
--breznflow-minimap-bg: rgba(13, 8, 0, 0.9);
|
||||
--breznflow-minimap-border: #3a2a00;
|
||||
--breznflow-color-trigger: #22c55e;
|
||||
--breznflow-color-http: #0066b3;
|
||||
--breznflow-color-code: #f5c800;
|
||||
--breznflow-color-logic: #cc2200;
|
||||
--breznflow-color-database: #b88a00;
|
||||
--breznflow-color-ai: #ff8c00;
|
||||
--breznflow-color-fallback: #5b9bc4;
|
||||
}
|
||||
|
|
@ -8,12 +8,12 @@
|
|||
* Plugin Name: BreznFlow
|
||||
* Plugin URI: https://breznflow.com/
|
||||
* Description: Display n8n automation workflows with an interactive SVG diagram, node detail panel, and sensitive data masking.
|
||||
* Version: 1.0.1
|
||||
* Version: 1.0.2
|
||||
* Requires at least: 6.0
|
||||
* Requires PHP: 8.0
|
||||
* Author: mifupa
|
||||
* Author URI: https://mifupa.com/
|
||||
* License: GPL-2.0-or-later
|
||||
* Author: NoSchmarrn.dev
|
||||
* Author URI: https://noschmarrn.dev/
|
||||
* License: GPLv2 or later
|
||||
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
* Text Domain: breznflow
|
||||
* Domain Path: /languages
|
||||
|
|
@ -23,7 +23,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
exit;
|
||||
}
|
||||
|
||||
define( 'BREZNFLOW_VERSION', '1.0.1' );
|
||||
define( 'BREZNFLOW_VERSION', '1.0.2' );
|
||||
define( 'BREZNFLOW_FILE', __FILE__ );
|
||||
define( 'BREZNFLOW_DIR', plugin_dir_path( __FILE__ ) );
|
||||
define( 'BREZNFLOW_URL', plugin_dir_url( __FILE__ ) );
|
||||
|
|
|
|||
|
|
@ -170,7 +170,18 @@ class AdminMenu {
|
|||
* @return void
|
||||
*/
|
||||
public function render_wizard(): void {
|
||||
$step = isset( $_GET['step'] ) ? (int) $_GET['step'] : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$step = isset( $_GET['step'] ) ? (int) $_GET['step'] : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- step is cast to int and only selects a template; nonce verified below for steps with sensitive params.
|
||||
|
||||
// Steps 2 and 3 receive post_id via GET — verify nonce to prevent CSRF.
|
||||
if ( $step >= 2 ) {
|
||||
if (
|
||||
! isset( $_GET['_wpnonce'] )
|
||||
|| ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'breznflow_wizard_step' )
|
||||
) {
|
||||
wp_die( esc_html__( 'Security check failed. Please try again.', 'breznflow' ), 403 );
|
||||
}
|
||||
}
|
||||
|
||||
switch ( $step ) {
|
||||
case 2:
|
||||
require BREZNFLOW_DIR . 'includes/Admin/views/wizard-step-2.php';
|
||||
|
|
|
|||
|
|
@ -52,11 +52,16 @@ class ThemesPage {
|
|||
wp_die( esc_html__( 'Insufficient permissions.', 'breznflow' ) );
|
||||
}
|
||||
|
||||
$file = $_FILES['breznflow_theme_file']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
|
||||
// Access individual $_FILES keys with explicit sanitization.
|
||||
$file_name = isset( $_FILES['breznflow_theme_file']['name'] )
|
||||
? sanitize_file_name( wp_unslash( $_FILES['breznflow_theme_file']['name'] ) )
|
||||
: '';
|
||||
$file_tmp = isset( $_FILES['breznflow_theme_file']['tmp_name'] )
|
||||
? sanitize_text_field( $_FILES['breznflow_theme_file']['tmp_name'] ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- tmp_name is a server-generated path, not user input.
|
||||
: '';
|
||||
|
||||
// Verify file extension.
|
||||
$filename = isset( $file['name'] ) ? sanitize_file_name( $file['name'] ) : '';
|
||||
if ( ! str_ends_with( $filename, '.breznflow.json' ) && ! str_ends_with( $filename, '.json' ) ) {
|
||||
if ( ! str_ends_with( $file_name, '.breznflow.json' ) && ! str_ends_with( $file_name, '.json' ) ) {
|
||||
wp_safe_redirect(
|
||||
add_query_arg(
|
||||
array(
|
||||
|
|
@ -69,7 +74,7 @@ class ThemesPage {
|
|||
exit;
|
||||
}
|
||||
|
||||
if ( ! isset( $file['tmp_name'] ) || ! is_uploaded_file( $file['tmp_name'] ) ) {
|
||||
if ( '' === $file_tmp || ! is_uploaded_file( $file_tmp ) ) {
|
||||
wp_safe_redirect(
|
||||
add_query_arg(
|
||||
array(
|
||||
|
|
@ -83,7 +88,7 @@ class ThemesPage {
|
|||
}
|
||||
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
|
||||
$raw = file_get_contents( $file['tmp_name'] );
|
||||
$raw = file_get_contents( $file_tmp );
|
||||
if ( false === $raw ) {
|
||||
wp_safe_redirect(
|
||||
add_query_arg(
|
||||
|
|
|
|||
|
|
@ -83,8 +83,12 @@ class WizardPage {
|
|||
wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'breznflow' ) ) );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- verified above; sanitize_textarea_field() is not used because strip_tags() corrupts JSON (strips HTML inside string values); sanitization happens after json_decode() in WorkflowSanitizer
|
||||
$raw = isset( $_POST['json'] ) ? trim( wp_unslash( (string) $_POST['json'] ) ) : '';
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitize_textarea_field() cannot be used: its internal strip_tags() corrupts JSON containing HTML-like string values. Input is validated structurally via json_decode() in WorkflowValidator and sanitized field-by-field in WorkflowSanitizer.
|
||||
$raw = isset( $_POST['json'] ) ? wp_unslash( $_POST['json'] ) : '';
|
||||
if ( ! is_string( $raw ) ) {
|
||||
$raw = '';
|
||||
}
|
||||
$raw = trim( $raw );
|
||||
|
||||
if ( '' === $raw ) {
|
||||
wp_send_json_error( array( 'message' => __( 'No JSON provided.', 'breznflow' ) ) );
|
||||
|
|
@ -191,8 +195,12 @@ class WizardPage {
|
|||
wp_die( esc_html__( 'Insufficient permissions.', 'breznflow' ) );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- verified above; sanitize_textarea_field() is not used because strip_tags() corrupts JSON (strips HTML inside string values); sanitization happens after json_decode() in WorkflowSanitizer
|
||||
$raw = isset( $_POST['breznflow_json'] ) ? trim( wp_unslash( (string) $_POST['breznflow_json'] ) ) : '';
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitize_textarea_field() cannot be used: its internal strip_tags() corrupts JSON containing HTML-like string values. Input is validated structurally via json_decode() in WorkflowValidator and sanitized field-by-field in WorkflowSanitizer.
|
||||
$raw = isset( $_POST['breznflow_json'] ) ? wp_unslash( $_POST['breznflow_json'] ) : '';
|
||||
if ( ! is_string( $raw ) ) {
|
||||
$raw = '';
|
||||
}
|
||||
$raw = trim( $raw );
|
||||
|
||||
if ( '' === $raw ) {
|
||||
wp_safe_redirect(
|
||||
|
|
@ -299,6 +307,7 @@ class WizardPage {
|
|||
'page' => 'breznflow-add',
|
||||
'step' => '2',
|
||||
'post_id' => $post_id,
|
||||
'_wpnonce' => wp_create_nonce( 'breznflow_wizard_step' ),
|
||||
),
|
||||
admin_url( 'admin.php' )
|
||||
)
|
||||
|
|
@ -371,6 +380,7 @@ class WizardPage {
|
|||
'page' => 'breznflow-add',
|
||||
'step' => '3',
|
||||
'post_id' => $post_id,
|
||||
'_wpnonce' => wp_create_nonce( 'breznflow_wizard_step' ),
|
||||
),
|
||||
admin_url( 'admin.php' )
|
||||
)
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@ class WorkflowListTable extends \WP_List_Table {
|
|||
'page' => 'breznflow-add',
|
||||
'step' => '2',
|
||||
'post_id' => $item->ID,
|
||||
'_wpnonce' => wp_create_nonce( 'breznflow_wizard_step' ),
|
||||
),
|
||||
admin_url( 'admin.php' )
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
}
|
||||
// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- template file, not global scope
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.WP.GlobalVariablesOverride.Prohibited -- nonce verified in AdminMenu::render_wizard() before this template loads.
|
||||
$post_id = isset( $_GET['post_id'] ) ? (int) $_GET['post_id'] : 0;
|
||||
$workflow = $post_id > 0 ? get_post( $post_id ) : null;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
}
|
||||
// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- template file, not global scope
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.WP.GlobalVariablesOverride.Prohibited -- nonce verified in AdminMenu::render_wizard() before this template loads.
|
||||
$post_id = isset( $_GET['post_id'] ) ? (int) $_GET['post_id'] : 0;
|
||||
$workflow = $post_id > 0 ? get_post( $post_id ) : null;
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ $preview_theme = in_array( $saved_theme, $allowed_themes, true ) ? $saved_them
|
|||
}
|
||||
$bf_custom_css = \BreznFlow\Features\ThemeRegistry::get_custom_theme_css();
|
||||
if ( $bf_custom_css ) {
|
||||
wp_add_inline_style( 'breznflow-renderer', $bf_custom_css );
|
||||
wp_add_inline_style( 'breznflow-renderer', wp_strip_all_tags( $bf_custom_css ) );
|
||||
}
|
||||
wp_localize_script(
|
||||
'breznflow-renderer',
|
||||
|
|
@ -163,6 +163,7 @@ $preview_theme = in_array( $saved_theme, $allowed_themes, true ) ? $saved_them
|
|||
'page' => 'breznflow-add',
|
||||
'step' => '2',
|
||||
'post_id' => $post_id,
|
||||
'_wpnonce' => wp_create_nonce( 'breznflow_wizard_step' ),
|
||||
),
|
||||
admin_url( 'admin.php' )
|
||||
)
|
||||
|
|
|
|||
|
|
@ -36,11 +36,13 @@ class DownloadHandler {
|
|||
* @return void
|
||||
*/
|
||||
public function handle_download(): void {
|
||||
if ( ! isset( $_GET['breznflow_download'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- public read-only download endpoint; serves only published data gated by global allow_download setting + per-post _breznflow_show_download meta. No state change.
|
||||
if ( ! isset( $_GET['breznflow_download'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_id = (int) $_GET['breznflow_download']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- public read-only endpoint (see above).
|
||||
$post_id = (int) $_GET['breznflow_download'];
|
||||
|
||||
if ( $post_id <= 0 ) {
|
||||
status_header( 400 );
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class EmbedHandler {
|
|||
* @return void
|
||||
*/
|
||||
public function handle_embed(): void {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- public read-only embed endpoint, no state change; only serves published data.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- public read-only embed endpoint; serves only published data gated by global + per-post settings. No state change.
|
||||
if ( ! isset( $_GET['breznflow_embed'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -80,11 +80,11 @@ class EmbedHandler {
|
|||
}
|
||||
|
||||
$allowed_themes = \BreznFlow\Features\ThemeRegistry::get_theme_ids();
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- public read-only embed endpoint; theme is validated against whitelist.
|
||||
$url_theme = isset( $_GET['theme'] ) ? sanitize_text_field( wp_unslash( $_GET['theme'] ) ) : '';
|
||||
$theme = in_array( $url_theme, $allowed_themes, true ) ? $url_theme : ( $settings['default_theme'] ?? 'dark' );
|
||||
$theme = in_array( $theme, $allowed_themes, true ) ? $theme : 'dark';
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- public read-only embed page, no state change.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- public read-only embed endpoint; boolean flag, sanitized.
|
||||
$show_minimap_embed = isset( $_GET['minimap'] ) ? ( '0' !== sanitize_text_field( wp_unslash( $_GET['minimap'] ) ) ) : true;
|
||||
|
||||
$body_bgs = array(
|
||||
|
|
@ -96,19 +96,20 @@ class EmbedHandler {
|
|||
);
|
||||
$body_bg = $body_bgs[ $theme ] ?? '#1a1a2e';
|
||||
|
||||
// Hide admin bar on standalone embed page.
|
||||
show_admin_bar( false );
|
||||
|
||||
// Set headers.
|
||||
header( 'Content-Type: text/html; charset=utf-8' );
|
||||
header( 'X-Robots-Tag: noindex, nofollow' );
|
||||
header( 'X-Content-Type-Options: nosniff' );
|
||||
header_remove( 'X-Frame-Options' );
|
||||
|
||||
$article_url = esc_url( get_permalink( $post_id ) );
|
||||
$article_url = get_permalink( $post_id );
|
||||
$anchor_id = 'breznflow-' . $post_id;
|
||||
$blog_name = esc_html( get_bloginfo( 'name' ) );
|
||||
$blog_url = esc_url( home_url( '/' ) );
|
||||
$title = esc_html( $post->post_title );
|
||||
$css_url = esc_url( BREZNFLOW_URL . 'assets/renderer.css' ) . '?v=' . BREZNFLOW_VERSION;
|
||||
$js_url = esc_url( BREZNFLOW_URL . 'assets/renderer.js' ) . '?v=' . BREZNFLOW_VERSION;
|
||||
$blog_name = get_bloginfo( 'name' );
|
||||
$blog_url = home_url( '/' );
|
||||
$title = $post->post_title;
|
||||
|
||||
$inline_data = array(
|
||||
array(
|
||||
|
|
@ -131,55 +132,60 @@ class EmbedHandler {
|
|||
),
|
||||
);
|
||||
|
||||
$icons_json = wp_json_encode( Features\NodeTypeRegistry::get_registry() );
|
||||
$data_json = wp_json_encode( $inline_data );
|
||||
$i18n_json = wp_json_encode( Shortcode::get_js_i18n() );
|
||||
// Enqueue renderer assets via WordPress enqueue system.
|
||||
wp_enqueue_style( 'breznflow-renderer', BREZNFLOW_URL . 'assets/renderer.css', array(), BREZNFLOW_VERSION );
|
||||
wp_enqueue_script( 'breznflow-renderer', BREZNFLOW_URL . 'assets/renderer.js', array(), BREZNFLOW_VERSION, true );
|
||||
|
||||
foreach ( \BreznFlow\Features\ThemeRegistry::BUILTIN as $bf_embed_id => $bf_embed_name ) {
|
||||
wp_enqueue_style(
|
||||
'breznflow-theme-' . $bf_embed_id,
|
||||
\BreznFlow\Features\ThemeRegistry::get_builtin_url( $bf_embed_id ),
|
||||
array( 'breznflow-renderer' ),
|
||||
BREZNFLOW_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
$embed_custom_css = \BreznFlow\Features\ThemeRegistry::get_custom_theme_css();
|
||||
if ( $embed_custom_css ) {
|
||||
wp_add_inline_style( 'breznflow-renderer', wp_strip_all_tags( $embed_custom_css ) );
|
||||
}
|
||||
|
||||
$embed_layout_css = '*, *::before, *::after { box-sizing: border-box; }'
|
||||
. 'html, body { margin: 0; padding: 0; height: 100%; background: ' . esc_attr( $body_bg ) . '; color: #e0e0e0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }'
|
||||
. 'body { display: flex; flex-direction: column; }'
|
||||
. '#breznflow-embed-viewer { flex: 1; min-height: 0; }'
|
||||
. '#breznflow-embed-viewer .breznflow-embed { height: 100%; border-radius: 0; border: none; }'
|
||||
. '#breznflow-embed-footer { padding: 6px 12px; background: #111; border-top: 1px solid #333; font-size: 11px; color: #888; display: flex; align-items: center; gap: 8px; flex-shrink: 0; }'
|
||||
. '#breznflow-embed-footer a { color: #aaa; text-decoration: none; }'
|
||||
. '#breznflow-embed-footer a:hover { color: #e0e0e0; }';
|
||||
wp_add_inline_style( 'breznflow-renderer', $embed_layout_css );
|
||||
|
||||
wp_localize_script( 'breznflow-renderer', 'breznflowData', $inline_data );
|
||||
wp_localize_script( 'breznflow-renderer', 'breznflowIcons', Features\NodeTypeRegistry::get_registry() );
|
||||
wp_localize_script( 'breznflow-renderer', 'breznflowI18n', Shortcode::get_js_i18n() );
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- intentional standalone HTML output; all dynamic values escaped above
|
||||
?><!DOCTYPE html>
|
||||
<html lang="<?php echo esc_attr( get_bloginfo( 'language' ) ); ?>" data-theme="<?php echo esc_attr( $theme ); ?>">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<title><?php echo $title; ?></title>
|
||||
<link rel="stylesheet" href="<?php echo $css_url; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet -- standalone embed page, no wp_head(). ?>">
|
||||
<?php foreach ( \BreznFlow\Features\ThemeRegistry::BUILTIN as $bf_embed_id => $bf_embed_name ) : ?>
|
||||
<link rel="stylesheet" href="<?php echo esc_url( \BreznFlow\Features\ThemeRegistry::get_builtin_url( $bf_embed_id ) ) . '?v=' . BREZNFLOW_VERSION; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet -- standalone embed page, no wp_head(). ?>">
|
||||
<?php endforeach; ?>
|
||||
<?php $embed_custom_css = \BreznFlow\Features\ThemeRegistry::get_custom_theme_css(); if ( $embed_custom_css ) : ?>
|
||||
<style><?php echo wp_strip_all_tags( $embed_custom_css ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- CSS from validated color tokens, stripped of HTML tags. ?></style>
|
||||
<?php endif; ?>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; height: 100%; background: <?php echo esc_attr( $body_bg ); ?>; color: #e0e0e0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
|
||||
body { display: flex; flex-direction: column; }
|
||||
#breznflow-embed-viewer { flex: 1; min-height: 0; }
|
||||
#breznflow-embed-viewer .breznflow-embed { height: 100%; border-radius: 0; border: none; }
|
||||
#breznflow-embed-footer { padding: 6px 12px; background: #111; border-top: 1px solid #333; font-size: 11px; color: #888; display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
|
||||
#breznflow-embed-footer a { color: #aaa; text-decoration: none; }
|
||||
#breznflow-embed-footer a:hover { color: #e0e0e0; }
|
||||
</style>
|
||||
<title><?php echo esc_html( $title ); ?></title>
|
||||
<?php wp_head(); ?>
|
||||
</head>
|
||||
<body>
|
||||
<div id="breznflow-embed-viewer">
|
||||
<div id="breznflow-wrap-<?php echo (int) $post_id; ?>" class="breznflow-embed" data-id="<?php echo (int) $post_id; ?>"></div>
|
||||
</div>
|
||||
<footer id="breznflow-embed-footer">
|
||||
<a href="<?php echo $article_url; ?>#<?php echo esc_attr( $anchor_id ); ?>"><?php echo $title; ?></a>
|
||||
<a href="<?php echo esc_url( $article_url ); ?>#<?php echo esc_attr( $anchor_id ); ?>"><?php echo esc_html( $title ); ?></a>
|
||||
<span>•</span>
|
||||
<span><?php esc_html_e( 'Source:', 'breznflow' ); ?> <a href="<?php echo $blog_url; ?>"><?php echo $blog_name; ?></a></span>
|
||||
<span><?php esc_html_e( 'Source:', 'breznflow' ); ?> <a href="<?php echo esc_url( $blog_url ); ?>"><?php echo esc_html( $blog_name ); ?></a></span>
|
||||
</footer>
|
||||
<script>
|
||||
var breznflowData = <?php echo $data_json; ?>;
|
||||
var breznflowIcons = <?php echo $icons_json; ?>;
|
||||
var breznflowI18n = <?php echo $i18n_json; ?>;
|
||||
</script>
|
||||
<script src="<?php echo $js_url; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript -- standalone embed page, no wp_head(). ?>"></script>
|
||||
<?php wp_footer(); ?>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
// phpcs:enable
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,15 +88,15 @@ class ThemeRegistry {
|
|||
$css = '';
|
||||
|
||||
foreach ( $themes as $theme ) {
|
||||
$id = $theme['id'];
|
||||
$id = sanitize_key( $theme['id'] );
|
||||
$sel = '.breznflow-wrap[data-theme="' . $id . '"],'
|
||||
. '.breznflow-modal-overlay[data-theme="' . $id . '"],'
|
||||
. '.breznflow-fs-portal[data-theme="' . $id . '"]';
|
||||
|
||||
$css .= $sel . '{';
|
||||
foreach ( $theme['tokens'] as $token => $value ) {
|
||||
$var = '--breznflow-' . str_replace( '_', '-', $token );
|
||||
$css .= $var . ':' . $value . ';';
|
||||
$var = '--breznflow-' . str_replace( '_', '-', sanitize_key( $token ) );
|
||||
$css .= $var . ':' . esc_attr( $value ) . ';';
|
||||
}
|
||||
$css .= '}';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -311,7 +311,7 @@ class Shortcode {
|
|||
// Output custom themes as inline CSS.
|
||||
$custom_css = ThemeRegistry::get_custom_theme_css();
|
||||
if ( $custom_css ) {
|
||||
wp_add_inline_style( 'breznflow-renderer', $custom_css );
|
||||
wp_add_inline_style( 'breznflow-renderer', wp_strip_all_tags( $custom_css ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ Contributors: mifupadev
|
|||
Tags: n8n, workflow, automation, diagram, svg
|
||||
Requires at least: 6.0
|
||||
Tested up to: 6.9
|
||||
Stable tag: 1.0.1
|
||||
Stable tag: 1.0.2
|
||||
Requires PHP: 8.0
|
||||
License: GPL-2.0-or-later
|
||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
Display n8n automation workflows as interactive SVG diagrams in WordPress posts and pages — with zoom, pan, node detail panels, and automatic sensitive data masking.
|
||||
Display n8n workflows as interactive SVG diagrams with zoom, pan, node details, and automatic secret masking.
|
||||
|
||||
== Description ==
|
||||
|
||||
|
|
@ -158,6 +158,16 @@ For security, requests to private and internal network addresses are blocked: lo
|
|||
|
||||
== Changelog ==
|
||||
|
||||
= 1.0.2 =
|
||||
* Fixed WordPress.org plugin review issues.
|
||||
* Embed page now uses wp_enqueue_style/wp_enqueue_script with wp_head/wp_footer instead of direct HTML tags.
|
||||
* Added nonce verification to wizard step navigation (steps 2 and 3).
|
||||
* Improved input sanitization for $_FILES handling in theme import.
|
||||
* Improved JSON input handling with explicit type validation.
|
||||
* Added wp_strip_all_tags() escaping for inline CSS in wp_add_inline_style() calls.
|
||||
* Added late escaping (sanitize_key, esc_attr) in custom theme CSS output.
|
||||
* Improved phpcs:ignore documentation for public read-only endpoints.
|
||||
|
||||
= 1.0.1 =
|
||||
* Fixed WordPress Plugin Check warnings for WordPress.org compliance.
|
||||
* Removed deprecated `load_plugin_textdomain()` call — translations are now loaded automatically by WordPress (since WP 4.6).
|
||||
|
|
@ -186,6 +196,9 @@ For security, requests to private and internal network addresses are blocked: lo
|
|||
|
||||
== Upgrade Notice ==
|
||||
|
||||
= 1.0.2 =
|
||||
Fixes WordPress.org plugin review issues: proper asset enqueueing, nonce verification, input sanitization, and output escaping.
|
||||
|
||||
= 1.0.1 =
|
||||
Fixes WordPress Plugin Check warnings. No functionality changes.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue