qcms 1.3.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|