--- a/net/minecraft/world/level/block/entity/SignBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/SignBlockEntity.java
@@ -12,6 +12,7 @@
 import net.minecraft.network.chat.CommonComponents;
 import net.minecraft.network.chat.Component;
 import net.minecraft.network.chat.ComponentUtils;
+import net.minecraft.network.chat.MutableComponent;
 import net.minecraft.network.chat.Style;
 import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
 import net.minecraft.server.level.ServerLevel;
@@ -23,39 +24,54 @@
 import net.minecraft.world.phys.Vec2;
 import net.minecraft.world.phys.Vec3;
 
-public class SignBlockEntity extends BlockEntity {
+public class SignBlockEntity extends BlockEntity implements CommandSource { // CraftBukkit - implements
+    private static final boolean CONVERT_LEGACY_SIGNS = Boolean.getBoolean("convertLegacySigns"); // Paper
+
     public static final int LINES = 4;
     private static final String[] RAW_TEXT_FIELD_NAMES = new String[]{"Text1", "Text2", "Text3", "Text4"};
     private static final String[] FILTERED_TEXT_FIELD_NAMES = new String[]{"FilteredText1", "FilteredText2", "FilteredText3", "FilteredText4"};
-    public final Component[] messages = new Component[]{CommonComponents.EMPTY, CommonComponents.EMPTY, CommonComponents.EMPTY, CommonComponents.EMPTY};
-    private final Component[] filteredMessages = new Component[]{CommonComponents.EMPTY, CommonComponents.EMPTY, CommonComponents.EMPTY, CommonComponents.EMPTY};
-    public boolean isEditable = true;
+    public final Component[] messages;
+    private final Component[] filteredMessages;
+    public boolean isEditable;
     @Nullable
     private UUID playerWhoMayEdit;
     @Nullable
     private FormattedCharSequence[] renderMessages;
     private boolean renderMessagedFiltered;
-    private DyeColor color = DyeColor.BLACK;
+    private DyeColor color;
     private boolean hasGlowingText;
+    private static final org.apache.logging.log4j.Logger LOGGER = org.apache.logging.log4j.LogManager.getLogger();
 
     public SignBlockEntity(BlockPos pos, BlockState state) {
         super(BlockEntityType.SIGN, pos, state);
+        this.messages = new Component[]{CommonComponents.EMPTY, CommonComponents.EMPTY, CommonComponents.EMPTY, CommonComponents.EMPTY};
+        this.filteredMessages = new Component[]{CommonComponents.EMPTY, CommonComponents.EMPTY, CommonComponents.EMPTY, CommonComponents.EMPTY};
+        this.isEditable = true;
+        this.color = DyeColor.BLACK;
     }
 
     @Override
     protected void saveAdditional(CompoundTag nbt) {
         super.saveAdditional(nbt);
 
-        for(int i = 0; i < 4; ++i) {
-            Component component = this.messages[i];
-            String string = Component.Serializer.toJson(component);
-            nbt.putString(RAW_TEXT_FIELD_NAMES[i], string);
-            Component component2 = this.filteredMessages[i];
-            if (!component2.equals(component)) {
-                nbt.putString(FILTERED_TEXT_FIELD_NAMES[i], Component.Serializer.toJson(component2));
+        for (int i = 0; i < 4; ++i) {
+            Component ichatbasecomponent = this.messages[i];
+            String s = Component.Serializer.toJson(ichatbasecomponent);
+
+            nbt.putString(SignBlockEntity.RAW_TEXT_FIELD_NAMES[i], s);
+            Component ichatbasecomponent1 = this.filteredMessages[i];
+
+            if (!ichatbasecomponent1.equals(ichatbasecomponent)) {
+                nbt.putString(SignBlockEntity.FILTERED_TEXT_FIELD_NAMES[i], Component.Serializer.toJson(ichatbasecomponent1));
             }
         }
 
+        // CraftBukkit start
+        if (CONVERT_LEGACY_SIGNS) { // Paper
+            nbt.putBoolean("Bukkit.isConverted", true);
+        }
+        // CraftBukkit end
+
         nbt.putString("Color", this.color.getName());
         nbt.putBoolean("GlowingText", this.hasGlowingText);
     }
@@ -66,15 +82,33 @@
         super.load(nbt);
         this.color = DyeColor.byName(nbt.getString("Color"), DyeColor.BLACK);
 
-        for(int i = 0; i < 4; ++i) {
-            String string = nbt.getString(RAW_TEXT_FIELD_NAMES[i]);
-            Component component = this.loadLine(string);
-            this.messages[i] = component;
-            String string2 = FILTERED_TEXT_FIELD_NAMES[i];
-            if (nbt.contains(string2, 8)) {
-                this.filteredMessages[i] = this.loadLine(nbt.getString(string2));
+        // CraftBukkit start - Add an option to convert signs correctly
+        // This is done with a flag instead of all the time because
+        // we have no way to tell whether a sign is from 1.7.10 or 1.8
+        boolean oldSign = Boolean.getBoolean("convertLegacySigns") && !nbt.getBoolean("Bukkit.isConverted");
+        // CraftBukkit end
+
+        for (int i = 0; i < 4; ++i) {
+            String s = nbt.getString(SignBlockEntity.RAW_TEXT_FIELD_NAMES[i]);
+            // CraftBukkit start
+            if (s != null && s.length() > 2048) {
+                s = "\"\"";
+            }
+
+            if (oldSign && !this.isLoadingStructure) { // Paper - saved structures will be in the new format, but will not have isConverted
+                this.messages[i] = org.bukkit.craftbukkit.v1_19_R1.util.CraftChatMessage.fromString(s)[0];
+                continue;
+            }
+            // CraftBukkit end
+            Component ichatbasecomponent = this.loadLine(s);
+
+            this.messages[i] = ichatbasecomponent;
+            String s1 = SignBlockEntity.FILTERED_TEXT_FIELD_NAMES[i];
+
+            if (nbt.contains(s1, 8)) {
+                this.filteredMessages[i] = this.loadLine(nbt.getString(s1));
             } else {
-                this.filteredMessages[i] = component;
+                this.filteredMessages[i] = ichatbasecomponent;
             }
         }
 
@@ -83,24 +117,32 @@
     }
 
     private Component loadLine(String json) {
-        Component component = this.deserializeTextSafe(json);
+        Component ichatbasecomponent = this.deserializeTextSafe(json);
+
         if (this.level instanceof ServerLevel) {
             try {
-                return ComponentUtils.updateForEntity(this.createCommandSourceStack((ServerPlayer)null), component, (Entity)null, 0);
-            } catch (CommandSyntaxException var4) {
+                return ComponentUtils.updateForEntity(this.createCommandSourceStack((ServerPlayer) null), ichatbasecomponent, (Entity) null, 0);
+            } catch (CommandSyntaxException commandsyntaxexception) {
+                ;
             }
         }
 
-        return component;
+        return ichatbasecomponent;
     }
 
     private Component deserializeTextSafe(String json) {
         try {
-            Component component = Component.Serializer.fromJson(json);
-            if (component != null) {
-                return component;
+            MutableComponent ichatmutablecomponent = Component.Serializer.fromJson(json);
+
+            if (ichatmutablecomponent != null) {
+                return ichatmutablecomponent;
             }
-        } catch (Exception var3) {
+            // CraftBukkit start
+        } catch (com.google.gson.JsonParseException jsonparseexception) {
+            return Component.empty();
+            // CraftBukkit end
+        } catch (Exception exception) {
+            ;
         }
 
         return CommonComponents.EMPTY;
@@ -125,8 +167,8 @@
             this.renderMessagedFiltered = filterText;
             this.renderMessages = new FormattedCharSequence[4];
 
-            for(int i = 0; i < 4; ++i) {
-                this.renderMessages[i] = textOrderingFunction.apply(this.getMessage(i, filterText));
+            for (int i = 0; i < 4; ++i) {
+                this.renderMessages[i] = (FormattedCharSequence) textOrderingFunction.apply(this.getMessage(i, filterText));
             }
         }
 
@@ -174,21 +216,76 @@
     }
 
     public boolean executeClickCommands(ServerPlayer player) {
-        for(Component component : this.getMessages(player.isTextFilteringEnabled())) {
-            Style style = component.getStyle();
-            ClickEvent clickEvent = style.getClickEvent();
-            if (clickEvent != null && clickEvent.getAction() == ClickEvent.Action.RUN_COMMAND) {
-                player.getServer().getCommands().performPrefixedCommand(this.createCommandSourceStack(player), clickEvent.getValue());
+        Component[] aichatbasecomponent = this.getMessages(player.isTextFilteringEnabled());
+        int i = aichatbasecomponent.length;
+
+        for (int j = 0; j < i; ++j) {
+            Component ichatbasecomponent = aichatbasecomponent[j];
+            Style chatmodifier = ichatbasecomponent.getStyle();
+            ClickEvent chatclickable = chatmodifier.getClickEvent();
+
+            if (chatclickable != null && chatclickable.getAction() == ClickEvent.Action.RUN_COMMAND) {
+                // Paper start
+                String command = chatclickable.getValue().startsWith("/") ? chatclickable.getValue() : "/" + chatclickable.getValue();
+                if (org.spigotmc.SpigotConfig.logCommands)  {
+                    LOGGER.info("{} issued server command: {}", player.getScoreboardName(), command);
+                }
+                io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent event = new io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent(player.getBukkitEntity(), command, new org.bukkit.craftbukkit.v1_19_R1.util.LazyPlayerSet(player.getServer()), (org.bukkit.block.Sign) io.papermc.paper.util.MCUtil.toBukkitBlock(this.level, this.worldPosition).getState());
+                if (!event.callEvent()) {
+                    return false;
+                }
+                player.getServer().getCommands().performPrefixedCommand(this.createCommandSourceStack(((org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer) event.getPlayer()).getHandle()), event.getMessage());
+                // Paper end
             }
         }
 
         return true;
     }
 
+    // CraftBukkit start
+    @Override
+    public void sendSystemMessage(Component message) {}
+
+    @Override
+    public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
+        return wrapper.getEntity() != null ? wrapper.getEntity().getBukkitSender(wrapper) : new org.bukkit.craftbukkit.v1_19_R1.command.CraftBlockCommandSender(wrapper, this);
+    }
+
+    @Override
+    public boolean acceptsSuccess() {
+        return false;
+    }
+
+    @Override
+    public boolean acceptsFailure() {
+        return false;
+    }
+
+    @Override
+    public boolean shouldInformAdmins() {
+        return false;
+    }
+    // CraftBukkit end
+
     public CommandSourceStack createCommandSourceStack(@Nullable ServerPlayer player) {
-        String string = player == null ? "Sign" : player.getName().getString();
-        Component component = (Component)(player == null ? Component.literal("Sign") : player.getDisplayName());
-        return new CommandSourceStack(CommandSource.NULL, Vec3.atCenterOf(this.worldPosition), Vec2.ZERO, (ServerLevel)this.level, 2, string, component, this.level.getServer(), player);
+        String s = player == null ? "Sign" : player.getName().getString();
+        Object object = player == null ? Component.literal("Sign") : player.getDisplayName();
+
+        // Paper start - send messages back to the player
+        CommandSource commandSource = this.level.paperConfig().misc.showSignClickCommandFailureMsgsToPlayer ? new io.papermc.paper.commands.DelegatingCommandSource(this) {
+            @Override
+            public void sendSystemMessage(Component message) {
+                player.sendSystemMessage(message);
+            }
+
+            @Override
+            public boolean acceptsFailure() {
+                return true;
+            }
+        } : this;
+        // Paper end
+        // CraftBukkit - this
+        return new CommandSourceStack(commandSource, Vec3.atCenterOf(this.worldPosition), Vec2.ZERO, (ServerLevel) this.level, 2, s, (Component) object, this.level.getServer(), player); // Paper
     }
 
     public DyeColor getColor() {
@@ -221,6 +318,6 @@
 
     private void markUpdated() {
         this.setChanged();
-        this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3);
+        if (this.level != null) this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3); // CraftBukkit - skip notify if world is null (SPIGOT-5122)
     }
 }
