qcms 1.3.3

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.
Files changed (53) hide show
  1. data/.gitignore +14 -0
  2. data/README +70 -0
  3. data/Rakefile +39 -0
  4. data/VERSION +1 -0
  5. data/app/controllers/admin/admin_controller.rb +12 -0
  6. data/app/controllers/admin/documents_controller.rb +133 -0
  7. data/app/controllers/admin/meta_definitions_controller.rb +36 -0
  8. data/app/controllers/documents_controller.rb +187 -0
  9. data/app/controllers/sendmail_controller.rb +87 -0
  10. data/app/helpers/admin/documents_helper.rb +71 -0
  11. data/app/helpers/documents_helper.rb +30 -0
  12. data/app/helpers/sendmail_helper.rb +2 -0
  13. data/app/models/document.rb +292 -0
  14. data/app/models/document_mailer.rb +13 -0
  15. data/app/models/document_sweeper.rb +28 -0
  16. data/app/models/meta_definition.rb +57 -0
  17. data/app/models/sendmail.rb +18 -0
  18. data/app/views/admin/admin/system.html.erb +34 -0
  19. data/app/views/admin/documents/_form.html.erb +60 -0
  20. data/app/views/admin/documents/default.edit.html.erb +10 -0
  21. data/app/views/admin/documents/default.new.html.erb +10 -0
  22. data/app/views/admin/documents/default.show.html.erb +77 -0
  23. data/app/views/admin/documents/index.html.erb +32 -0
  24. data/app/views/admin/documents/shared/_resource_link.html.erb +6 -0
  25. data/app/views/document_mailer/new_document.erb +12 -0
  26. data/app/views/layouts/admin.html.erb +43 -0
  27. data/app/views/layouts/application.html.erb +44 -0
  28. data/app/views/pages/404.html.erb +28 -0
  29. data/app/views/pages/contact.html.erb +42 -0
  30. data/app/views/pages/default.html.erb +12 -0
  31. data/app/views/pages/feed.rss.builder +17 -0
  32. data/app/views/pages/home.html.erb +5 -0
  33. data/app/views/pages/search.html.erb +19 -0
  34. data/app/views/pages/shared/_archived_pages.erb +16 -0
  35. data/app/views/pages/shared/_related_pages.html.erb +13 -0
  36. data/app/views/pages/sitemap.html.erb +9 -0
  37. data/app/views/pages/template.erb +51 -0
  38. data/app/views/pages/thank_you.html.erb +1 -0
  39. data/app/views/sendmail/default.erb +12 -0
  40. data/config/sitemap.example.yml +39 -0
  41. data/db/migrate/20090824150210_create_documents.rb +37 -0
  42. data/db/migrate/20091208124512_create_meta_definition.rb +26 -0
  43. data/init.rb +1 -0
  44. data/install.rb +1 -0
  45. data/lib/qcms.rb +4 -0
  46. data/lib/tasks/cms.rake +237 -0
  47. data/qcms.gemspec +91 -0
  48. data/rails/init.rb +3 -0
  49. data/tasks/qcms_tasks.rake +223 -0
  50. data/test/qwerty_test.rb +8 -0
  51. data/test/test_helper.rb +3 -0
  52. data/uninstall.rb +1 -0
  53. metadata +107 -0
@@ -0,0 +1,87 @@
1
+ class SendmailController < ApplicationController
2
+ unloadable
3
+
4
+ def deliver
5
+ if has_required_fields
6
+ meta = params[:meta]
7
+
8
+ if recipients_valid(meta[:recipients])
9
+
10
+ template = view_for([params[:form], 'default'], 'sendmail', '.erb')
11
+
12
+ Sendmail.deliver_form(sanatize_params(params), meta, template)
13
+
14
+ # After deliver options (redirect, show flash message or render template)
15
+ if meta[:redirect_to]
16
+ flash[:notice] = meta[:message] if meta[:message]
17
+ redirect_to meta[:redirect_to] and return
18
+ end
19
+
20
+ if meta[:message]
21
+ flash[:notice] = meta[:message]
22
+ redirect_to root_url
23
+ end
24
+
25
+ if meta[:show_page]
26
+ render :template => File.join('pages', meta[:show_page]) + '.html.erb' and return
27
+ end
28
+ else
29
+ render :text => 'Error: Recipient not in white list'
30
+ end
31
+ else
32
+ render :text => 'Error: Required Field Missing'
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+
39
+ def view_for(filenames, suffix = '', prefix = '.html.erb')
40
+ filenames.each do | filename |
41
+ next if filename.nil?
42
+ self.view_paths.each do |path|
43
+ target = (File.join(path.to_s, suffix, filename.to_s) + prefix).downcase
44
+ logger.debug 'Looking: ' + target
45
+ if File.file? target
46
+ logger.debug 'Template CHOOSEN: ' + target
47
+ return filename
48
+ end
49
+ end
50
+ end
51
+ logger.debug 'NO TEMPLATE FOUND!!'
52
+ nil
53
+ end
54
+
55
+
56
+
57
+ # Anti SPAM detection
58
+ def recipients_valid(recipients)
59
+ recipients.split(',').each do |r|
60
+ unless Settings.forms.recipients.include? r.strip
61
+ logger.warn('Email Address not in white list: ' + r)
62
+ return false
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ def has_required_fields
69
+ required_fields = ['recipients', 'subject']
70
+ required_fields.each do |f|
71
+ unless params[:meta][f]
72
+ logger.debug 'Missing form field ' + f
73
+ return false
74
+ end
75
+ end
76
+ end
77
+
78
+ def sanatize_params(params)
79
+ black_list = ['meta', 'authenticity_token', 'id', 'controller', 'action', 'send', 'submit', 'form']
80
+ white_list = []
81
+ params.delete_if { |k,v| black_list.include? k.downcase }
82
+ params.each do |k,v|
83
+ params[k] = Sanitize.clean(v) unless white_list.include? k.downcase
84
+ end
85
+ params
86
+ end
87
+ end
@@ -0,0 +1,71 @@
1
+ module Admin::DocumentsHelper
2
+
3
+ # returns options Array for 'sort_by' select
4
+ def sort_by_options
5
+ options = { 'Date DESC' => 'published_at DESC',
6
+ 'Date ASC' => 'published_at ASC',
7
+ 'Title' => 'title',
8
+ 'Manually' => 'position ASC'
9
+ }
10
+
11
+ result = []
12
+ options.each do |k,v|
13
+ k = k + ' (Default)' if v == Settings.documents._sort_by
14
+ result << [k,v]
15
+ end
16
+
17
+ result.sort
18
+ end
19
+
20
+ # Returns options Array for 'per_page' select
21
+ def per_page_options
22
+ options = [1, 5, 10, 20, 50, 100]
23
+ result = []
24
+ options.each do | o |
25
+ k = o.to_s
26
+ v = o
27
+ k = k + ' (Default)' if o == Settings.documents.per_page
28
+ result << [k, v]
29
+ end
30
+ result
31
+ end
32
+
33
+ # Find the correct form partial for @document
34
+ def form_partial
35
+ file = "admin/documents/_#{@document.label}_form.html.erb"
36
+ self.view_paths.each do | view_path |
37
+ target = File.join(view_path, file)
38
+ logger.debug 'PARTIAL ' + target
39
+ if File.exists? target
40
+ return file.gsub('/_', '/')
41
+ end
42
+ end
43
+ "admin/documents/form"
44
+ end
45
+
46
+ def admin_bread_crumb(document, root = true)
47
+ result = ''
48
+ if root
49
+ result << '<ul class="bread_crumb">'
50
+ result << "<li><a href='#{admin_documents_path}'>Dashboard</a></li>"
51
+ end
52
+ if document
53
+ result << admin_bread_crumb(document.parent, false) if document.parent
54
+ unless document.new_record? # no title yet
55
+ result << '<li>'
56
+ result << (root ? (document.title || document.permalink) : link_to(document.permalink.titleize, admin_document_path(document.id)))
57
+ result << '</li>'
58
+ end
59
+ end
60
+ result << '</ul>' if root
61
+ result
62
+ end
63
+
64
+ def admin_cancel_document_path(document)
65
+ if document.parent
66
+ admin_document_path(document.parent.id)
67
+ else
68
+ admin_dashboard_path
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,30 @@
1
+ module DocumentsHelper
2
+
3
+ def related_pages(heading=nil)
4
+ render :partial => 'pages/shared/related_pages', :locals => { :heading => heading }
5
+ end
6
+
7
+ def archived_pages(heading=nil)
8
+ render :partial => 'pages/shared/archived_pages', :locals => { :heading => heading }
9
+ end
10
+
11
+ def bread_crumb(document)
12
+ result = '<ul class="bread_crumb">'
13
+ if document.parent
14
+ result = bread_crumb(document.parent)
15
+ end
16
+ result << '<li>' + link_to(document.title, document_path(document)) + '</li>'
17
+ result << '</ul>'
18
+ result
19
+ end
20
+
21
+ # def archive_date
22
+ # if showing_archive?
23
+ # "#{Date::MONTHNAMES[params[:month].to_i]} #{params[:year]}"
24
+ # end
25
+ # end
26
+ #
27
+ # def showing_archive?
28
+ # params[:month] && params[:year]
29
+ # end
30
+ end
@@ -0,0 +1,2 @@
1
+ module SendmailHelper
2
+ end
@@ -0,0 +1,292 @@
1
+ class Document < ActiveRecord::Base
2
+ acts_as_tree
3
+ path_finder :uid => 'permalink'
4
+
5
+ acts_as_list :scope => :parent
6
+
7
+ # Allow lists to be independant when grouping by type
8
+ # acts_as_list :scope => 'parent_id = #{parent_id} and type = \'#{type}\''
9
+
10
+ self.skip_time_zone_conversion_for_attributes = [] # FIX BUG: https://rails.lighthouseapp.com/projects/8994/tickets/1339-arbase-should-not-be-nuking-its-children-just-because-it-lost-interest
11
+
12
+ has_attached_file :resource,
13
+ :styles => { :medium => "300x300>", :thumb => "100x100>" },
14
+ :url => "/files/document/:id/:style.:extension",
15
+ :path => ":rails_root/public/files/document/:id/:style.:extension",
16
+ :whiny_thumbnails => false # allow non-images to be uploaded
17
+
18
+ # Prevent "not recognized by the 'identify' command" error for non-images
19
+ before_post_process :resource_image?
20
+ def resource_image?
21
+ !(resource_content_type =~ /^image.*/).nil?
22
+ end
23
+
24
+ # Settings this to true or '1' will remove the resource
25
+ attr_accessor :delete_resource
26
+
27
+ # Assosiations
28
+ belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
29
+ belongs_to :meta_definition
30
+
31
+ # Scopes
32
+ named_scope :recent, lambda { { :conditions => ['created_at > ?', 1.week.ago] } }
33
+ named_scope :latest, { :order => 'updated_at DESC' }
34
+ named_scope :published_between, lambda {|start_date, end_date| { :conditions => {:published_at => start_date..end_date } } }
35
+ named_scope :order_by, lambda { |order| { :order => order } }
36
+ named_scope :by_label, lambda { |label| { :conditions => { :label => label }, :include => [:meta_definition, :parent, :author] } }
37
+ named_scope :only, lambda { |limit| { :limit => limit } }
38
+ named_scope :roots, { :conditions => { :parent_id => nil }, :include => [:meta_definition, :parent] }
39
+ named_scope :public, { :conditions => { :state => 'published' }}
40
+ named_scope :offset, lambda { |offset| { :offset => offset } }
41
+
42
+ # Callbacks
43
+ before_validation_on_create :pull_meta_definition
44
+ before_validation_on_create :auto_populate
45
+ before_save :generate_summary, :set_meta_data, :sanatize_body
46
+ before_save :clear_resource # better name?
47
+ after_save :touch_parent
48
+ before_save :copy_permalink_to_path
49
+
50
+ # Validations
51
+ validates_presence_of :state, :author_id, :type, :label, :meta_definition_id
52
+ validates_presence_of :published_at, :if => proc { |d| d.published? }
53
+ validates_uniqueness_of :path
54
+
55
+ # States
56
+ state_machine :attribute => :state, :initial => :draft do
57
+ event :publish do
58
+ transition :draft => :published
59
+ end
60
+
61
+ event :unpublish do
62
+ transition :published => :draft
63
+ end
64
+
65
+ state :private, :system, :pending
66
+
67
+ after_transition :on => :published do |document, transition|
68
+ document.published_at = Time.now unless document.published_at
69
+ end
70
+ end
71
+
72
+ # autherisation
73
+ def allowed?(user, action)
74
+ meta_definition.allowed?(user, action)
75
+ end
76
+
77
+ def meta_definition_for(label)
78
+ meta_definition.children.by_label(label).first
79
+ end
80
+
81
+ def module_name
82
+ self.root.permalink.titleize
83
+ end
84
+
85
+ def permalink
86
+ create_permalink if read_attribute(:permalink).blank?
87
+ read_attribute(:permalink)
88
+ end
89
+
90
+ # Rails support
91
+ def to_param
92
+ path
93
+ end
94
+
95
+ # allow Document['homepage']
96
+ def self.[](path)
97
+ find_by_path(path)
98
+ end
99
+
100
+ # All children grouped by year and month
101
+ def archive
102
+ result = ActiveSupport::OrderedHash.new
103
+ children.public.group_by(&:year).sort { |a,b| b <=> a }.each do | year, posts |
104
+ result[year] = ActiveSupport::OrderedHash.new
105
+ grouped_posts = posts.group_by(&:month_num)
106
+ (1..12).each do | month |
107
+ result[year][month] = grouped_posts[month] || []
108
+ end
109
+ end
110
+ result
111
+ end
112
+
113
+ # children for month and year
114
+ def archive_for(month, year)
115
+ from_date = Time.parse("#{month}/01/#{year}") # mm/dd/yy
116
+ to_date = from_date.end_of_month
117
+ children.public.published_between(from_date, to_date).order_by('published_at DESC')
118
+ end
119
+
120
+ # Never return nil
121
+ # FIXME: use database default value
122
+ def body
123
+ read_attribute(:body) || ''
124
+ end
125
+
126
+ # Types of children this document can have
127
+ def child_labels
128
+ self.meta_definition.children.map { |c| c.label }
129
+ end
130
+
131
+ def can_have_children?
132
+ !self.meta_definition.children.empty?
133
+ end
134
+
135
+ def image
136
+ self.resource
137
+ end
138
+
139
+ def image_description
140
+ self.resource_description
141
+ end
142
+
143
+ def attachment
144
+ self.resource
145
+ end
146
+
147
+ # Used for grouping
148
+ # FIXME: put in module eg. date_groupable :column => 'published_at'
149
+ def week
150
+ published_at.strftime('%W')
151
+ end
152
+
153
+ def day
154
+ published_at.strftime('%d')
155
+ end
156
+
157
+ def month
158
+ published_at.strftime('%B')
159
+ end
160
+
161
+ def month_num
162
+ published_at.month
163
+ end
164
+
165
+ def year
166
+ published_at.strftime('%Y')
167
+ end
168
+
169
+ def month_year
170
+ published_at.strftime('%B %Y')
171
+ end
172
+
173
+ # Note: The standard ActiveRecord#exists? only fetches the id field so
174
+ # the add_assosiations (after_find) fails due to use of assosiations
175
+ def self.exists?(conditions)
176
+ self.find(:first, :conditions => conditions)
177
+ end
178
+
179
+ # after_find can only be called like this
180
+ def after_find
181
+ add_assosiations
182
+ map_fields
183
+ end
184
+
185
+ private
186
+
187
+ # make sure the last element of the path is the same as the permalink
188
+ def copy_permalink_to_path
189
+ self.path = (self.path.split('/')[0..-2] + [self.permalink]).join('/')
190
+ end
191
+
192
+ # This will trigger a touch all they way to root
193
+ # after_save
194
+ def touch_parent
195
+ self.parent.touch if parent
196
+ end
197
+
198
+ # before_validation_on_create
199
+ def pull_meta_definition
200
+ return if root?
201
+ self.meta_definition = self.parent.meta_definition.children.by_label(label).first
202
+ raise "No Meta Definition found for label [#{self.label}] in parents children. Choices are [#{self.parent.meta_definition.children.map { |md| md.label }.join(', ') }]" if self.meta_definition.nil?
203
+
204
+ # Enforced values
205
+ self.label = self.meta_definition.label # cache label to reduce sql calls
206
+
207
+ # Defaults values
208
+ self.type ||= self.meta_definition.default_type || 'Document'
209
+ self.state ||= self.meta_definition.default_state || 'draft'
210
+ end
211
+
212
+ # Alias children and parent eg. @post.comments and @comment.post
213
+ # Note they are scope to a published state
214
+ def add_assosiations
215
+ self.child_labels.each do | label |
216
+ self.class.send(:define_method, label.pluralize) { self.children.with_state(:published).by_label(label) }
217
+ end
218
+
219
+ if self.parent
220
+ self.class.send(:define_method, self.parent.label) { self.parent }
221
+ end
222
+ end
223
+
224
+ def map_fields
225
+ meta_definition.field_map.each do | source , target |
226
+ self.class.send(:define_method, source) { send(target) }
227
+ end
228
+ end
229
+
230
+ # before_validation_on_create
231
+ def auto_populate
232
+ create_permalink
233
+ generate_summary
234
+ set_meta_data
235
+ end
236
+
237
+ def generate_summary
238
+ self.summary = Sanitize.clean(self.body).to(255) if self.body_changed? && (!self.summary_changed? or self.summary.blank?)
239
+ end
240
+
241
+ def set_meta_data
242
+ self.meta_title = self.title.to(50) if self.title_changed? && (!self.meta_title_changed? or self.meta_title.blank?)
243
+ self.meta_description = self.summary.to(50) if self.summary_changed? && (!self.meta_description_changed? or self.meta_description.blank?)
244
+ end
245
+
246
+ # before_save
247
+ def sanatize_body
248
+ if self.meta_definition.body_strip_html?
249
+ self.body = Sanitize.clean(self.body)
250
+ end
251
+
252
+ unless self.meta_definition.body_length.blank?
253
+ self.body = self.body.to(self.meta_definition.body_length)
254
+ end
255
+ end
256
+
257
+ # TODO: Refactor in to a module
258
+ def create_permalink(separator = '_', max_size = 127)
259
+ pull_meta_definition if self.meta_definition.nil?
260
+
261
+ unless self.meta_definition.randomise_permalink?
262
+ return '' if title.blank?
263
+ # words to ignore, usually the same words ignored by search engines
264
+ ignore_words = ['a', 'an', 'the', '?']
265
+ ignore_re = String.new
266
+ # building ignore regexp
267
+ ignore_words.each { |word|
268
+ ignore_re << word + '\b|\b'
269
+ }
270
+ ignore_re = '\b' + ignore_re + '\b'
271
+ new_permalink = self.title.dup.gsub("'", separator)
272
+ new_permalink.gsub!(ignore_re, '')
273
+ new_permalink.downcase!
274
+ new_permalink.strip!
275
+ new_permalink.gsub!(/[^a-z0-9]+/, separator)
276
+ new_permalink = new_permalink.to(max_size)
277
+ new_permalink.gsub!(/^\\#{separator}+|\\#{separator}+$/, '')
278
+ else
279
+ new_permalink = ActiveSupport::SecureRandom.hex(16)
280
+ end
281
+
282
+ write_attribute(:permalink, new_permalink)
283
+ end
284
+
285
+ # before_save
286
+ # '1' is for compatibility with form inputs
287
+ def clear_resource
288
+ if self.delete_resource && (self.delete_resource == '1' || self.delete_resource == true)
289
+ self.resource = nil
290
+ end
291
+ end
292
+ end