1 module Radiant
2
3 class << self
4 def config_definitions
5 @config_definitions ||= {}
6 end
7
8 def config_definitions=(definitions)
9 @config_definitions = definitions
10 end
11 end
12
13 class Config < ActiveRecord::Base
14 #
15 # The Radiant.config model class is stored in the database (and cached) but emulates a hash
16 # with simple bracket methods that allow you to get and set values like so:
17 #
18 # Radiant.config['setting.name'] = 'value'
19 # Radiant.config['setting.name'] #=> "value"
20 #
21 # Config entries can be used freely as general-purpose global variables unless a definition
22 # has been given for that key, in which case restrictions and defaults may apply. The restrictions
23 # can take the form of validations, requirements, permissions or permitted options. They are
24 # declared by calling Radiant::Config#define:
25 #
26 # # setting must be either 'foo', 'bar' or 'blank'
27 # define('admin.name', :select_from => ['foo', 'bar'])
28 #
29 # # setting is (and must be) chosen from the names of currently available layouts
30 # define('shop.layout', :select_from => lambda { Layout.all.map{|l| [l.name,l.id]} }, :alow_blank => false)
31 #
32 # # setting cannot be changed at runtime
33 # define('setting.important', :default => "something", :allow_change => false)
34 #
35 # Which almost always happens in a block like this:
36 #
37 # Radiant.config do |config|
38 # config.namespace('user', :allow_change => true) do |user|
39 # user.define 'allow_password_reset?', :default => true
40 # end
41 # end
42 #
43 # and usually in a config/radiant_config.rb file either in radiant itself, in the application directory
44 # or in an extension. Radiant currently defines the following settings and makes them editable by
45 # admin users on the site configuration page:
46 #
47 # admin.title :: the title of the admin system
48 # admin.subtitle :: the subtitle of the admin system
49 # defaults.page.parts :: a comma separated list of default page parts
50 # defaults.page.status :: a string representation of the default page status
51 # defaults.page.filter :: the default filter to use on new page parts
52 # defaults.page.fields :: a comma separated list of the default page fields
53 # defaults.snippet.filter :: the default filter to use on new snippets
54 # dev.host :: the hostname where draft pages are viewable
55 # local.timezone :: the timezone name (`rake -D time` for full list)
56 # used to correct displayed times
57 # page.edit.published_date? :: when true, shows the datetime selector
58 # for published date on the page edit screen
59 #
60 # Helper methods are defined in ConfigurationHelper that will display config entry values
61 # or edit fields:
62 #
63 # # to display label and value, where label comes from looking up the config key in the active locale
64 # show_setting('admin.name')
65 #
66 # # to display an appropriate checkbox, text field or select box with label as above:
67 # edit_setting('admin.name)
68 #
69
70 set_table_name "config"
71 after_save :update_cache
72 attr_reader :definition
73
74 class ConfigError < RuntimeError; end
75
76 class << self
77 def [](key)
78 if table_exists?
79 unless Radiant::Config.cache_file_exists?
80 Radiant::Config.ensure_cache_file
81 Radiant::Config.initialize_cache
82 end
83 Radiant::Config.initialize_cache if Radiant::Config.stale_cache?
84 Rails.cache.read('Radiant::Config')[key]
85 end
86 end
87
88 def []=(key, value)
89 if table_exists?
90 setting = find_or_initialize_by_key(key)
91 setting.value = value
92 end
93 end
94
95 def to_hash
96 Hash[ *find(:all).map { |pair| [pair.key, pair.value] }.flatten ]
97 end
98
99 def initialize_cache
100 Radiant::Config.ensure_cache_file
101 Rails.cache.write('Radiant::Config',Radiant::Config.to_hash)
102 Rails.cache.write('Radiant.cache_mtime', File.mtime(cache_file))
103 Rails.cache.silence!
104 end
105
106 def cache_file_exists?
107 File.file?(cache_file)
108 end
109
110 def stale_cache?
111 return true unless Radiant::Config.cache_file_exists?
112 Rails.cache.read('Radiant.cache_mtime') != File.mtime(cache_file)
113 end
114
115 def ensure_cache_file
116 FileUtils.mkpath(cache_path)
117 FileUtils.touch(cache_file)
118 end
119
120 def cache_path
121 "#{Rails.root}/tmp"
122 end
123
124 def cache_file
125 cache_file = File.join(cache_path,'radiant_config_cache.txt')
126 end
127
128 def site_settings
129 @site_settings ||= %w{ site.title site.host dev.host local.timezone }
130 end
131
132 def default_settings
133 @default_settings ||= %w{ defaults.locale defaults.page.filter defaults.page.parts defaults.page.fields defaults.page.status defaults.snippet.filter }
134 end
135
136 def user_settings
137 @user_settings ||= ['user.allow_password_reset?']
138 end
139
140 # A convenient drying method for specifying a prefix and options common to several settings.
141 #
142 # Radiant.config do |config|
143 # config.namespace('secret', :allow_display => false) do |secret|
144 # secret.define('identity', :default => 'batman') # defines 'secret.identity'
145 # secret.define('lair', :default => 'batcave') # defines 'secret.lair'
146 # secret.define('longing', :default => 'vindication') # defines 'secret.longing'
147 # end
148 # end
149 #
150 def namespace(prefix, options = {}, &block)
151 prefix = [options[:prefix], prefix].join('.') if options[:prefix]
152 with_options(options.merge(:prefix => prefix), &block)
153 end
154
155 # Declares a setting definition that will constrain and support the use of a particular config entry.
156 #
157 # define('setting.key', options)
158 #
159 # Can take several options:
160 # * :default is the value that will be placed in the database if none has been set already
161 # * :type can be :string, :boolean or :integer. Note that all settings whose key ends in ? are considered boolean.
162 # * :select_from should be a list or hash suitable for passing to options_for_select, or a block that will return such a list at runtime
163 # * :validate_with should be a block that will receive a value and return true or false. Validations are also implied by type or select_from.
164 # * :allow_blank should be false if the config item must not be blank or nil
165 # * :allow_change should be false if the config item can only be set, not changed. Add a default to specify an unchanging config entry.
166 # * :allow_display should be false if the config item should not be showable in radius tags
167 #
168 # From the main radiant config/initializers/radiant_config.rb:
169 #
170 # Radiant.config do |config|
171 # config.define 'defaults.locale', :select_from => lambda { Radiant::AvailableLocales.locales }, :allow_blank => true
172 # config.define 'defaults.page.parts', :default => "Body,Extended"
173 # ...
174 # end
175 #
176 # It's also possible to reuse a definition by passing it to define:
177 #
178 # choose_layout = Radiant::Config::Definition.new(:select_from => lambda {Layout.all.map{|l| [l.name, l.d]}})
179 # define "my.layout", choose_layout
180 # define "your.layout", choose_layout
181 #
182 # but at the moment that's only done in testing.
183 #
184 def define(key, options={})
185 called_from = caller.grep(/\/initializers\//).first
186 if options.is_a? Radiant::Config::Definition
187 definition = options
188 else
189 key = [options[:prefix], key].join('.') if options[:prefix]
190 end
191
192 raise LoadError, %{
193 Config definition error: '#{key}' is defined twice:
194 1. #{called_from}
195 2. #{definitions[key].definer}
196 } unless definitions[key].nil? || definitions[key].empty?
197
198 definition ||= Radiant::Config::Definition.new(options.merge(:definer => called_from))
199 definitions[key] = definition
200
201 if self[key].nil? && !definition.default.nil?
202 begin
203 self[key] = definition.default
204 rescue ActiveRecord::RecordInvalid
205 raise LoadError, "Default configuration invalid: value '#{definition.default}' is not allowed for '#{key}'"
206 end
207 end
208 end
209
210 def definitions
211 Radiant.config_definitions
212 end
213
214 def definition_for(key)
215 definitions[key] ||= Radiant::Config::Definition.new(:empty => true)
216 end
217
218 def clear_definitions!
219 Radiant.config_definitions = {}
220 end
221
222 end
223
224 # The usual way to use a config item:
225 #
226 # Radiant.config['key'] = value
227 #
228 # is equivalent to this:
229 #
230 # Radiant::Config.find_or_create_by_key('key').value = value
231 #
232 # Calling value= also applies any validations and restrictions that are found in the associated definition.
233 # so this will raise a ConfigError if you try to change a protected config entry or a RecordInvalid if you
234 # set a value that is not among those permitted.
235 #
236 def value=(param)
237 newvalue = param.to_s
238 if newvalue != self[:value]
239 raise ConfigError, "#{self.key} cannot be changed" unless settable? || self[:value].blank?
240 if boolean?
241 self[:value] = (newvalue == "1" || newvalue == "true") ? "true" : "false"
242 else
243 self[:value] = newvalue
244 end
245 self.save!
246 end
247 self[:value]
248 end
249
250 # Requesting a config item:
251 #
252 # key = Radiant.config['key']
253 #
254 # is equivalent to this:
255 #
256 # key = Radiant::Config.find_or_create_by_key('key').value
257 #
258 # If the config item is boolean the response will be true or false. For items with :type => :integer it will be an integer,
259 # for everything else a string.
260 #
261 def value
262 if boolean?
263 checked?
264 else
265 self[:value]
266 end
267 end
268
269 # Returns the definition associated with this config item. If none has been declared this will be an empty definition
270 # that does not restrict use.
271 #
272 def definition
273 @definition ||= self.class.definition_for(self.key)
274 end
275
276 # Returns true if the item key ends with '?' or the definition specifies :type => :boolean.
277 #
278 def boolean?
279 definition.boolean? || self.key.ends_with?("?")
280 end
281
282 # Returns true if the item is boolean and true.
283 #
284 def checked?
285 return nil if self[:value].nil?
286 boolean? && self[:value] == "true"
287 end
288
289 # Returns true if the item defintion includes a :select_from parameter that limits the range of permissible options.
290 #
291 def selector?
292 definition.selector?
293 end
294
295 # Returns a name corresponding to the current setting value, if the setting definition includes a select_from parameter.
296 #
297 def selected_value
298 definition.selected(value)
299 end
300
301 def update_cache
302 Radiant::Config.initialize_cache
303 end
304
305 delegate :default, :type, :allow_blank?, :hidden?, :visible?, :settable?, :selection, :notes, :units, :to => :definition
306
307 def validate
308 definition.validate(self)
309 end
310
311 end
312 end