qcms 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
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,14 @@
1
+ *.log
2
+ db/schema.rb
3
+ db/schema.sql
4
+ .DS_Store
5
+ doc/api
6
+ doc/app
7
+ config/database.yml
8
+ config/settings.yml
9
+ config/authorisation.yml
10
+ nbproject
11
+ pkg/**
12
+ .yardoc/*
13
+ doc/*
14
+ rdoc/*
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
@@ -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,12 @@
1
+ class Admin::AdminController < ApplicationController
2
+ before_filter :require_user
3
+
4
+ layout 'admin'
5
+
6
+ def system
7
+ end
8
+
9
+ def dashboard
10
+ redirect_to admin_documents_path
11
+ end
12
+ end
@@ -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