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.
- data/.gitignore +14 -0
- data/README +70 -0
- data/Rakefile +39 -0
- data/VERSION +1 -0
- data/app/controllers/admin/admin_controller.rb +12 -0
- data/app/controllers/admin/documents_controller.rb +133 -0
- data/app/controllers/admin/meta_definitions_controller.rb +36 -0
- data/app/controllers/documents_controller.rb +187 -0
- data/app/controllers/sendmail_controller.rb +87 -0
- data/app/helpers/admin/documents_helper.rb +71 -0
- data/app/helpers/documents_helper.rb +30 -0
- data/app/helpers/sendmail_helper.rb +2 -0
- data/app/models/document.rb +292 -0
- data/app/models/document_mailer.rb +13 -0
- data/app/models/document_sweeper.rb +28 -0
- data/app/models/meta_definition.rb +57 -0
- data/app/models/sendmail.rb +18 -0
- data/app/views/admin/admin/system.html.erb +34 -0
- data/app/views/admin/documents/_form.html.erb +60 -0
- data/app/views/admin/documents/default.edit.html.erb +10 -0
- data/app/views/admin/documents/default.new.html.erb +10 -0
- data/app/views/admin/documents/default.show.html.erb +77 -0
- data/app/views/admin/documents/index.html.erb +32 -0
- data/app/views/admin/documents/shared/_resource_link.html.erb +6 -0
- data/app/views/document_mailer/new_document.erb +12 -0
- data/app/views/layouts/admin.html.erb +43 -0
- data/app/views/layouts/application.html.erb +44 -0
- data/app/views/pages/404.html.erb +28 -0
- data/app/views/pages/contact.html.erb +42 -0
- data/app/views/pages/default.html.erb +12 -0
- data/app/views/pages/feed.rss.builder +17 -0
- data/app/views/pages/home.html.erb +5 -0
- data/app/views/pages/search.html.erb +19 -0
- data/app/views/pages/shared/_archived_pages.erb +16 -0
- data/app/views/pages/shared/_related_pages.html.erb +13 -0
- data/app/views/pages/sitemap.html.erb +9 -0
- data/app/views/pages/template.erb +51 -0
- data/app/views/pages/thank_you.html.erb +1 -0
- data/app/views/sendmail/default.erb +12 -0
- data/config/sitemap.example.yml +39 -0
- data/db/migrate/20090824150210_create_documents.rb +37 -0
- data/db/migrate/20091208124512_create_meta_definition.rb +26 -0
- data/init.rb +1 -0
- data/install.rb +1 -0
- data/lib/qcms.rb +4 -0
- data/lib/tasks/cms.rake +237 -0
- data/qcms.gemspec +91 -0
- data/rails/init.rb +3 -0
- data/tasks/qcms_tasks.rake +223 -0
- data/test/qwerty_test.rb +8 -0
- data/test/test_helper.rb +3 -0
- data/uninstall.rb +1 -0
- 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,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
|