post_content, self::TAG ) ) { return; } wp_register_script( 'sugarcalc-disclaimer-init', false, array(), Plugin::version(), false ); wp_enqueue_script( 'sugarcalc-disclaimer-init' ); wp_add_inline_script( 'sugarcalc-disclaimer-init', "if(sessionStorage.getItem('sugarCalcDisclaimerAccepted')==='true'){document.documentElement.classList.add('sugarcalc-accepted');}" ); } /** * Render the shortcode output. * * @param mixed $atts Shortcode attributes (unused — SugarCalc takes no attributes). * @return string HTML output. */ public static function render( mixed $atts ) : string { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Required WordPress shortcode callback signature. // ponytail: one converter per page, because app.js binds fixed ids (#00054). // True multi-instance support belongs with shortcode parameters (#00046). if ( self::$rendered ) { return ( current_user_can( 'edit_posts' ) ? '

' . esc_html__( 'SugarCalc: only one converter per page is supported. This notice is shown to editors only.', 'sugarcalc' ) . '

' : '' ); } self::$rendered = true; self::enqueue_assets(); $panel_texts = PanelTexts::get_content(); ob_start(); include SUGARCALC_DIR . 'templates/home.php'; include SUGARCALC_DIR . 'templates/converter-workspace.php'; $content = (string) ob_get_clean(); $container_class = 'sugarcalc-container'; return '
' . $content . '
'; } /** * Enqueue CSS, JS, and inline data scripts on first shortcode render per request. */ private static function enqueue_assets() : void { if ( self::$assets_enqueued ) { return; } self::$assets_enqueued = true; $base_url = SUGARCALC_URL . 'assets/'; wp_enqueue_style( 'sugarcalc-variables', $base_url . 'css/sugarcalc-variables.css', array(), self::asset_version( 'assets/css/sugarcalc-variables.css' ) ); wp_enqueue_style( 'sugarcalc-main', $base_url . 'css/sugarcalc-main.css', array('sugarcalc-variables'), self::asset_version( 'assets/css/sugarcalc-main.css' ) ); wp_enqueue_style( 'sugarcalc-components', $base_url . 'css/sugarcalc-components.css', array('sugarcalc-main'), self::asset_version( 'assets/css/sugarcalc-components.css' ) ); wp_enqueue_script( 'sugarcalc-app', $base_url . 'js/app.js', array(), self::asset_version( 'assets/js/app.js' ), true ); $thresholds = Thresholds::get(); wp_add_inline_script( 'sugarcalc-app', 'window.sugarcalcThresholds = ' . wp_json_encode( self::format_thresholds_for_js( $thresholds ) ) . ';', 'before' ); wp_add_inline_script( 'sugarcalc-app', 'window.sugarcalcPanelTexts = ' . wp_json_encode( PanelTexts::get_content() ) . ';', 'before' ); add_filter( 'script_loader_tag', array(self::class, 'add_module_type'), 10, 2 ); } /** * Resolve the cache-busting version query for a plugin asset. * * Order of precedence (see compose_asset_version() for the pure logic): * 1. SUGARCALC_BUILD_ID constant — unique per internal build, so every * build fetches fresh even when the semver version string is unchanged. * 2. File mtime under WP_DEBUG — busts in-place dev edits between builds. * 3. Plain plugin version — clean semver for production releases. * * @param string $relative Asset path relative to the plugin root (e.g. 'assets/js/app.js'). * @return string Version string suitable for wp_enqueue_* $ver. */ private static function asset_version( string $relative ) : string { $build_id = ( defined( 'SUGARCALC_BUILD_ID' ) && '' !== (string) SUGARCALC_BUILD_ID ? (string) SUGARCALC_BUILD_ID : null ); $file = SUGARCALC_DIR . $relative; $mtime = ( is_readable( $file ) ? filemtime( $file ) : false ); $debug = defined( 'WP_DEBUG' ) && WP_DEBUG; return self::compose_asset_version( Plugin::version(), $build_id, ( false === $mtime ? null : $mtime ), $debug ); } /** * Pure composition of an asset version string — no WordPress, unit-testable. * * @param string $base Base plugin version (clean semver). * @param string|null $build_id Build identifier, or null/empty when none. * @param int|null $mtime File modification time, or null when unavailable. * @param bool $debug Whether WP_DEBUG is active. * @return string Composed version string. */ public static function compose_asset_version( string $base, ?string $build_id, ?int $mtime, bool $debug ) : string { if ( null !== $build_id && '' !== $build_id ) { return $base . '+' . $build_id; } if ( $debug && null !== $mtime ) { return $base . '.' . (string) $mtime; } return $base; } /** * Add type="module" to the sugarcalc-app script tag. * * @param string $tag The script HTML tag. * @param string $handle The registered script handle. * @return string Modified tag. */ public static function add_module_type( string $tag, string $handle ) : string { if ( 'sugarcalc-app' === $handle ) { return str_replace( '