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.
- data/README +5 -0
- data/lib/comment.rb +4 -0
- data/lib/infopark_fiona_connector.rb +23 -0
- data/lib/rails_connector/attr_dict.rb +146 -0
- data/lib/rails_connector/attr_value_provider.bundle +0 -0
- data/lib/rails_connector/attr_value_provider.so +0 -0
- data/lib/rails_connector/attr_value_provider64.so +0 -0
- data/lib/rails_connector/attr_value_provider_loader.rb +11 -0
- data/lib/rails_connector/blob.rb +67 -0
- data/lib/rails_connector/blob_mysql.rb +42 -0
- data/lib/rails_connector/blob_oracle.rb +39 -0
- data/lib/rails_connector/cache_middleware.rb +13 -0
- data/lib/rails_connector/cms_base_model.rb +45 -0
- data/lib/rails_connector/date_attribute.rb +28 -0
- data/lib/rails_connector/default_search_request.rb +6 -0
- data/lib/rails_connector/errors.rb +8 -0
- data/lib/rails_connector/link.rb +135 -0
- data/lib/rails_connector/lucene_search_request.rb +107 -0
- data/lib/rails_connector/named_link.rb +76 -0
- data/lib/rails_connector/news.rb +16 -0
- data/lib/rails_connector/obj.rb +517 -0
- data/lib/rails_connector/permission.rb +41 -0
- data/lib/rails_connector/rack_middlewares.rb +6 -0
- data/lib/rails_connector/ses/verity_accessor.rb +130 -0
- data/lib/rails_connector/verity_search_request.rb +84 -0
- data/lib/rating.rb +2 -0
- metadata +149 -0
@@ -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
|
+
|