This plugin comes with a side effect, it seems that having the IsDeveloper flag set lets you edit any sign even if it's locked. I've tried overwriting this behaviour to no avail using this code:

private bool CanUpdateSign(BasePlayer player, Signage sign)
{
    return !(sign.IsLocked() && player.userID != sign.OwnerID);
}​
Even tho it returns false when trying to edit a locked sign that someone else made, the sign can still be edited.

Is there any way around this? I'm using this to give noclip to players on a creative mode server, as Faux Clip is too laggy to use, and some players are using this to grief the signs, pictures and banners of other players.

edit: I found this forum post which is the only useful reference I could find that shows where IsAdmin is used, and it shows that it's used directly in Signage.CanUpdateSign. I think IsDeveloper is used there as well for the same purpose. This can not be overwritten, right?

Merged post

Okay, this is the best solution I could come up with. I'm storing all signs transiently and check if a player is building blocked, if so and they update a sign, the sign is restored with the stored data, otherwise the data is updated. This is the code if anyone else needs to do the same thing:

void Loaded()
{
    // original code here ...

    BaseEntity.FindObjectsOfType<Signage>().Where(s => s.textureID != 0).ToList().ForEach(s => this.storeSign(s));
    Puts($"Stored {this.signs.Count} signs in transient storage.");
}

#region Signs
private Dictionary<uint, byte[]> signs = new Dictionary<uint, byte[]>();

private void OnSignUpdated(Signage sign, BasePlayer player)
{
    if (player.IsBuildingBlocked())
    {
        // Revoke and restore
        Puts($"Revoking sign edit of user {player.IPlayer.Name}, restoring texture");
        timer.Once(1f, () => restoreSign(sign));
    }
    else
    {
        // Accept and store
        Puts($"Accepting sign edit of user {player.IPlayer.Name}.");
        this.storeSign(sign);
    }
}

private void storeSign(Signage sign)
{
    byte[] imageByte = FileStorage.server.Get(sign.textureID, FileStorage.Type.png, sign.net.ID);

    if (sign.textureID > 0 && imageByte != null)
    {
        if (this.signs.ContainsKey(sign.net.ID))
            this.signs.Remove(sign.net.ID);
        this.signs.Add(sign.net.ID, imageByte);
    }
    else
        Puts("Can not store sign, no texture or image data.");
}

private void restoreSign(Signage sign)
{
    FileStorage.server.Remove(sign.textureID, FileStorage.Type.png, sign.net.ID);
    sign.textureID = FileStorage.server.Store(this.signs[sign.net.ID], FileStorage.Type.png, sign.net.ID);
    sign.SendNetworkUpdate();
}
#endregion​
The reason I had to use building blocked instead of OwnerID of the sign is because a player with the IsDeveloper flag is able to unlock and lock a sign which changes the owner to the locking user, allowing evasion of the workaround. Instead players can only change a sign if they are not building blocked.
I'm also not usre why I can not call restoreSign in the same frame as OnSignUpdate was called. I've tried a few other methods, like NextFrame but the only way it would definitely revert the sign all the time was to give it a large delay, like a second or so. Another downside of this code is that on a server with a ton of large signs the memory usage of this plugin will grow quickly and loading the plugin initially may take a few seconds but it's an acceptable workaround.