infopark_fiona_connector 6.8.0.beta.200.891.647580e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+