-
Notifications
You must be signed in to change notification settings - Fork 617
Expand file tree
/
Copy pathcontent_entry_service.rb
More file actions
279 lines (232 loc) · 8.82 KB
/
content_entry_service.rb
File metadata and controls
279 lines (232 loc) · 8.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
module Locomotive
class ContentEntryService < Struct.new(:content_type, :account, :locale)
include Locomotive::Concerns::ActivityService
# List all the entries of a content type.
#
# If the content type allows the pagination, in other words, if the entries are not
# ordered by the position column), then this method will return
# a paginated list of entries.
#
# This list can also be filtered by the _label attribute, by setting the "q" key in the options.
#
# For a more powerful search, you can use the "where" key which accepts a JSON string or a Hash.
#
# The no_pagination option is used to skip the pagination of the content entries
#
# @param [ Hash ] options The options for the pagination and the filtering
#
# @return [ Object ] a paginated list of content entries
#
def all(options = {})
_options = prepare_options_for_all(options)
content_type.ordered_entries(_options)
end
# Sort the entries of a content type.
#
# @param [ Array ] ids Ordered list of content entry ids.
#
def sort(ids)
klass = self.content_type.klass_with_custom_fields(:entries)
klass.sort_entries!(ids, self.content_type.sortable_column)
# we need to clear out the cache
content_type.site.touch(:content_version)
track_activity 'content_entry.sorted', parameters: activity_parameters
end
# Create a content entry from the attributes passed in parameter.
# It sets the created_by column with the current account.
#
# @param [ Hash ] attributes The attributes of new content entry.
#
# @return [ Object ] An instance of the content entry.
#
def create(attributes)
sanitize_attributes!(attributes)
content_type.entries.build(attributes).tap do |entry|
entry.created_by = account if account
if entry.save
track_activity 'content_entry.created', parameters: activity_parameters(entry)
end
end
end
def create!(attributes)
if (entry = create(attributes)).errors.empty?
entry
else
entry.fail_due_to_validation!
end
end
# Create a content entry from the attributes passed in parameter.
# It does not set the created_by column since it's called
# from the public side of the site with no logged in account.
# The attributes are filtered through the Grape::Entity / Form.
# A notification email is sent to the selected members of the site.
#
# @param [ Hash ] attributes The attributes of new content entry.
# @param [ Hash ] options For now, only store the ip address of the person who submitted the content entry + other accounts
#
# @return [ Object ] An instance of the content entry.
#
def public_create(attributes, options = {})
form = Locomotive::API::Forms::ContentEntryForm.new(self.content_type, attributes)
without_tracking_activity { create(form.serializable_hash) }.tap do |entry|
if entry.errors.empty?
# send an email to selected local accounts + potential external accounts described by their emails
send_notifications(entry, options[:emails])
track_activity 'content_entry.created_public', locale: locale, parameters: activity_parameters(entry)
end
end
end
# Update a content entry from the attributes passed in parameter.
# It sets the updated_by column with the current account.
#
# @param [ Object ] entry The content entry to update.
# @param [ Hash ] attributes The attributes of new content entry.
#
# @return [ Object ] The instance of the content entry.
#
def update(entry, attributes)
sanitize_attributes!(attributes)
entry.tap do |entry|
entry.attributes = attributes
entry.updated_by = account
if entry.save
track_activity 'content_entry.updated', locale: locale, parameters: activity_parameters(entry)
end
end
end
def update!(entry, attributes)
update(entry, attributes)
if entry.errors.empty?
entry
else
entry.fail_due_to_validation!
end
end
def destroy(entry)
entry.destroy.tap do
track_activity 'content_entry.destroyed', parameters: activity_parameters(entry)
end
end
# Destroy all the entries described by their id
def bulk_destroy(ids)
content_type.entries.where(:_id.in => ids).map do |entry|
entry.destroy
entry._label
end.tap do |labels|
track_activity 'content_entry.destroyed_bulk',
parameters: activity_parameters.merge(labels: labels)
end
end
# Destroy all the entries of a content type.
# Runs each entry's destroy callbacks.
#
def destroy_all
content_type.entries.destroy_all
end
# Make sure the content entries has a non-blank slug in the new locales.
# We take the slug in the default locale or the previous default locale (if provided)
#
def localize(new_locales, previous_default_locale)
default_locale = previous_default_locale || content_type.site.default_locale
content_type.entries.each_by(50) do |entry|
slug = entry._slug_translations[default_locale]
new_locales.each do |locale|
next if locale == default_locale
::Mongoid::Fields::I18n.with_locale(locale) do
entry._slug ||= slug
end
end
entry.save if entry.changed?
end
end
def send_notifications(entry, emails = nil)
return unless self.content_type.public_submission_enabled?
Locomotive::Account.any_of(
{ :_id.in => self.content_type.public_submission_accounts || [] },
{ :email.in => emails || [] }
).each do |account|
Locomotive::Notifications.new_content_entry(site, account, entry).deliver
end
end
# Give the list of permitted attributes for a content entry.
# It includes:
# - the default ones (_slug, seo, ...etc)
# - the dynamic simple attributes (strings, texts, dates, belongs_to, ...etc)
# - the relationships (has_many + many_to_many)
#
# @return [ Array ] List of permitted attributes
#
def permitted_attributes
# needed to get the custom fields
_entry = content_type.entries.build
default = %w(_slug _position _visible seo_title meta_keywords meta_description meta_robots)
dynamic = _entry.custom_fields_safe_setters
referenced = {}
# has_many
has_many = _entry.has_many_custom_fields.each do |name, inverse_of|
referenced["#{name}_attributes"] = [:_id, :_destroy, :"position_in_#{inverse_of}"]
end
# many_to_many
many_to_many = _entry.many_to_many_custom_fields.each do |(_, s)|
referenced[s] = []
end
(default + dynamic + [referenced.empty? ? nil : referenced]).compact
end
def site
self.content_type.site
end
protected
def sanitize_attributes!(attributes)
# needed to get the custom fields
_entry = content_type.entries.build
# if the user deletes all the entries of a many_to_many field,
# make sure the list gets empty instead of nil.
_entry.many_to_many_custom_fields.each do |(n, s)|
next unless content_type.is_field_with_ui_enabled?(n)
# we don't want to clear the relationship with the form doesn't display the field
attributes[s] = [] unless attributes.has_key?(s)
end
end
def prepare_options_for_all(options)
{ where: prepare_where_statement(options) }.tap do |_options|
unless options[:no_pagination]
_options[:page] = options[:page] || 1
_options[:per_page] = options[:per_page] || Locomotive.config.ui[:per_page]
end
_options[:order_by] = options[:order_by] if options[:order_by]
end
end
def prepare_where_statement(options)
where = (case options[:where]
when String then options[:where].blank? ? {} : JSON.parse(options[:where])
when Hash then options[:where]
else {}
end).with_indifferent_access
if options[:q]
where.merge!(prepare_query_statement(options[:q]))
end
where
end
def prepare_query_statement(query)
regexp = /.*#{query.split.map { |q| Regexp.escape(q) }.join('.*')}.*/i
{}.tap do |where|
if self.content_type.filter_fields.blank?
where[content_type.label_field_name.to_sym] = regexp
else
where['$or'] = []
self.content_type.filter_fields.each do |field_name|
where['$or'] << { field_name => regexp }
end
end
end
end
def activity_parameters(entry = nil)
{
_id: entry.try(:_id),
label: entry.try(:_label),
content_type: content_type.name,
content_type_slug: content_type.slug
}
end
end
end