infopark_fiona_connector 6.8.0.beta.200.891.647580e

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ module RailsConnector
2
+ # This class provides a default implementation for accessing the search server.
3
+ # It is used by DefaultSearchController.
4
+ class DefaultSearchRequest < VeritySearchRequest
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ require "active_record/errors"
2
+
3
+ module RailsConnector
4
+
5
+ ResourceNotFound = ActiveRecord::RecordNotFound
6
+
7
+ end # module RailsConnector
8
+
@@ -0,0 +1,135 @@
1
+ module RailsConnector
2
+ # This class provides an interfaces for handling CMS Links.
3
+ # To format a link for rendering in an html page, use the +cms_path+ or +cms_url+ methods.
4
+ class Link
5
+
6
+ extend ActiveModel::Naming
7
+
8
+ def initialize(link_data, destination_object = nil) #:nodoc:
9
+ @link_data = link_data.symbolize_keys
10
+ @destination_object = destination_object
11
+ end
12
+
13
+ # The link's external url. Only available for external links.
14
+ # Warning: Do not output the url directly unless you know what you are doing.
15
+ # Normally you want to use the +cms_path+ or +cms_url+ methods to format a link.
16
+ def url
17
+ @link_data[:url]
18
+ end
19
+
20
+ # The link's title.
21
+ def title
22
+ @link_data[:title]
23
+ end
24
+
25
+ # Returns the link's query string as in "index.html?query_string".
26
+ # See RFC3986 for details (http://www.ietf.org/rfc/rfc3986.txt).
27
+ def query
28
+ @link_data[:search]
29
+ end
30
+
31
+ # Deprecated: use Link#query instead.
32
+ # Returns the link's query string as in "index.html?query_string".
33
+ # See RFC3986 for details (http://www.ietf.org/rfc/rfc3986.txt).
34
+ def search
35
+ query
36
+ end
37
+
38
+ # Returns the link's anchor as in "index.html#anchor".
39
+ # See RFC3986 for details (http://www.ietf.org/rfc/rfc3986.txt).
40
+ def fragment
41
+ @link_data[:fragment]
42
+ end
43
+
44
+ # Returns the browser window or browser frame to be used as a target for this link.
45
+ # Example: Links that should be opened in a new window will return "_blank" as their target.
46
+ def target
47
+ @link_data[:target]
48
+ end
49
+
50
+ def id #:nodoc:
51
+ @link_data[:link_id]
52
+ end
53
+
54
+ def tag_name # :nodoc:
55
+ @link_data[:tag_name]
56
+ end
57
+
58
+ def markdown_type # :nodoc:
59
+ @link_data[:markdown_type]
60
+ end
61
+
62
+ def markdown? # :nodoc:
63
+ 'markdown' == @link_data[:source_format]
64
+ end
65
+
66
+ # Returns the file extension (e.g. zip, pdf) of this link's (internal or external) target.
67
+ # Returns an empty string if the file extension is can not be determined.
68
+ def file_extension
69
+ if internal?
70
+ destination_object ? destination_object.file_extension : ""
71
+ else
72
+ path = URI.parse(url).path rescue nil
73
+ path.blank? ? "" : File.extname(path)[1..-1] || ""
74
+ end
75
+ end
76
+
77
+ # Returns the id of the Links' destination_object.
78
+ def destination_object_id
79
+ destination
80
+ end
81
+
82
+ # Returns the title of this Link if it is set.
83
+ # Otherwise it returns the display_title of the destination object for internal Links
84
+ # or the URL for external Links.
85
+ def display_title
86
+ dt = title
87
+ dt = destination_object.display_title if dt.blank? && !external?
88
+ dt = url if dt.blank?
89
+ dt
90
+ end
91
+
92
+ # Returns true this Link links to a CMS Object.
93
+ def internal?
94
+ url.nil?
95
+ end
96
+
97
+ # Returns true if this Link links to an external URL.
98
+ def external?
99
+ !internal?
100
+ end
101
+
102
+ # An internal Link is active if it's destination object is active.
103
+ # An external Link is always active.
104
+ def active?
105
+ external? || (destination_object && destination_object.active?)
106
+ end
107
+
108
+ def external_prefix? #:nodoc:
109
+ url =~ /\s?external:/
110
+ end
111
+
112
+ def resolved? #:nodoc:
113
+ external? || resolved_internal?
114
+ end
115
+
116
+ # Returns the destination object (+Obj+) of this Link.
117
+ def destination_object
118
+ @destination_object ||= Obj.find(destination) if resolved_internal?
119
+ end
120
+
121
+ def to_liquid # :nodoc:
122
+ LiquidSupport::LinkDrop.new(self)
123
+ end
124
+
125
+ private
126
+
127
+ def resolved_internal?
128
+ internal? && !destination.nil?
129
+ end
130
+
131
+ def destination
132
+ @link_data[:destination]
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,107 @@
1
+ #:enddoc:
2
+ require 'rsolr'
3
+
4
+ module RailsConnector
5
+
6
+ # This class provides a basic implementation for accessing a Solr search server.
7
+ # It can be activated by making it the superclass of SearchRequest
8
+ # (instead of DefaultSearchRequest).
9
+ # It should be customized by subclassing.
10
+ class LuceneSearchRequest
11
+ attr_reader :query_string
12
+
13
+ # Sanitizes the given +query_string+ and takes +options+ for accessing Solr.
14
+ #
15
+ # +options+ is a hash and may include:
16
+ #
17
+ # <tt>:limit</tt>:: The maximum number of hits
18
+ # <tt>:offset</tt>:: The search offset
19
+ # <tt>:solr_url</tt>:: A non-default Solr server URL
20
+ # <tt>:filter_query</tt>:: See #filter_query_conditions
21
+ # <tt>:solr_parameters</tt>:: A hash of additional query parameters (e. g. +timeAllowed+, +sort+)
22
+ # <tt>:lazy_load</tt>:: when set to false (the default), the hits will load their associated Objs immediatley. Otherwise they will be loaded lazyly (when accessed through Hit#obj). Note that loading lazyly may expose hits that do not have an Obj, i.e. they return nil when Hit#obj is invoked. When loading immediately, those hits are filtered.
23
+ def initialize(query_string, options = nil)
24
+ @query_string = self.class.sanitize(query_string)
25
+ @options = Configuration.search_options.merge(options || {})
26
+ end
27
+
28
+ # Accesses Solr and fetches search hits.
29
+ #
30
+ # Uses the #filter_query and +options+ given in #new.
31
+ def fetch_hits
32
+ solr = RSolr.connect(:url => @options[:solr_url])
33
+ solr_result = solr.get("select", :params => solr_query_parameters)
34
+ solr_response = solr_result['response']
35
+ build_search_result(solr_response['numFound'], solr_response['docs'], solr_response['maxScore'])
36
+ end
37
+
38
+ # Removes unwanted characters from +text+.
39
+ def self.sanitize(text) #:nodoc:
40
+ text.gsub(/[^\w\*]/, ' ').gsub(/\s+/, ' ').strip
41
+ end
42
+
43
+ def solr_query_for_query_string
44
+ @query_string.downcase.split(/\s+/).map do |word|
45
+ word unless %w(and or not).include?(word)
46
+ end.compact.join(" AND ")
47
+ end
48
+
49
+ # Combines filter query conditions (see #filter_query_conditions).
50
+ #
51
+ # A filter query is used to reduce the number of documents before executing the actual query.
52
+ # By default, all filter query conditions must be met, each is passed on as a separate filter query.
53
+ def filter_query
54
+ filter_query_conditions.values.compact
55
+ end
56
+
57
+ # A hash of conditions, combined to a filter query by #filter_query.
58
+ # Note that all values of the hash must be valid Solr syntax.
59
+ # The keys have no meaning and exist only so single conditions can be replaced
60
+ # in a subclass:
61
+ #
62
+ # class SearchRequest < LuceneSearchRequest
63
+ # def filter_query_conditions
64
+ # super.merge(:subset => 'path:/de/*')
65
+ # end
66
+ # end
67
+ def filter_query_conditions
68
+ conditions = {}
69
+ conditions[:object_type] = 'NOT object_type:image'
70
+ conditions[:suppress_export] = 'NOT suppress_export:1'
71
+ now = Time.now
72
+ conditions[:valid_from] = "NOT valid_from:[#{now.succ.to_iso} TO *]"
73
+ conditions[:valid_until] = "NOT valid_until:[* TO #{now.to_iso}]"
74
+ conditions.merge(@options[:filter_query] || {})
75
+ end
76
+
77
+ private
78
+
79
+ def solr_query_parameters
80
+ {
81
+ :q => solr_query_for_query_string,
82
+ :fq => filter_query,
83
+ :fl => 'id,score',
84
+ :start => @options[:offset],
85
+ :rows => @options[:limit]
86
+ }.merge(@options[:solr_parameters] || {})
87
+ end
88
+
89
+ def build_search_result(total_hits, docs, max_score)
90
+ result = SES::SearchResult.new(total_hits)
91
+ docs.each do |doc|
92
+ begin
93
+ id = doc['id']
94
+ score = doc['score'] / max_score
95
+ hit = SES::Hit.new(id, score, doc)
96
+ if @options[:lazy_load].blank? && hit.obj.blank?
97
+ Rails.logger.warn("OBJ with ID ##{doc['id']} not found: This search result will not be shown")
98
+ else
99
+ result << hit
100
+ end
101
+ end
102
+ end
103
+ result
104
+ end
105
+ end
106
+
107
+ end
@@ -0,0 +1,76 @@
1
+ module RailsConnector
2
+
3
+ # This class provides methods used to retrieve objects from CMS based an entry
4
+ # in CMS of the obj_class <tt>NamedLink</tt>.
5
+
6
+ class NamedLink < Obj
7
+
8
+ class NotFound < StandardError
9
+ end
10
+
11
+ @@named_links_cache = nil
12
+ @@updated_at = nil
13
+ @@cache_expiry_time = nil
14
+
15
+ # Generates a cache of named links based on the CMS objects related link list.
16
+ # Raises exceptions if zero or more than one objects have this obj_class
17
+ def self.generate_named_links_cache
18
+ return if @@named_links_cache
19
+
20
+ found_objects = self.all
21
+ Rails.logger.warn "Couldn't find NamedLink CMS Object!" if found_objects.empty?
22
+ Rails.logger.warn "More than one NamedLink CMS Object found!" if found_objects.size > 1
23
+
24
+ @@named_links_cache = found_objects.
25
+ sort_by(&:valid_from).
26
+ reverse.
27
+ map(&:related_links).
28
+ flatten(1).
29
+ each_with_object({}) do |link_obj, temp|
30
+ temp[link_obj.title] = link_obj.destination_object
31
+ end
32
+
33
+ @@updated_at = Time.now
34
+ return nil
35
+ end
36
+
37
+ # Sets the time in minutes after which the cache will be expired.
38
+ # The default expiry time is set to 1 minute
39
+ def self.cache_expiry_time=(value)
40
+ @@cache_expiry_time = value
41
+ end
42
+
43
+ def self.cache_expiry_time #:nodoc:
44
+ @@cache_expiry_time || 1
45
+ end
46
+
47
+ def self.cache_expired? #:nodoc:
48
+ @@updated_at.nil? || @@updated_at <= cache_expiry_time.minute.ago
49
+ end
50
+
51
+ # Returns the CMS object mapped to the given title or nil.
52
+ # The title can be a string of symbol.
53
+ # If :cache => false is provided, the object will not be fetched from the cache.
54
+ def self.get_object(title, options = {})
55
+ object = named_links[title.to_s]
56
+ raise NotFound, "The NamedLink '#{title.to_s}' does not exist" if object.nil?
57
+ options[:cache] == false ? object.reload : object
58
+ end
59
+
60
+ def self.reset_cache #:nodoc:
61
+ @@named_links_cache = nil
62
+ end
63
+
64
+ def self.named_links #:nodoc:
65
+ reset_cache if cache_expired?
66
+ generate_named_links_cache unless named_links_cache
67
+ named_links_cache
68
+ end
69
+
70
+ def self.named_links_cache #:nodoc:
71
+ @@named_links_cache
72
+ end
73
+
74
+ end
75
+
76
+ end
@@ -0,0 +1,16 @@
1
+ module RailsConnector
2
+
3
+ # The CMS news object
4
+ #
5
+ # This class models the join table between channels and objects.
6
+ # Instances of this class also hold information about validity range.
7
+
8
+ class News < CmsBaseModel
9
+
10
+ self.primary_key = "news_id"
11
+
12
+ belongs_to :object, :class_name => 'Obj', :foreign_key => 'object_id'
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,517 @@
1
+ require "json"
2
+
3
+ module RailsConnector
4
+
5
+ # The CMS file class
6
+ #
7
+ # [children] an Array of objects, Obj, of which this one is the parent
8
+ # [parent] the Obj of which this one is a child - nil for the root object
9
+ # [sort_value] the sort value of this object with respect to its siblings, calculated using the sort criteria of the parent
10
+ class Obj < CmsBaseModel
11
+ include AttrValueProvider
12
+ include DateAttribute
13
+ include SEO
14
+
15
+ self.store_full_sti_class = false
16
+
17
+ def self.configure_for_content(which) # :nodoc:
18
+ case which
19
+ when :released then configure_column_information("objs", true)
20
+ when :edited
21
+ configure_column_information("preview_objs", false)
22
+ has_many(:permissions, :class_name => "::RailsConnector::Permission", :foreign_key => "object_id")
23
+ else
24
+ raise "configure_for_content called with unknown parameter #{which}"
25
+ end
26
+ end
27
+
28
+ def self.configure_column_information(table_name_postfix, use_cached_permissions)
29
+ reset_column_information
30
+
31
+ self.table_name = "#{table_name_prefix}#{table_name_postfix}"
32
+ self.primary_key = "obj_id"
33
+ self.inheritance_column = "obj_class"
34
+
35
+ @@use_cached_permissions = use_cached_permissions
36
+ end
37
+
38
+ # use released contents as a default
39
+ configure_for_content(:released)
40
+
41
+ # Patch to avoid a type_condition being added by ActiveRecord::Base.add_conditions! for Obj.find(params):
42
+ def self.descends_from_active_record? # :nodoc:
43
+ superclass == CmsBaseModel
44
+ end
45
+
46
+ # A CMS administrator can specify the <tt>obj_class</tt> for a given CMS object.
47
+ # In Rails, this could be either:
48
+ #
49
+ # * A valid and existing model name
50
+ # * A valid and non-existing model name
51
+ # * An invalid model name
52
+ #
53
+ # Rails' STI mechanism only considers the first case.
54
+ # In any other case, RailsConnector::Obj is used, except when explicitely asked
55
+ # for a model in the RailsConnector namespace (RailsConnector::Permission etc.)
56
+
57
+ def self.compute_type(type_name) # :nodoc:
58
+ try_type { type_name.constantize } ||
59
+ self
60
+ end
61
+
62
+ def permissions
63
+ @@use_cached_permissions ? attr_dict.permissions : super
64
+ end
65
+
66
+ def permitted_for_user?(user)
67
+ if permitted_groups.blank?
68
+ true
69
+ else
70
+ if user
71
+ (permitted_groups & user.live_server_groups).any?
72
+ else
73
+ false
74
+ end
75
+ end
76
+ end
77
+
78
+ # Returns the root Obj. Its id is 2001 and cannot be changed.
79
+ def self.root
80
+ Obj.find(2001)
81
+ end
82
+
83
+ # Returns the homepage object. This can be overwritten in your application's +ObjExtensions+.
84
+ # Use <tt>Obj#homepage?</tt> to check if an object is the homepage.
85
+ def self.homepage
86
+ root
87
+ end
88
+
89
+ # for testing purposes only
90
+ def self.reset_homepage #:nodoc:
91
+ @@homepage_id = nil
92
+ end
93
+
94
+ # returns the obj's permalink.
95
+ def permalink
96
+ self[:permalink]
97
+ end
98
+
99
+ # This method determines the controller that should be invoked when the Obj is requested.
100
+ # By default a controller matching the Obj's obj_class will be used.
101
+ # If the controller does not exist, the CmsController will be used as a fallback.
102
+ # Overwrite this method to force a different controller to be used.
103
+ def controller_name
104
+ obj_class
105
+ end
106
+
107
+ # This method determines the action that should be invoked when the Obj is requested.
108
+ # The default action is 'index'.
109
+ # Overwrite this method to force a different action to be used.
110
+ def controller_action_name
111
+ "index"
112
+ end
113
+
114
+ @@homepage_id = nil
115
+ # Returns true if the current object has the same id as the homepage object. Always use this method instead of
116
+ # manually comparing an object to <tt>Obj.homepage</tt>, as Obj#homepage? caches the object id
117
+ # and thus requires no extra database access.
118
+ def homepage?
119
+ self.id == (@@homepage_id ||= self.class.homepage.id)
120
+ end
121
+
122
+ # Returns the title of the content or the name.
123
+ def display_title
124
+ self.title || name
125
+ end
126
+
127
+ OBJECT_TYPES = {
128
+ '2' => :document,
129
+ '5' => :publication,
130
+ 'B' => :image,
131
+ 'C' => :generic
132
+ }.freeze unless defined?(OBJECT_TYPES)
133
+
134
+ # Returns the type of the object: :document, :publication, :image or :generic
135
+ def object_type
136
+ OBJECT_TYPES[obj_type_code]
137
+ end
138
+
139
+ # Returns true if image? or generic?
140
+ def binary?
141
+ [:image, :generic].include? object_type
142
+ end
143
+
144
+ # Returns true if object_type == :image
145
+ def image?
146
+ object_type == :image
147
+ end
148
+
149
+ # Returns true if object_type == :generic
150
+ def generic?
151
+ object_type == :generic
152
+ end
153
+
154
+ # Returns true if object_type == :publication (for folders)
155
+ def publication?
156
+ object_type == :publication
157
+ end
158
+
159
+ # Returns true if object_type == :document
160
+ def document?
161
+ object_type == :document
162
+ end
163
+
164
+ # Returns true if this object is active (time_when is in objects time interval)
165
+ def active?(time_when = nil)
166
+ return false if !valid_from
167
+ time_then = time_when || Obj.preview_time
168
+ valid_from <= time_then && (!valid_until || time_then <= valid_until)
169
+ end
170
+
171
+ # Returns true if this object has edited content.
172
+ # Note that edited content is not available when Configuration.mode == :live
173
+ # and edited? will always return false in this case.
174
+ def edited?
175
+ (is_edited == 1)
176
+ end
177
+
178
+ # Returns true if this object has released content
179
+ def released?
180
+ (is_released == 1)
181
+ end
182
+
183
+ # Returns true if the Obj is suppressed.
184
+ # A suppressed Obj does not represent an entire web page, but only a part of a page
185
+ # (for example a teaser) and will not be delivered by the rails application
186
+ # as a standalone web page.
187
+ def suppressed?
188
+ suppress_export == 1
189
+ end
190
+
191
+ # Returns true if the export of the object is not suppressed and the content is active?
192
+ def exportable?(current_time = nil)
193
+ !suppressed? && active?(current_time)
194
+ end
195
+
196
+ # Returns the file name to which the Content.file_extension has been appended.
197
+ def filename
198
+ extension = ".#{file_extension}" unless file_extension.blank?
199
+ "#{name}#{extension}"
200
+ rescue NoMethodError
201
+ name
202
+ end
203
+
204
+ # Returns an array with the names of groups that are permitted to access this Obj.
205
+ # This corresponds to the cms permission "permissionLiveServerRead".
206
+ def permitted_groups
207
+ attr_dict.permitted_groups
208
+ end
209
+
210
+ # Returns true if this object is the root object.
211
+ def root?
212
+ parent_obj_id.nil?
213
+ end
214
+
215
+ has_many :children, :class_name => 'Obj', :foreign_key => 'parent_obj_id'
216
+ belongs_to :parent, :class_name => 'Obj', :foreign_key => 'parent_obj_id'
217
+
218
+ # Returns a list of exportable? children excluding the binary? ones unless :all is specfied.
219
+ # This is mainly used for navigations.
220
+ def toclist(*args)
221
+ return [] unless publication?
222
+ time = args.detect {|value| value.kind_of? Time}
223
+ toclist = children.find(:all).select{ |toc| toc.exportable?(time) }
224
+ toclist = toclist.reject { |toc| toc.binary? } unless args.include?(:all)
225
+ toclist
226
+ end
227
+
228
+ # Returns the sorted +toclist+, respecting sort order and type of this Obj.
229
+ def sorted_toclist(*args)
230
+ list = self.toclist(*args)
231
+ return [] if list.blank?
232
+
233
+ cached_sort_key1 = self.sort_key1
234
+ cached_sort_type1 = self.sort_type1
235
+
236
+ sorted_list =
237
+ if cached_sort_key1.blank?
238
+ list.sort { |left_obj, right_obj| left_obj.name <=> right_obj.name }
239
+ else
240
+ cached_sort_key2 = self.sort_key2
241
+ cached_sort_type2 = self.sort_type2
242
+ cached_sort_key3 = self.sort_key3
243
+ cached_sort_type3 = self.sort_type3
244
+
245
+ list.sort do |left_obj, right_obj|
246
+ compare = compare_on_sort_key(left_obj, right_obj, cached_sort_key1, cached_sort_type1)
247
+ if compare == 0 && cached_sort_key2
248
+ compare = compare_on_sort_key(left_obj, right_obj, cached_sort_key2, cached_sort_type2)
249
+ if compare == 0 && cached_sort_key3
250
+ compare = compare_on_sort_key(left_obj, right_obj, cached_sort_key3, cached_sort_type3)
251
+ end
252
+ end
253
+ compare
254
+ end
255
+ end
256
+
257
+ return self.sort_order == "descending" ? sorted_list.reverse : sorted_list
258
+ end
259
+
260
+ def sort_key1 # :nodoc:
261
+ self[:sort_key1]
262
+ end
263
+
264
+ def sort_key2 # :nodoc:
265
+ self[:sort_key2]
266
+ end
267
+
268
+ def sort_key3 # :nodoc:
269
+ self[:sort_key3]
270
+ end
271
+
272
+ # Returns an Array of all the ancestor objects, starting at the root and ending at this object's parent.
273
+ def ancestors
274
+ if parent
275
+ parent.ancestors + [parent]
276
+ else
277
+ []
278
+ end
279
+ end
280
+
281
+ # Returns the Object with the given name next in the hierarchy
282
+ # returns nil if no object with the given name was found.
283
+ def find_nearest(name)
284
+ obj = self.children.find_by_name(name)
285
+ return obj if obj and obj.active?
286
+ parent.find_nearest(name) unless self.root?
287
+ end
288
+
289
+ # Returns the value of the attribute specified by its name.
290
+ #
291
+ # Passing an invalid key will not raise an error, but return nil.
292
+ def [](key)
293
+ # convenience access to name
294
+ return name if key.to_sym == :name
295
+
296
+ # regular activerecord attributes
297
+ if @attributes.include?(key.to_s)
298
+ if key == :valid_from or key == :valid_until or key == :last_changed
299
+ return as_date(super(key))
300
+ else
301
+ return super(key)
302
+ end
303
+ end
304
+
305
+ # Unknown Obj attributes are delegated to the corresponding instance of AttrDict.
306
+ begin
307
+ return (attr_dict.send key)
308
+ rescue NoMethodError
309
+ end
310
+
311
+ # fallback
312
+ return nil
313
+ end
314
+
315
+ # Reloads the attributes of this object from the database, invalidates custom attributes
316
+ def reload
317
+ super
318
+ @attr_dict = nil
319
+ @attr_values = nil
320
+ @attr_defs = nil
321
+ self
322
+ end
323
+
324
+ # object_name is a legacy alias to name. Please use name instead, because only name
325
+ # works with ActiveRecord's dynamic finder methods like find_by_name(...)
326
+ def object_name
327
+ name
328
+ end
329
+
330
+ # object_class is a legacy alias to name. Please use obj_class instead, because only obj_class
331
+ # works with ActiveRecord's dynamic finder methods like find_by_obj_class(...)
332
+ def object_class
333
+ obj_class
334
+ end
335
+
336
+ # for binary Objs body_length equals the file size
337
+ # for non-binary Objs body_length equals the number of characters in the body (main content)
338
+ def body_length
339
+ attr_dict.body_length
340
+ end
341
+
342
+ # Override this method to provide an external url
343
+ # where the content (the body) of this Obj can be downloaded.
344
+ # The Rails Connector will then use this url when creating links to this Obj.
345
+ # This is useful when delivering images via a content delivery network (CDN), for example.
346
+ # Returns nil by default.
347
+ #
348
+ # Note: When this method returns an url, the Rails Connector's DefaultCmsController
349
+ # will redirect to this url instead of delivering this Obj's body.
350
+ # And the Rails Connector's cms_path and cms_url helpers will link to this url
351
+ # instead of linking to the Obj.
352
+ #
353
+ # Note also that the url will be used regardless of the Obj's permissions, so be careful not
354
+ # to provide urls that contain unprotected secrets.
355
+ def body_data_url
356
+ nil
357
+ end
358
+
359
+ def set_attr_values(dictionary) # :nodoc:
360
+ @attr_values = dictionary
361
+ @attr_dict = nil
362
+ end
363
+
364
+ # Returns an instance of AttrDict which provides access to formatted attribute values (i.e. content).
365
+ # The method uses attr_defs to determine the right formatting depending on the particular field type.
366
+ def attr_dict # :nodoc:
367
+ @attr_dict ||= AttrDict.new(self, attr_values, attr_defs)
368
+ end
369
+
370
+ # Returns a nested hash of attribute values.
371
+ def attr_values # :nodoc:
372
+ @attr_values ||= fetch_attr_values
373
+ end
374
+
375
+ # Returns a nested hash of attribute definitions.
376
+ def attr_defs # :nodoc:
377
+ @attr_defs ||= JSON.parse(read_attribute(:attr_defs) || "{}")
378
+ end
379
+
380
+ # Provides access to field metadata:
381
+ #
382
+ # <%= @obj.metadata_for_field(:body_de, :titles, :en) %>
383
+ #
384
+ # In addition to the field name, the method takes an arbitrary number of arguments
385
+ # constituting a path through the nested (hash) structures of the attribute definition (attr_defs).
386
+ #
387
+ # If the path doesn't fit the metadata structure, the method returns nil and doesn't raise an exception.
388
+ #
389
+ def metadata_for_field(field, *args) # :nodoc:
390
+ rslt = fiona_fields[field.to_s] || attr_defs[field.to_s]
391
+ args.each do |key|
392
+ rslt = rslt[key.to_s] unless rslt.nil?
393
+ end
394
+ rslt
395
+ end
396
+
397
+ def last_changed
398
+ self[:last_changed]
399
+ end
400
+
401
+ def valid_from
402
+ self[:valid_from]
403
+ end
404
+
405
+ def valid_until
406
+ self[:valid_until]
407
+ end
408
+
409
+ # deprecated, use file_extension instead
410
+ def content_type
411
+ logger.warn "DEPRECATION WARNING: Obj#content_type is deprecated, use file_extension instead"
412
+ file_extension
413
+ end
414
+
415
+ # Returns the MIME-type as determined from the file_extension - see MIME::Types
416
+ def mime_type
417
+ @mime_type ||= compute_mime_type
418
+ end
419
+
420
+ def to_liquid # :nodoc:
421
+ LiquidSupport::ObjDrop.new(self)
422
+ end
423
+
424
+ def respond_to?(method_id, include_private=false) # :nodoc:
425
+ if super
426
+ true
427
+ elsif %w(_attr_dict _attr_defs _attr_values).include?(method_id.to_s)
428
+ # prevent infinite recursion when calling "attr_*" below,
429
+ # since rails checks the absence of an "_attr_*" method internally
430
+ return false
431
+ else
432
+ attr_dict.respond_to?(method_id)
433
+ end
434
+ end
435
+
436
+ def self.preview_time=(t) # :nodoc:
437
+ Thread.current[:preview_time] = t
438
+ end
439
+
440
+ def self.preview_time # :nodoc:
441
+ Thread.current[:preview_time] || Time.now
442
+ end
443
+
444
+ private
445
+
446
+ def as_date(value)
447
+ DateAttribute.parse(value) unless value.nil?
448
+ end
449
+
450
+ # Forwards any unknown method call to a corresponding instance
451
+ # of AttrDict and thus provides access to object fields, i.e. content.
452
+ #
453
+ # In case of an invalid method call, an error is raised.
454
+ #
455
+ # Hint: Use [] instead to suppress error messages.
456
+ def method_missing(method_id, *args)
457
+ super
458
+ rescue NoMethodError, NameError
459
+ # prevent infinite recursion when calling "attr_*" below,
460
+ # since rails checks the absence of an "_attr_*" method internally
461
+ raise if %w(_attr_dict _attr_defs _attr_values).include?(method_id.to_s)
462
+
463
+ if attr_dict.respond_to?(method_id)
464
+ attr_dict.send method_id, *args
465
+ else
466
+ raise
467
+ end
468
+ end
469
+
470
+ def fiona_fields
471
+ @fiona_fields ||=
472
+ ['name', 'obj_class', 'workflow', 'suppressexport', 'permalink'].inject({}) do |all,field|
473
+ all.merge! field => {
474
+ 'titles' => {'de' => field.humanize, 'en' => field.humanize},
475
+ 'type' => 'string',
476
+ 'help_texts' => {'de' => field, 'en' => field}
477
+ }
478
+ end
479
+ end
480
+
481
+ def compute_mime_type
482
+ MIME::Types.type_for(file_extension).first.content_type
483
+ rescue
484
+ binary? ? "application/octet-stream" : "text/plain"
485
+ end
486
+
487
+ def compare_on_sort_key(left_obj, right_obj, my_sort_key, my_sort_type)
488
+ left_value = left_obj[my_sort_key]
489
+ right_value = right_obj[my_sort_key]
490
+
491
+ if left_value.nil?
492
+ 1
493
+ elsif right_value.nil?
494
+ -1
495
+ # hardcoded type check needed for speed
496
+ elsif left_value.is_a?(Time) && right_value.is_a?(Time)
497
+ left_value <=> right_value
498
+ else
499
+ if my_sort_type == "numeric"
500
+ (left_value.to_i rescue 0) <=> (right_value.to_i rescue 0)
501
+ else
502
+ left_value.to_s.downcase <=> right_value.to_s.downcase
503
+ end
504
+ end
505
+ end
506
+
507
+ def self.try_type
508
+ result = yield
509
+ result if class_of_active_record_descendant(result)
510
+ rescue NameError, ActiveRecord::ActiveRecordError
511
+ nil
512
+ end
513
+
514
+ end
515
+
516
+ end
517
+