> */ private static array $render_queue = array(); /** * Whether frontend assets have been enqueued for this page load. * * @var bool */ private static bool $assets_enqueued = false; /** * Registers the shortcode and footer hook. * * @since 1.0.0 * @return void */ public function register(): void { add_shortcode( 'breznflow', array( $this, 'render' ) ); add_action( 'wp_footer', array( $this, 'output_script_data' ), 1 ); } /** * Renders the [breznflow] shortcode. * * @param array $atts Shortcode attributes. * @return string HTML output. */ public function render( $atts ): string { $settings = SettingsPage::get_defaults(); $saved = get_option( 'breznflow_settings', array() ); $settings = array_merge( $settings, $saved ); $atts = shortcode_atts( array( 'id' => 0, 'mode' => '', 'show_title' => '', 'show_infobox' => '', 'show_download' => '', 'show_minimap' => '', 'zoom' => '', 'max_code_lines' => '', 'preset' => '', 'show_share' => '', 'show_embed' => '', 'show_get_json' => '', 'theme' => '', ), $atts, 'breznflow' ); $post_id = (int) $atts['id']; if ( $post_id <= 0 ) { return ''; } $post = get_post( $post_id ); if ( ! $post || 'breznflow_workflow' !== $post->post_type || 'publish' !== $post->post_status ) { return ''; } $raw_json = get_post_meta( $post_id, '_breznflow_raw_json', true ); if ( ! $raw_json ) { return ''; } $workflow = json_decode( $raw_json, true ); if ( ! is_array( $workflow ) ) { return ''; } // Resolve settings from meta, overridden by shortcode attrs. $att_mode = $atts['mode']; $meta_mode = get_post_meta( $post_id, '_breznflow_default_mode', true ); $mode = $att_mode ? $att_mode : ( $meta_mode ? $meta_mode : $settings['default_mode'] ); $mode = in_array( $mode, array( 'visual', 'info', 'compact' ), true ) ? $mode : 'visual'; $zoom = '' !== $atts['zoom'] ? max( 10, min( 200, (int) $atts['zoom'] ) ) : (int) get_post_meta( $post_id, '_breznflow_default_zoom', true ); $show_title = '' !== $atts['show_title'] ? (bool) $atts['show_title'] : (bool) get_post_meta( $post_id, '_breznflow_show_title', true ); $show_infobox = '' !== $atts['show_infobox'] ? (bool) $atts['show_infobox'] : (bool) get_post_meta( $post_id, '_breznflow_show_infobox', true ); // Download: shortcode can only disable if meta has it enabled (not enable if meta disables). $meta_download = (bool) get_post_meta( $post_id, '_breznflow_show_download', true ); $global_download = (bool) $settings['allow_download']; $allow_download = $meta_download && $global_download; if ( '' !== $atts['show_download'] && ! (bool) $atts['show_download'] ) { $allow_download = false; } $allow_share = (bool) $settings['allow_share']; if ( '' !== $atts['show_share'] && ! (bool) $atts['show_share'] ) { $allow_share = false; } // Embed: dual-gate (global + per-post meta). $meta_embed = (bool) get_post_meta( $post_id, '_breznflow_show_embed', true ); $allow_embed = $meta_embed && (bool) $settings['allow_embed']; if ( '' !== $atts['show_embed'] && ! (bool) $atts['show_embed'] ) { $allow_embed = false; } $allow_get_json = (bool) $settings['allow_get_json']; if ( '' !== $atts['show_get_json'] && ! (bool) $atts['show_get_json'] ) { $allow_get_json = false; } $allowed_themes = ThemeRegistry::get_theme_ids(); $att_theme = $atts['theme']; $meta_theme = get_post_meta( $post_id, '_breznflow_default_theme', true ); $theme = $att_theme ? $att_theme : ( $meta_theme ? $meta_theme : ( $settings['default_theme'] ?? 'dark' ) ); $theme = in_array( $theme, $allowed_themes, true ) ? $theme : 'dark'; $meta_minimap = get_post_meta( $post_id, '_breznflow_show_minimap', true ); $show_minimap = '' !== $atts['show_minimap'] ? (bool) $atts['show_minimap'] : ( '' !== $meta_minimap ? (bool) $meta_minimap : true ); $max_code_lines = '' !== $atts['max_code_lines'] ? max( 1, (int) $atts['max_code_lines'] ) : (int) $settings['max_code_lines']; // Increment view count. ViewCounter::increment( $post_id ); // Enqueue assets once. if ( ! self::$assets_enqueued ) { $this->enqueue_assets( $settings ); self::$assets_enqueued = true; } // Queue workflow data for JS output. self::$render_queue[] = array( 'id' => $post_id, 'workflow' => $workflow, 'mode' => $mode, 'zoom' => $zoom ? $zoom : 100, 'autofit_threshold' => (int) ( $settings['autofit_threshold'] ?? 30 ), 'show_title' => $show_title, 'show_infobox' => $show_infobox, 'show_download' => $allow_download, 'show_minimap' => $show_minimap, 'max_code_lines' => $max_code_lines, 'download_label' => esc_html( $settings['download_label'] ), 'download_url' => $allow_download ? esc_url( add_query_arg( 'breznflow_download', $post_id, home_url( '/' ) ) ) : '', 'show_share' => $allow_share, 'show_embed' => $allow_embed, 'show_get_json' => $allow_get_json, 'permalink' => $allow_share ? esc_url( get_permalink() ) : '', 'anchor_id' => 'breznflow-' . $post_id, 'workflow_title' => esc_html( $post->post_title ), 'node_count' => (int) get_post_meta( $post_id, '_breznflow_node_count', true ), 'is_ai_powered' => (bool) get_post_meta( $post_id, '_breznflow_has_ai_nodes', true ), 'blog_name' => esc_html( get_bloginfo( 'name' ) ), 'blog_url' => esc_url( home_url( '/' ) ), 'embed_url' => $allow_embed ? esc_url( add_query_arg( 'breznflow_embed', $post_id, home_url( '/' ) ) ) : '', 'theme' => $theme, ); // Build HTML placeholder. $html = ''; if ( $show_title ) { $html .= '

' . esc_html( $post->post_title ) . '

'; } if ( 'info' === $mode ) { $node_summary = get_post_meta( $post_id, '_breznflow_node_summary', true ); $node_count = (int) get_post_meta( $post_id, '_breznflow_node_count', true ); $has_ai = (int) get_post_meta( $post_id, '_breznflow_has_ai_nodes', true ); $categorized = array( 'counts' => (array) json_decode( $node_summary ? $node_summary : '{}', true ), 'has_ai' => (bool) $has_ai, 'total' => $node_count, ); $html .= InfoBoxBuilder::build( $categorized ); } else { $html .= '
' . '' . '
' . '
' . '
'; } return $html; } /** * Outputs accumulated workflow data as a single wp_localize_script call. * Hooked to wp_footer priority 1 (before scripts). */ public function output_script_data(): void { if ( empty( self::$render_queue ) ) { return; } wp_localize_script( 'breznflow-renderer', 'breznflowData', self::$render_queue ); wp_localize_script( 'breznflow-renderer', 'breznflowIcons', NodeTypeRegistry::get_registry() ); wp_localize_script( 'breznflow-renderer', 'breznflowI18n', self::get_js_i18n() ); } /** * Returns translatable strings for the frontend renderer JS. * * @return array */ public static function get_js_i18n(): array { return array( 'share' => __( 'Share', 'breznflow' ), 'embed' => __( 'Embed', 'breznflow' ), 'getJson' => __( 'Get JSON', 'breznflow' ), 'copy' => __( 'Copy', 'breznflow' ), 'copied' => __( 'Copied!', 'breznflow' ), 'error' => __( 'Error', 'breznflow' ), 'close' => __( 'Close', 'breznflow' ), 'zoomIn' => __( 'Zoom in', 'breznflow' ), 'zoomOut' => __( 'Zoom out', 'breznflow' ), 'resetView' => __( 'Reset view', 'breznflow' ), 'fullscreen' => __( 'Fullscreen', 'breznflow' ), 'minimap' => __( 'Minimap', 'breznflow' ), 'highlightInDiagram' => __( 'Highlight in diagram', 'breznflow' ), 'articleLink' => __( 'Article Link', 'breznflow' ), 'anchorLink' => __( 'Workflow Anchor Link', 'breznflow' ), 'embedDesc' => __( 'Embed this workflow on any website:', 'breznflow' ), 'optionalParams' => __( 'Optional URL parameters:', 'breznflow' ), 'code' => __( 'Code', 'breznflow' ), 'credential' => __( 'Credential', 'breznflow' ), 'type' => __( 'Type', 'breznflow' ), 'aiPowered' => __( 'AI-powered', 'breznflow' ), 'more' => __( 'more', 'breznflow' ), 'node' => __( 'node', 'breznflow' ), 'nodes' => __( 'nodes', 'breznflow' ), 'line' => __( 'line', 'breznflow' ), 'lines' => __( 'lines', 'breznflow' ), ); } /** * Outputs Schema.org HowTo structured data if enabled. */ public function maybe_output_schema(): void { if ( ! is_singular() ) { return; } $settings = get_option( 'breznflow_settings', array() ); if ( empty( $settings['schema_howto'] ) ) { return; } // Schema output is handled per-shortcode; here we hook for future expansion. } /** * Enqueues renderer assets. * * @since 1.0.0 * @param array $settings Plugin settings array (reserved for future use). * @return void */ private function enqueue_assets( array $settings ): void { 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 ); // Enqueue built-in theme CSS files. foreach ( ThemeRegistry::BUILTIN as $id => $name ) { wp_enqueue_style( 'breznflow-theme-' . $id, ThemeRegistry::get_builtin_url( $id ), array( 'breznflow-renderer' ), BREZNFLOW_VERSION ); } // Output custom themes as inline CSS. $custom_css = ThemeRegistry::get_custom_theme_css(); if ( $custom_css ) { wp_add_inline_style( 'breznflow-renderer', $custom_css ); } } }