| @@ -2065,8 +2065,15 @@ | ||
| 2065 | 2065 | * One exception: with the script placed in the head (load_java = 0) that |
| 2066 | 2066 | * decision cannot be deferred -- the head is sent before the content runs. |
| 2067 | 2067 | * In that configuration the script is enqueued unconditionally, as before. |
| 2068 | 2068 | * |
| 2069 | + * That head branch deliberately does not look at "java" at all, and never | |
| 2070 | + * did -- this method leaves it untouched. It follows that a | |
| 2071 | + * "[cryptx java=...]" shortcode override has nothing to add there: the | |
| 2072 | + * script is already on every page regardless of the global setting, so | |
| 2073 | + * only the footer branch below needs to read "java" or care about a | |
| 2074 | + * shortcode overriding it. | |
| 2075 | + * | |
| 2069 | 2076 | * @return void |
| 2070 | 2077 | */ |
| 2071 | 2078 | public function loadJavascriptFiles(): void |
| 2072 | 2079 | { |
| @@ -2078,8 +2085,32 @@ | ||
| 2078 | 2085 | |
| 2079 | 2086 | if (!$inFooter) { |
| 2080 | 2087 | wp_enqueue_script('cryptx-js'); |
| 2081 | 2088 | wp_enqueue_style('cryptx-styles'); |
| 2089 | + } elseif (!empty(self::$cryptXOptions['java'])) { | |
| 2090 | + // Footer placement, JavaScript handler enabled: enqueue cryptx-js | |
| 2091 | + // unconditionally, here, before it is known whether THIS request's | |
| 2092 | + // content carries an address. wp_register_script() above already | |
| 2093 | + // registered it with $inFooter = true, so this does not move the | |
| 2094 | + // print location -- it still prints in wp_footer, exactly as | |
| 2095 | + // before. Deferring to enqueueAssetsIfNeeded() (scriptNeeded) only | |
| 2096 | + // covers a classic page load, where the address a visitor clicks | |
| 2097 | + // is guaranteed to be in the same document that carried the | |
| 2098 | + // script. A client-side navigation (swup.js, PJAX, Barba, Turbo) | |
| 2099 | + // can land a visitor on a page with no address at all and then | |
| 2100 | + // drop .cryptx-link elements in later, without ever loading a | |
| 2101 | + // second script -- the delegated handler in cryptx.js was simply | |
| 2102 | + // never attached. See | |
| 2103 | + // docs/entscheidungen/2026-09-11-assets-bei-clientseitiger-navigation.md | |
| 2104 | + // for the analysis. | |
| 2105 | + // | |
| 2106 | + // Known remaining gap, not closable from here: this branch only | |
| 2107 | + // reads the global "java" setting. A page whose first load carries | |
| 2108 | + // no [cryptx java="1"] shortcode, under a global java = 0, still | |
| 2109 | + // enqueues nothing here -- so a later client-side navigation to a | |
| 2110 | + // page that DOES carry that shortcode still finds no click handler | |
| 2111 | + // attached. See the decision doc above for why this is left open. | |
| 2112 | + wp_enqueue_script('cryptx-js'); | |
| 2082 | 2113 | } |
| 2083 | 2114 | } |
| 2084 | 2115 | |
| 2085 | 2116 | /** |
| @@ -2091,14 +2122,47 @@ | ||
| 2091 | 2122 | */ |
| 2092 | 2123 | public function enqueueAssetsIfNeeded(): void |
| 2093 | 2124 | { |
| 2094 | 2125 | if (self::$scriptNeeded) { |
| 2126 | + // Still needed as a net: a shortcode can set java=1 for its own | |
| 2127 | + // instance while the global setting says java=0, since "java" is | |
| 2128 | + // not in NOT_SETTABLE_BY_SHORTCODE. loadJavascriptFiles() only | |
| 2129 | + // sees the global setting, so this is what catches that case. A | |
| 2130 | + // second wp_enqueue_script() on an already-enqueued handle is a | |
| 2131 | + // no-op. | |
| 2095 | 2132 | wp_enqueue_script('cryptx-js'); |
| 2096 | 2133 | } |
| 2097 | 2134 | |
| 2098 | - if (self::$styleNeeded) { | |
| 2135 | + // wp_register_style() has no footer flag to lean on the way the | |
| 2136 | + // script does, and enqueuing the stylesheet at wp_enqueue_scripts | |
| 2137 | + // would move it from the footer to <head> -- a behaviour change for | |
| 2138 | + // classic navigation, which is exactly what must not happen. So the | |
| 2139 | + // same client-side-navigation gap for a configured picture variant | |
| 2140 | + // (opt_linktext 2/3/5, the only settings that need img.cryptxImage | |
| 2141 | + // { height: 1em }) is closed here instead, gated on the setting | |
| 2142 | + // alone rather than on whether THIS request's content produced one. | |
| 2143 | + if (self::$styleNeeded || self::isPictureLinktext((int) (self::$cryptXOptions['opt_linktext'] ?? 0))) { | |
| 2099 | 2144 | wp_enqueue_style('cryptx-styles'); |
| 2100 | 2145 | } |
| 2146 | + } | |
| 2147 | + | |
| 2148 | + /** | |
| 2149 | + * Whether an opt_linktext setting renders links as pictures. | |
| 2150 | + * | |
| 2151 | + * Same known gap as the script branch in loadJavascriptFiles(), and purely | |
| 2152 | + * cosmetic here: a page whose first load carries no picture-producing | |
| 2153 | + * shortcode override, under a global opt_linktext that is not 2/3/5, still | |
| 2154 | + * skips the stylesheet -- a later client-side navigation to a page that | |
| 2155 | + * DOES render a picture link can arrive without img.cryptxImage. See | |
| 2156 | + * docs/entscheidungen/2026-09-11-assets-bei-clientseitiger-navigation.md. | |
| 2157 | + * | |
| 2158 | + * @param int $optLinktext The opt_linktext setting value. | |
| 2159 | + * | |
| 2160 | + * @return bool | |
| 2161 | + */ | |
| 2162 | + private static function isPictureLinktext(int $optLinktext): bool | |
| 2163 | + { | |
| 2164 | + return in_array($optLinktext, [2, 3, 5], true); | |
| 2101 | 2165 | } |
| 2102 | 2166 | |
| 2103 | 2167 | /** |
| 2104 | 2168 | * Updates the CryptX settings. |