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
data/.gitignore
ADDED
data/README
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
Qwerty CMS
|
2
|
+
==========
|
3
|
+
|
4
|
+
A unique CMS provided as a Rails 2 engine
|
5
|
+
|
6
|
+
Key Features
|
7
|
+
|
8
|
+
* Extended template pathing
|
9
|
+
** blog.post.erb.html
|
10
|
+
** blog.post.comment.erb.html
|
11
|
+
** homepage.erb.html
|
12
|
+
** gallery.picture.erb.html
|
13
|
+
|
14
|
+
* sitemap.yml
|
15
|
+
** A nice way to represent a sites nested structure and defaults for each level such as pagination and ordering
|
16
|
+
|
17
|
+
* Not bound to a autherisation or authentication system
|
18
|
+
|
19
|
+
== Install
|
20
|
+
|
21
|
+
sudo gem install qcms
|
22
|
+
|
23
|
+
== Get going
|
24
|
+
|
25
|
+
Firstly, it is easiest to start using Skeletor app which provides a bare bones application with examples configuration files, and Authlogic integration.
|
26
|
+
|
27
|
+
Otherwise:
|
28
|
+
|
29
|
+
Copy the migrations and rake tasks to your Rails app.
|
30
|
+
|
31
|
+
config.gem "qcms", :version => '~>1.0'
|
32
|
+
|
33
|
+
Create sitemap.yml (see skeletor for examples)
|
34
|
+
|
35
|
+
rake qwerty:bootstrap
|
36
|
+
|
37
|
+
== TODO
|
38
|
+
|
39
|
+
* migrations to work from inside a gem
|
40
|
+
* rake tasks to work from inside a gem.
|
41
|
+
* Specify gem dependancies
|
42
|
+
|
43
|
+
== Release Gem with Jeweler and Git to RubyGems
|
44
|
+
|
45
|
+
(next) git co -b release_1.2.1
|
46
|
+
(release_1.2.1) rake version:bump:patch
|
47
|
+
(release_1.2.1) git co master
|
48
|
+
(master) git merge --no-ff release_1.2.1
|
49
|
+
(master) rake release
|
50
|
+
(master) git co next
|
51
|
+
(next) git merge --no-ff release_1.2.1
|
52
|
+
|
53
|
+
== Versioning
|
54
|
+
|
55
|
+
Versioning schema: http://semver.org/
|
56
|
+
|
57
|
+
== Upgrade Notes
|
58
|
+
|
59
|
+
= 1.3
|
60
|
+
|
61
|
+
settings.yml
|
62
|
+
|
63
|
+
documents
|
64
|
+
cache: true
|
65
|
+
|
66
|
+
== Authors
|
67
|
+
|
68
|
+
Kris Leech @ Interkonect Limited
|
69
|
+
|
70
|
+
http://interkonect.com
|
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the qwerty plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Generate documentation for the qwerty plugin.'
|
17
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
18
|
+
rdoc.rdoc_dir = 'rdoc'
|
19
|
+
rdoc.title = 'Qwerty'
|
20
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
21
|
+
rdoc.rdoc_files.include('README')
|
22
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
|
+
rdoc.rdoc_files.include('app/**/*.rb')
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
require 'jeweler'
|
28
|
+
Jeweler::Tasks.new do |gemspec|
|
29
|
+
gemspec.name = "qcms"
|
30
|
+
gemspec.summary = "A CMS built in collaberation with designers"
|
31
|
+
gemspec.description = "Key CMS features: extended template pathing, sitemap.yml, simple configurable, deeply nestable content"
|
32
|
+
gemspec.email = "kris.leech@interkonect.com"
|
33
|
+
gemspec.homepage = "http://github.com/krisleech/qcms"
|
34
|
+
gemspec.authors = ["Kris Leech"]
|
35
|
+
end
|
36
|
+
Jeweler::GemcutterTasks.new
|
37
|
+
rescue LoadError
|
38
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
39
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.3.3
|
@@ -0,0 +1,133 @@
|
|
1
|
+
class Admin::DocumentsController < Admin::AdminController
|
2
|
+
before_filter :find_document, :except => [:index]
|
3
|
+
before_filter :catch_sti_problem, :only => [:update]
|
4
|
+
|
5
|
+
cache_sweeper :document_sweeper, :only => [:create, :update, :destroy]
|
6
|
+
|
7
|
+
def index
|
8
|
+
@documents = Document.roots.sort_by(&:position)
|
9
|
+
end
|
10
|
+
|
11
|
+
def show
|
12
|
+
render :text => "Document Not Found [#{params[:id]}]" and return unless @document
|
13
|
+
render :template => admin_view_for('show')
|
14
|
+
end
|
15
|
+
|
16
|
+
def new
|
17
|
+
@document.type = params[:type] || 'Document'
|
18
|
+
if params[:parent]
|
19
|
+
@document.parent = Document.find(params[:parent])
|
20
|
+
@document.meta_definition = @document.parent.meta_definition.children.by_label(params[:label]).first
|
21
|
+
else
|
22
|
+
@document.meta_definition = MetaDefinition.find_by_label_path(params[:label_path])
|
23
|
+
end
|
24
|
+
@document.label = params[:label] || @document.meta_definition.label
|
25
|
+
render :template => admin_view_for('new')
|
26
|
+
end
|
27
|
+
|
28
|
+
def create
|
29
|
+
@document = Document.create(params[:document])
|
30
|
+
@document.type = params[:document][:type] || 'Document'
|
31
|
+
@document.author = current_user
|
32
|
+
if @document.save
|
33
|
+
flash[:notice] = "Succesfully Saved"
|
34
|
+
redirect_to parent_path
|
35
|
+
else
|
36
|
+
render :template => admin_view_for('new')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def edit
|
41
|
+
render :template => admin_view_for('edit')
|
42
|
+
end
|
43
|
+
|
44
|
+
def update
|
45
|
+
if @document.update_attributes(params[:document])
|
46
|
+
flash[:notice] = "Succesfully Saved"
|
47
|
+
redirect_to parent_path
|
48
|
+
else
|
49
|
+
render :template => admin_view_for('edit')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def destroy
|
54
|
+
@document.destroy
|
55
|
+
redirect_to parent_path
|
56
|
+
end
|
57
|
+
|
58
|
+
def up
|
59
|
+
@document.move_higher
|
60
|
+
redirect_to parent_path
|
61
|
+
end
|
62
|
+
|
63
|
+
def down
|
64
|
+
@document.move_lower
|
65
|
+
redirect_to parent_path
|
66
|
+
end
|
67
|
+
|
68
|
+
# Generate a view from template.erb
|
69
|
+
def generate_template
|
70
|
+
if request.post?
|
71
|
+
template = File.new("#{RAILS_ROOT}/app/views/pages/#{@document.meta_definition.label_path.gsub('/', '.')}.html.erb", 'w')
|
72
|
+
template.write(ERB.new(IO.read("#{RAILS_ROOT}/app/views/pages/template.erb")).result(binding))
|
73
|
+
template.close
|
74
|
+
end
|
75
|
+
@document.touch # delete cache (FIXME: Not ideal)
|
76
|
+
redirect_to document_path(@document)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def find_document
|
82
|
+
if params[:id]
|
83
|
+
@document = Document.find(params[:id])
|
84
|
+
else
|
85
|
+
@document = Document.new
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def parent_path
|
90
|
+
if @document.parent
|
91
|
+
admin_document_path(@document.parent.id)
|
92
|
+
else
|
93
|
+
admin_dashboard_path
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
# eg. my_first_post.html.erb, blog.post.comment.html.erb, default.html.erb
|
99
|
+
def admin_view_for(action, options = {})
|
100
|
+
options[:extension] = '.html.erb'
|
101
|
+
|
102
|
+
filenames = [@document.permalink, @document.meta_definition.label_path.gsub('/', '.'), 'default']
|
103
|
+
|
104
|
+
view_paths.each do | view_path |
|
105
|
+
filenames.each do | filename |
|
106
|
+
next if filename.nil?
|
107
|
+
target = File.join('admin', 'documents', filename + '.' + action) + options[:extension]
|
108
|
+
target_with_view_path = File.join(view_path, target)
|
109
|
+
if File.exists? target_with_view_path
|
110
|
+
headers['content-type'] = 'text/html' # Otherwise wrong content-type is set due to multiple file extensions
|
111
|
+
logger.debug 'TEMPLATE FOUND: ' + target_with_view_path
|
112
|
+
return target
|
113
|
+
else
|
114
|
+
logger.debug 'TEMPLATE NOT FOR: ' + target_with_view_path
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
raise 'No Template found'
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
# No idea why this happens but if the inputs are (incorrectly) named page[field] instead of document[field] you get
|
123
|
+
# params[:document] from now where. This would be okay but sometimes fields appear to be missing when using fckeditor(!?!)
|
124
|
+
# For the new form @document is_a? Document (not yet 'typed' via STI), but when editing it is_a? Page or Folder...
|
125
|
+
# To get the correct input names @document needs to be type cast to Document class
|
126
|
+
# Example: form_for @document.becomes(Document)
|
127
|
+
def catch_sti_problem
|
128
|
+
return unless RAILS_ENV == 'development'
|
129
|
+
if params[:document] && params[:page]
|
130
|
+
raise 'Both params[:document] and params[:page] found, you might need to add @document.becomes(Document) to get the correct input names'
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Admin::MetaDefinitionsController < Admin::AdminController
|
2
|
+
|
3
|
+
def index
|
4
|
+
end
|
5
|
+
|
6
|
+
def show
|
7
|
+
end
|
8
|
+
|
9
|
+
def new
|
10
|
+
end
|
11
|
+
|
12
|
+
def create
|
13
|
+
end
|
14
|
+
|
15
|
+
def edit
|
16
|
+
end
|
17
|
+
|
18
|
+
def update
|
19
|
+
@meta_definition = MetaDefinition.find(params[:id])
|
20
|
+
if @meta_definition.update_attributes(params[:meta_definition])
|
21
|
+
flash[:notice] = "Succesfully Saved"
|
22
|
+
else
|
23
|
+
flash[:notice] = 'Not saved'
|
24
|
+
end
|
25
|
+
redirect_to admin_document_path(params[:document_id])
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
class DocumentsController < ApplicationController
|
2
|
+
unloadable
|
3
|
+
skip_before_filter :require_user
|
4
|
+
|
5
|
+
# Show a document:
|
6
|
+
# Route using catch-all, /*path, or, pass specific 'id', :path => 'gallery'
|
7
|
+
def show
|
8
|
+
render :file => cache_file and return if cached_file?
|
9
|
+
|
10
|
+
@document = Document.find_by_path(request_path)
|
11
|
+
|
12
|
+
raise ActiveRecord::RecordNotFound unless @document and (@document.published? or @document.allowed? current_user, 'read')
|
13
|
+
|
14
|
+
respond_to do |format|
|
15
|
+
format.html do
|
16
|
+
setup_view_environment
|
17
|
+
render :template => view_for
|
18
|
+
end
|
19
|
+
format.xml { render :xml => @document.to_xml(:only => [:title, :summary, :body, :published_at, :permalink]) }
|
20
|
+
format.rss { render :template => 'pages/feed.rss.builder' }
|
21
|
+
end
|
22
|
+
|
23
|
+
cache_this_page!
|
24
|
+
end
|
25
|
+
|
26
|
+
# Show all pages in any Document for a particular month/year
|
27
|
+
# /*path/archive/:month/:year
|
28
|
+
def archive
|
29
|
+
render :file => cache_file and return if cached_file?
|
30
|
+
@document = Document.public.find_by_path(params[:path].join('/'))
|
31
|
+
raise ActiveRecord::RecordNotFound if @document.nil?
|
32
|
+
|
33
|
+
|
34
|
+
@documents = @document.archive_for(params[:month], params[:year]).paginate :page => params[:page], :per_page => Settings.documents.per_page
|
35
|
+
render :template => view_for(:suffix => '_archive')
|
36
|
+
cache_this_page!
|
37
|
+
end
|
38
|
+
|
39
|
+
# Allows searching of pages in a folder
|
40
|
+
# /:permalink/search?search[title_like]=hello
|
41
|
+
def search
|
42
|
+
params[:search] ||= {}
|
43
|
+
@document = Document.public.find_by_path(params[:path].join('/'))
|
44
|
+
params[:search].merge!(:parent_id => @document.id)
|
45
|
+
params[:search][:state_eq] = 'published'
|
46
|
+
@documents = Document.search(params[:search]).paginate :page => params[:page], :per_page => Settings.documents.per_page
|
47
|
+
setup_view_environment
|
48
|
+
render :template => view_for(:suffix => '_search')
|
49
|
+
end
|
50
|
+
|
51
|
+
# Allow Document wide search
|
52
|
+
# /search?terms[title_like]=hello
|
53
|
+
def search_all
|
54
|
+
params[:search] ||= {}
|
55
|
+
params[:search][:state_eq] = 'published'
|
56
|
+
@documents = Document.search(params[:search]).paginate :page => params[:page], :per_page => Settings.documents.per_page
|
57
|
+
render :template => '/pages/search'
|
58
|
+
end
|
59
|
+
|
60
|
+
# public facing creation of documents
|
61
|
+
def create
|
62
|
+
@document = Document.public.find(params[:id])
|
63
|
+
|
64
|
+
begin
|
65
|
+
do_human_test
|
66
|
+
rescue
|
67
|
+
flash.now[:notice] = 'You did not add up the numbers correctly, please try again.'
|
68
|
+
|
69
|
+
new_document = Document.new
|
70
|
+
new_document.body = params[:document][:body]
|
71
|
+
|
72
|
+
eval("@new_#{params[:label]} = new_document")
|
73
|
+
|
74
|
+
setup_view_environment
|
75
|
+
render :template => view_for
|
76
|
+
return
|
77
|
+
end
|
78
|
+
|
79
|
+
params[:document][:state] = nil # prevent auto approval hack (FIXME: use attr_protected)
|
80
|
+
|
81
|
+
|
82
|
+
if @document.meta_definition_for(params[:label]).allowed? current_user, 'create'
|
83
|
+
|
84
|
+
new_document = Document.new(params[:document])
|
85
|
+
new_document.parent = @document
|
86
|
+
new_document.label = params[:label]
|
87
|
+
new_document.author = current_user || User.anonymous
|
88
|
+
new_document.published_at = Time.now
|
89
|
+
|
90
|
+
if new_document.save
|
91
|
+
flash[:notice] = new_document.meta_definition.flash_messages['create'] || "Your #{params[:label]} has been saved"
|
92
|
+
if new_document.meta_definition.notify_admins
|
93
|
+
DocumentMailer.deliver_new_document(new_document)
|
94
|
+
end
|
95
|
+
redirect_to document_path(@document)
|
96
|
+
else
|
97
|
+
flash.now[:notice] = 'Could not be saved'
|
98
|
+
eval("@new_#{params[:label]} = new_document")
|
99
|
+
setup_view_environment
|
100
|
+
render :template => view_for
|
101
|
+
end
|
102
|
+
else
|
103
|
+
render :text => 'Not Allowed' and return
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def request_path
|
110
|
+
@path ||= CGI::unescape(request.path[1..-1])
|
111
|
+
@path.blank? ? 'home' : @path
|
112
|
+
end
|
113
|
+
|
114
|
+
# Does a cache exist for this URL
|
115
|
+
def cached_file?
|
116
|
+
Settings.documents.page_cache && File.exists?(cache_file)
|
117
|
+
end
|
118
|
+
|
119
|
+
def cache_file
|
120
|
+
@url_id ||= request_path.split('/').last + (request.query_string.blank? ? '' : '_' + Digest::MD5.hexdigest(request.query_string))
|
121
|
+
@cache_file ||= File.join(RAILS_ROOT, 'public', 'cache', request_path, self.action_name, @url_id) + '.html'
|
122
|
+
end
|
123
|
+
|
124
|
+
def cache_this_page!
|
125
|
+
if Settings.documents.page_cache && !File.exists?(cache_file)
|
126
|
+
logger.debug 'Caching Page: ' + cache_file
|
127
|
+
FileUtils.mkdir_p File.dirname(cache_file)
|
128
|
+
cache = File.open(cache_file, 'w')
|
129
|
+
cache.write(response.body)
|
130
|
+
cache.close
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def do_human_test
|
135
|
+
raise 'Human Test Failed' unless params[:human_test][:answer].crypt('humAn5') == params[:human_test][:crypted_answer]
|
136
|
+
end
|
137
|
+
|
138
|
+
def setup_view_environment
|
139
|
+
# create children vars such as @comments
|
140
|
+
if @document.can_have_children?
|
141
|
+
|
142
|
+
@children = []
|
143
|
+
|
144
|
+
# make an instance variable for each child eg. @posts @comments etc.
|
145
|
+
@document.child_labels.each do | label|
|
146
|
+
per_page = @document.meta_definition.children.by_label(label).first.per_page || Settings.documents.per_page
|
147
|
+
order = @document.meta_definition.children.by_label(label).first.sort_by || Settings.documents._sort_by
|
148
|
+
instance_variable_set('@' + label.pluralize, @document.children.by_label(label).with_states(:published).paginate(:order => order, :page => params[:page], :per_page => per_page))
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
# make instance variable for the parent eg. @story
|
154
|
+
instance_variable_set("@#{@document.label.gsub(' ', '_')}", @document)
|
155
|
+
|
156
|
+
# HTML meta tags
|
157
|
+
@page_title = @document.meta_title
|
158
|
+
@meta_description = @document.meta_description
|
159
|
+
@meta_keywords = @document.meta_keywords
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
# eg. blog.post.comment.html.erb
|
164
|
+
def view_for(options = {})
|
165
|
+
create_missing_template if !params[:create_template].nil? && RAILS_ENV == 'development'
|
166
|
+
|
167
|
+
options[:extension] = '.html.erb'
|
168
|
+
options[:prefix] ||= ''
|
169
|
+
options[:suffix] ||= ''
|
170
|
+
|
171
|
+
filenames = [@document.permalink, @document.meta_definition.template_filename(false), 'default']
|
172
|
+
|
173
|
+
view_paths.each do | view_path |
|
174
|
+
filenames.each do | filename |
|
175
|
+
target = File.join('pages', filename) + options[:suffix] + options[:extension]
|
176
|
+
target_with_view_path = File.join(view_path, target)
|
177
|
+
if File.exists? target_with_view_path
|
178
|
+
logger.debug 'FOUND: ' + target_with_view_path
|
179
|
+
return target
|
180
|
+
else
|
181
|
+
logger.debug 'NOT FOUND: ' + target_with_view_path
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
raise 'No Template found'
|
186
|
+
end
|
187
|
+
end
|