Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ public BookWrapper addToCurrentPage(BaseComponent... baseComponents) {
* @return the colorized string
*/
public String colorize(String s) {
return MessageAPI.colorize(s);
return com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting(
MessageAPI.colorize(s));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ public Layout addPlaceholder(String toReplace, String replaceWith) {
}

private String colorize(String s) {
return ChatColor.translateAlternateColorCodes('&', s);
return com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting(
ChatColor.translateAlternateColorCodes('&', s));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -497,8 +497,9 @@ private void cancelLegacyTimers() {
}

private String getDisplayName(Player player) {
return PlaceholderUtils.replaceJavascript(player,
PlaceholderUtils.replacePlaceHolder(inventoryName, placeholders));
return com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting(
PlaceholderUtils.replaceJavascript(player,
PlaceholderUtils.replacePlaceHolder(inventoryName, placeholders)));
}

private int getProperSize(int size) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

import com.bencodez.advancedcore.AdvancedCorePlugin;
import com.bencodez.advancedcore.api.javascript.JavascriptEngine;
import com.bencodez.advancedcore.api.messages.DisplayTextBoundaries;
import com.bencodez.advancedcore.api.messages.PlaceholderUtils;
import com.bencodez.advancedcore.api.rewards.RewardDisplayPlaceholders;
import com.bencodez.advancedcore.api.misc.MiscUtils;
Expand Down Expand Up @@ -1736,7 +1737,7 @@ public ItemStack parsePlaceholders(OfflinePlayer player) {
setSkullOwner(PlaceholderUtils.replaceJavascript(player,
PlaceholderUtils.replacePlaceHolder(skull, placeholders)));
}
return is;
return finishedDisplayItem();
}

/**
Expand Down Expand Up @@ -2141,6 +2142,38 @@ public ItemBuilder setUnbreakable(boolean unbreakable) {
return this;
}

/**
* Creates a final client-facing copy only when protected identifiers are
* present. The builder retains its guarded template for later GUI refreshes,
* which must never re-parse a previously unguarded display name.
*/
private ItemStack finishedDisplayItem() {
ItemMeta sourceMeta = is.getItemMeta();
if (sourceMeta == null) return is;
String name = sourceMeta.hasDisplayName() ? sourceMeta.getDisplayName() : null;
List<String> lore = sourceMeta.hasLore() ? sourceMeta.getLore() : null;
boolean guarded = name != null && name.indexOf('\u2060') >= 0;
if (lore != null) {
for (String line : lore) {
if (line != null && line.indexOf('\u2060') >= 0) {
guarded = true;
break;
}
}
}
if (!guarded) return is;
ItemStack displayed = is.clone();
ItemMeta displayMeta = displayed.getItemMeta();
if (name != null) displayMeta.setDisplayName(DisplayTextBoundaries.afterFormatting(name));
if (lore != null) {
ArrayList<String> visibleLore = new ArrayList<>(lore.size());
for (String line : lore) visibleLore.add(DisplayTextBoundaries.afterFormatting(line));
displayMeta.setLore(visibleLore);
}
displayed.setItemMeta(displayMeta);
return displayed;
}

/**
* Retrieves the itemstack from the ItemBuilder.
*
Expand All @@ -2167,7 +2200,7 @@ public ItemStack toItemStack() {
if (checkLoreLength) {
checkLoreLength();
}
return is;
return finishedDisplayItem();
}

/**
Expand All @@ -2191,7 +2224,7 @@ public ItemStack toItemStack(OfflinePlayer player) {
if (checkLoreLength) {
checkLoreLength();
}
return is;
return finishedDisplayItem();
}

/**
Expand All @@ -2215,7 +2248,7 @@ public ItemStack toItemStack(Player player) {
if (checkLoreLength) {
checkLoreLength();
}
return is;
return finishedDisplayItem();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.bencodez.advancedcore.api.messages;

import java.util.List;

import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;

/**
* Drops temporary WORD JOINER formatting guards only at a final player-facing
* sink. The guards must survive placeholder, JavaScript, color, and interactive
* message parsing; removing them earlier would make external identifiers active
* formatting syntax again.
*/
public final class DisplayTextBoundaries {

private DisplayTextBoundaries() {
}

public static String afterFormatting(String prepared) {
if (prepared == null || prepared.indexOf('\u2060') < 0) return prepared;
StringBuilder visible = new StringBuilder(prepared.length());
for (int i = 0; i < prepared.length(); i++) {
char next = prepared.charAt(i);
if (next != '\u2060') {
visible.append(next);
continue;
}
// Some legacy String sinks (inventory titles, item metadata, boss
// bars) still parse § codes on the client. Prevent an incomplete
// § in a trusted template from joining the next site-name letter
// into an active legacy color code when dropping this guard.
if (i > 0 && prepared.charAt(i - 1) == '\u00A7'
&& i + 1 < prepared.length() && isLegacyColorCode(prepared.charAt(i + 1))) {
visible.append(' ');
}
}
return visible.toString();
}

private static boolean isLegacyColorCode(char code) {
return "0123456789abcdefklmnorx".indexOf(Character.toLowerCase(code)) >= 0;
}

public static void afterFormatting(BaseComponent component) {
if (component == null) return;
if (component instanceof TextComponent text) {
// A component already passed legacy parsing, so it can retain the
// exact literal text without inserting a String-sink separator.
text.setText(text.getText() == null ? null : text.getText().replace("\u2060", ""));
}
List<BaseComponent> children = component.getExtra();
if (children != null) {
for (BaseComponent child : children) afterFormatting(child);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public static TextComponent parseJson(String msg) {

if (type.equalsIgnoreCase("hover")) {
BaseComponent[] hoverContent = TextComponent.fromLegacyText(typeData);
for (BaseComponent hover : hoverContent) DisplayTextBoundaries.afterFormatting(hover);
t.setHoverEvent(MessageAPI.getHoverEventSupport().createHoverEvent(hoverContent));
} else if (type.equalsIgnoreCase("command")) {
t.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, typeData));
Expand All @@ -87,6 +88,9 @@ public static TextComponent parseJson(String msg) {

comp.addExtra(new TextComponent(TextComponent.fromLegacyText(msg)));
}
// Syntax and legacy-color parsing are finished; restore the literal site
// name for the client without allowing another formatting pass.
DisplayTextBoundaries.afterFormatting(comp);
return comp;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class BossBar {
private org.bukkit.boss.BossBar bossBar;

public BossBar(String msg, String barColor, String barStyle, double progress) {
bossBar = Bukkit.createBossBar(MessageAPI.colorize(msg), EffectCompatibility.parseBarColor(barColor),
bossBar = Bukkit.createBossBar(com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting(MessageAPI.colorize(msg)), EffectCompatibility.parseBarColor(barColor),
EffectCompatibility.parseBarStyle(barStyle));
bossBar.setProgress(EffectCompatibility.clampProgress(progress));
}
Expand Down Expand Up @@ -116,7 +116,7 @@ public void setStyle(String barStyle) {

public void setTitle(String title) {
if (title != null) {
bossBar.setTitle(MessageAPI.colorize(title));
bossBar.setTitle(com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting(MessageAPI.colorize(title)));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,9 @@ public void send(Player player) {
}

public void send(Player player, String title, String subtitle, int fadeInTime, int stayTime, int fadeOutTime) {
player.sendTitle(MessageAPI.colorize(title), MessageAPI.colorize(subtitle), fadeInTime, stayTime, fadeOutTime);
player.sendTitle(com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting(MessageAPI.colorize(title)),
com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting(MessageAPI.colorize(subtitle)),
fadeInTime, stayTime, fadeOutTime);
}

/**
Expand Down Expand Up @@ -226,7 +228,7 @@ public void updateSubtitle(Player player) {
}

public void updateSubtitle(Player player, String subtitle) {
player.sendTitle("", MessageAPI.colorize(subtitle), -1, -1, -1);
player.sendTitle("", com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting(MessageAPI.colorize(subtitle)), -1, -1, -1);
}

/**
Expand All @@ -252,6 +254,6 @@ public void updateTitle(Player player) {
}

public void updateTitle(Player player, String title) {
player.sendTitle(MessageAPI.colorize(title), "", -1, -1, -1);
player.sendTitle(com.bencodez.advancedcore.api.messages.DisplayTextBoundaries.afterFormatting(MessageAPI.colorize(title)), "", -1, -1, -1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.bencodez.advancedcore.api.messages;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import org.junit.jupiter.api.Test;

import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.TextComponent;

class DisplayTextBoundariesTest {

@Test
void chatRemovesTemporaryMarkersOnlyAfterLegacyParsing() {
TextComponent text = PlaceholderUtils.parseJson("\u00A7eSite: \u2060Minecraft Menu\u2060");

assertEquals("Site: Minecraft Menu", text.toPlainText());
assertFalse(text.toPlainText().contains("\u2060"));
org.junit.jupiter.api.Assertions.assertTrue(text.toLegacyText().contains("\u00A7eSite: Minecraft Menu"));
assertFalse(text.toLegacyText().contains("\u2060"));
}

@Test
void guardedColorSequenceRemainsLiteralAfterLastParsingPass() {
TextComponent text = PlaceholderUtils.parseJson("&\u2060a");

assertEquals("&a", text.toPlainText());
assertFalse(text.toPlainText().contains("\u2060"));
}

@Test
void unfinishedSectionColorCodesCannotBecomeActiveAtLegacyStringSinks() {
assertEquals("\u00A7 a", DisplayTextBoundaries.afterFormatting("\u00A7\u2060a\u2060"));
assertEquals("Minecraft Menu", DisplayTextBoundaries.afterFormatting("\u2060Minecraft Menu\u2060"));
assertEquals("&aGreen", DisplayTextBoundaries.afterFormatting("\u2060&\u2060aGreen\u2060"));
}

@Test
void interactiveActionsStayExactWhileVisibleTextIsClean() {
TextComponent text = PlaceholderUtils.parseJson(
"[Text=\"\u2060Site name\u2060\",Url=\"https://example.com/vote\"]");

assertEquals("Site name", text.toPlainText());
TextComponent link = (TextComponent) text.getExtra().get(1);
ClickEvent click = link.getClickEvent();
assertNotNull(click);
assertEquals("https://example.com/vote", click.getValue());
}
}
Loading