radiant-reader-extension 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/.gitignore +2 -0
  2. data/README.md +89 -0
  3. data/Rakefile +140 -0
  4. data/VERSION +1 -0
  5. data/app/controllers/admin/messages_controller.rb +20 -0
  6. data/app/controllers/admin/reader_settings_controller.rb +92 -0
  7. data/app/controllers/admin/readers_controller.rb +28 -0
  8. data/app/controllers/password_resets_controller.rb +64 -0
  9. data/app/controllers/reader_action_controller.rb +84 -0
  10. data/app/controllers/reader_activations_controller.rb +60 -0
  11. data/app/controllers/reader_sessions_controller.rb +56 -0
  12. data/app/controllers/readers_controller.rb +131 -0
  13. data/app/helpers/admin/reader_settings_helper.rb +36 -0
  14. data/app/models/message.rb +108 -0
  15. data/app/models/message_function.rb +37 -0
  16. data/app/models/message_reader.rb +13 -0
  17. data/app/models/reader.rb +146 -0
  18. data/app/models/reader_notifier.rb +34 -0
  19. data/app/models/reader_session.rb +3 -0
  20. data/app/views/admin/messages/_form.html.haml +29 -0
  21. data/app/views/admin/messages/_help.html.haml +41 -0
  22. data/app/views/admin/messages/_message_description.html.haml +3 -0
  23. data/app/views/admin/messages/edit.html.haml +16 -0
  24. data/app/views/admin/messages/new.html.haml +16 -0
  25. data/app/views/admin/reader_settings/_setting.html.haml +24 -0
  26. data/app/views/admin/reader_settings/edit.html.haml +10 -0
  27. data/app/views/admin/reader_settings/index.html.haml +35 -0
  28. data/app/views/admin/reader_settings/show.html.haml +1 -0
  29. data/app/views/admin/readers/_avatar.html.haml +3 -0
  30. data/app/views/admin/readers/_form.html.haml +50 -0
  31. data/app/views/admin/readers/_list_head.html.haml +9 -0
  32. data/app/views/admin/readers/_listed.html.haml +22 -0
  33. data/app/views/admin/readers/_password_fields.html.haml +18 -0
  34. data/app/views/admin/readers/edit.html.haml +8 -0
  35. data/app/views/admin/readers/index.html.haml +17 -0
  36. data/app/views/admin/readers/new.html.haml +7 -0
  37. data/app/views/admin/readers/remove.html.haml +18 -0
  38. data/app/views/admin/sites/_choose_reader_layout.html.haml +7 -0
  39. data/app/views/password_resets/create.html.haml +13 -0
  40. data/app/views/password_resets/edit.html.haml +71 -0
  41. data/app/views/password_resets/new.html.haml +31 -0
  42. data/app/views/reader_activations/_activation_required.html.haml +34 -0
  43. data/app/views/reader_activations/_on_activation.html.haml +4 -0
  44. data/app/views/reader_activations/show.html.haml +41 -0
  45. data/app/views/reader_notifier/message.html.haml +1 -0
  46. data/app/views/reader_sessions/_login_form.html.haml +59 -0
  47. data/app/views/reader_sessions/new.html.haml +38 -0
  48. data/app/views/readers/_contributions.html.haml +2 -0
  49. data/app/views/readers/_controls.html.haml +25 -0
  50. data/app/views/readers/_extra_controls.html.haml +0 -0
  51. data/app/views/readers/_flasher.html.haml +6 -0
  52. data/app/views/readers/_form.html.haml +73 -0
  53. data/app/views/readers/create.html.haml +28 -0
  54. data/app/views/readers/edit.html.haml +47 -0
  55. data/app/views/readers/index.html.haml +16 -0
  56. data/app/views/readers/login.html.haml +15 -0
  57. data/app/views/readers/new.html.haml +41 -0
  58. data/app/views/readers/permission_denied.html.haml +23 -0
  59. data/app/views/readers/show.html.haml +35 -0
  60. data/app/views/wrappers/_field_errors.html.haml +5 -0
  61. data/config/routes.rb +22 -0
  62. data/config/settings.rb +9 -0
  63. data/db/migrate/001_create_readers.rb +31 -0
  64. data/db/migrate/002_extend_sites.rb +17 -0
  65. data/db/migrate/003_reader_honorifics.rb +12 -0
  66. data/db/migrate/004_user_readers.rb +11 -0
  67. data/db/migrate/005_last_login.rb +15 -0
  68. data/db/migrate/007_adapt_for_authlogic.rb +27 -0
  69. data/db/migrate/20090921125653_reader_messages.rb +27 -0
  70. data/db/migrate/20090924164413_functional_messages.rb +9 -0
  71. data/db/migrate/20090925081225_standard_messages.rb +106 -0
  72. data/db/migrate/20091006102438_message_visibility.rb +9 -0
  73. data/db/migrate/20091010083503_registration_config.rb +10 -0
  74. data/db/migrate/20091019124021_message_functions.rb +9 -0
  75. data/db/migrate/20091020133533_forenames.rb +9 -0
  76. data/db/migrate/20091020135152_contacts.rb +23 -0
  77. data/db/migrate/20091111090819_ensure_functional_messages_visible.rb +9 -0
  78. data/db/migrate/20091119092936_messages_have_layout.rb +9 -0
  79. data/db/migrate/20100922152338_lock_versions.rb +9 -0
  80. data/db/migrate/20100927095703_default_settings.rb +14 -0
  81. data/db/migrate/20101004074945_unlock_version.rb +9 -0
  82. data/lib/config_extensions.rb +5 -0
  83. data/lib/controller_extensions.rb +77 -0
  84. data/lib/reader_admin_ui.rb +64 -0
  85. data/lib/reader_helper.rb +36 -0
  86. data/lib/reader_site.rb +10 -0
  87. data/lib/reader_tags.rb +297 -0
  88. data/lib/rfc822.rb +29 -0
  89. data/lib/tasks/reader_extension_tasks.rake +28 -0
  90. data/pkg/radiant-reader-extension-0.9.0.gem +0 -0
  91. data/public/images/admin/chk_off.png +0 -0
  92. data/public/images/admin/chk_on.png +0 -0
  93. data/public/images/admin/new-message.png +0 -0
  94. data/public/images/admin/new-reader.png +0 -0
  95. data/public/javascripts/admin/messages.js +13 -0
  96. data/public/stylesheets/sass/admin/reader.sass +95 -0
  97. data/radiant-reader-extension.gemspec +184 -0
  98. data/reader_extension.rb +55 -0
  99. data/spec/controllers/admin/messages_controller_spec.rb +38 -0
  100. data/spec/controllers/admin/readers_controller_spec.rb +14 -0
  101. data/spec/controllers/password_resets_controller_spec.rb +140 -0
  102. data/spec/controllers/reader_activations_controller_spec.rb +45 -0
  103. data/spec/controllers/readers_controller_spec.rb +193 -0
  104. data/spec/datasets/messages_dataset.rb +49 -0
  105. data/spec/datasets/reader_layouts_dataset.rb +26 -0
  106. data/spec/datasets/reader_sites_dataset.rb +10 -0
  107. data/spec/datasets/readers_dataset.rb +51 -0
  108. data/spec/lib/reader_admin_ui_spec.rb +35 -0
  109. data/spec/lib/reader_site_spec.rb +18 -0
  110. data/spec/matchers/reader_login_system_matcher.rb +35 -0
  111. data/spec/models/message_spec.rb +109 -0
  112. data/spec/models/reader_notifier_spec.rb +34 -0
  113. data/spec/models/reader_spec.rb +155 -0
  114. data/spec/spec.opts +5 -0
  115. data/spec/spec_helper.rb +48 -0
  116. metadata +267 -0
@@ -0,0 +1,84 @@
1
+ class ReaderActionController < ApplicationController
2
+ helper_method :current_site, :current_site=, :logged_in?, :logged_in_user?, :logged_in_admin?
3
+
4
+ no_login_required
5
+ before_filter :set_site_title
6
+
7
+ # reader session is normally required for modifying actions
8
+ before_filter :require_reader, :except => [:index, :show]
9
+
10
+ radiant_layout { |controller| Radiant::Config['reader.layout'] }
11
+
12
+ # authorisation helpers
13
+
14
+ def logged_in?
15
+ true if current_reader
16
+ end
17
+
18
+ def logged_in_user?
19
+ true if logged_in? && current_reader.is_user?
20
+ end
21
+
22
+ def logged_in_admin?
23
+ true if logged_in_user? && current_reader.admin?
24
+ end
25
+
26
+ def permission_denied
27
+ session[:return_to] ||= request.referer
28
+ @title = flash[:error] || "Sorry: permission denied"
29
+ render
30
+ end
31
+
32
+ protected
33
+
34
+ # context-setters
35
+
36
+ def set_site_title
37
+ if defined? Site && current_site
38
+ @site_title = current_site.name
39
+ @short_site_title = current_site.abbreviation || @site_title
40
+ @site_url = current_site.base_domain
41
+ else
42
+ @site_title = Radiant::Config['site.title']
43
+ @short_site_title = Radiant::Config['site.abbreviation'] || @site_title
44
+ @site_url = request.host
45
+ end
46
+ end
47
+
48
+ def require_reader
49
+ unless set_reader # set_reader is in ControllerExtension and sets Reader.current while checking for current_reader
50
+ store_location
51
+ respond_to do |format|
52
+ format.html { redirect_to reader_login_url }
53
+ format.js {
54
+ @inline = true
55
+ render :partial => 'reader_sessions/login_form'
56
+ }
57
+ end
58
+ false
59
+ end
60
+ end
61
+
62
+ def require_activated_reader
63
+ unless current_reader && current_reader.activated?
64
+ respond_to do |format|
65
+ format.html { redirect_to reader_activation_url }
66
+ format.js {
67
+ @inline = true
68
+ render :partial => 'reader_activations/activation_required'
69
+ }
70
+ end
71
+ false
72
+ end
73
+ end
74
+
75
+ def require_no_reader
76
+ if set_reader
77
+ store_location
78
+ flash[:notice] = "Please log out first"
79
+ redirect_back_or_to url_for(current_reader)
80
+ return false
81
+ end
82
+ end
83
+
84
+ end
@@ -0,0 +1,60 @@
1
+ class ReaderActivationsController < ReaderActionController
2
+
3
+ no_login_required
4
+ skip_before_filter :require_reader
5
+ before_filter :authenticate_reader, :only => [:update]
6
+ before_filter :check_reader_inactive
7
+
8
+ radiant_layout { |controller| Radiant::Config['reader.layout'] }
9
+
10
+ # this is just fake REST: we're actually working on the reader, not an activation object, but in a usefully restricted way:
11
+ # .show sends out an activation message if we can identify the current reader
12
+ # .update activates the reader, if the token is correct
13
+
14
+ def show
15
+ render
16
+ end
17
+
18
+ def new
19
+ if current_reader
20
+ @reader = current_reader
21
+ @reader.send_activation_message
22
+ flash[:notice] = "Account activation instructions have been emailed to you."
23
+ end
24
+ render :action => 'show'
25
+ end
26
+
27
+ def update
28
+ if @reader
29
+ @reader.activate!
30
+ self.current_reader = @reader
31
+ flash[:notice] = "Thank you! Your account has been activated."
32
+ redirect_back_or_to default_activated_url
33
+
34
+ else
35
+ @error = "Sorry: something was wrong in that link. Please check your email message."
36
+ flash[:error] = "Activation failed."
37
+ render :action => 'show'
38
+ end
39
+ end
40
+
41
+ protected
42
+
43
+ def authenticate_reader
44
+ # not using authlogic's find_using_perishable_token because I don't want the token to expire
45
+ @reader = Reader.find_by_id_and_perishable_token(params[:id], params[:activation_code])
46
+ end
47
+
48
+ def check_reader_inactive
49
+ if @reader && @reader.activated?
50
+ flash[:notice] = "Hello #{@reader.name}! Your account is already active."
51
+ redirect_back_or_to default_activated_url
52
+ false
53
+ end
54
+ end
55
+
56
+ def default_activated_url
57
+ reader_url(@reader)
58
+ end
59
+
60
+ end
@@ -0,0 +1,56 @@
1
+ class ReaderSessionsController < ReaderActionController
2
+
3
+ before_filter :require_reader, :only => :destroy
4
+ radiant_layout { |controller| Radiant::Config['reader.layout'] }
5
+
6
+ def new
7
+ @reader_session = ReaderSession.new
8
+ end
9
+
10
+ def create
11
+ @reader_session = ReaderSession.new(params[:reader_session])
12
+ if @reader_session.save
13
+ if @reader_session.reader.activated? && @reader_session.reader.clear_password
14
+ @reader_session.reader.clear_password = "" # we forget the cleartext version on the first successful login after activation
15
+ @reader_session.reader.save(false)
16
+ end
17
+ respond_to do |format|
18
+ format.html {
19
+ flash[:notice] = "Hello #{@reader_session.reader.name}. Welcome back."
20
+ redirect_back_or_to default_loggedin_url
21
+ }
22
+ format.js {
23
+ redirect_back_with_format(:js)
24
+ }
25
+ end
26
+
27
+ else
28
+ respond_to do |format|
29
+ format.html {
30
+ flash[:error] = "Sorry: that combination of login and password is not known here."
31
+ render :action => :new
32
+ }
33
+ format.js { render :action => :new, :layout => false }
34
+ end
35
+ end
36
+ end
37
+
38
+ def destroy
39
+ current_reader_session.destroy
40
+ if current_user
41
+ cookies[:session_token] = { :expires => 1.day.ago }
42
+ current_user.forget_me
43
+ session['user_id'] = nil
44
+ current_user = nil
45
+ end
46
+ flash[:notice] = "You are logged out. Bye!"
47
+ redirect_back_or_to reader_login_url
48
+ end
49
+
50
+ protected
51
+
52
+ def default_loggedin_url
53
+ reader_url(@reader_session.reader)
54
+ end
55
+
56
+ end
@@ -0,0 +1,131 @@
1
+ class ReadersController < ReaderActionController
2
+ @@extended_form_partials = []
3
+ cattr_accessor :extended_form_partials
4
+
5
+ before_filter :check_registration_allowed, :only => [:new, :create]
6
+ before_filter :initialize_form_partials, :only => [:new, :edit, :update, :create]
7
+ before_filter :i_am_me, :only => [:show]
8
+ before_filter :require_reader, :except => [:index, :new, :create, :activate]
9
+ before_filter :restrict_to_self, :only => [:edit, :update, :resend_activation]
10
+ before_filter :no_removing, :only => [:remove, :destroy]
11
+ before_filter :require_password, :only => [:update]
12
+
13
+ def index
14
+ @readers = Reader.paginate(:page => params[:page], :order => 'readers.created_at desc')
15
+ end
16
+
17
+ def show
18
+ @reader = Reader.find(params[:id])
19
+ respond_to do |format|
20
+ format.html {
21
+ if @reader.inactive? && @reader == current_reader
22
+ redirect_to reader_activation_url
23
+ else
24
+ render
25
+ end
26
+ }
27
+ format.js {
28
+ @inline = true
29
+ render :partial => 'readers/controls'
30
+ }
31
+ end
32
+
33
+ end
34
+
35
+ def new
36
+ if current_reader
37
+ flash[:error] = "You're already logged in!"
38
+ redirect_to url_for(current_reader) and return
39
+ end
40
+ @reader = Reader.new
41
+ session[:return_to] = request.referer
42
+ session[:email_field] = @email_field = @reader.generate_email_field_name
43
+ end
44
+
45
+ def edit
46
+ end
47
+
48
+ def create
49
+ @reader = Reader.new(params[:reader])
50
+ @reader.clear_password = params[:reader][:password]
51
+
52
+ unless @reader.email.blank?
53
+ flash[:error] = "Please don't fill in the spam trap field."
54
+ @reader.email = ''
55
+ @reader.errors.add(:trap, "must be empty")
56
+ render :action => 'new' and return
57
+ end
58
+
59
+ unless @email_field = session[:email_field]
60
+ flash[:error] = "Please use the registration form."
61
+ redirect_to new_reader_url and return
62
+ end
63
+
64
+ @reader.email = params[@email_field.intern]
65
+ if (@reader.valid?)
66
+ @reader.save!
67
+ @reader.send_activation_message
68
+ self.current_reader = @reader
69
+ redirect_to reader_activation_url
70
+ else
71
+ render :action => 'new'
72
+ end
73
+ end
74
+
75
+ def update
76
+ @reader.attributes = params[:reader]
77
+ @reader.clear_password = params[:reader][:password] if params[:reader][:password]
78
+ if @reader.save
79
+ flash[:notice] = "Your account has been updated"
80
+ redirect_to url_for(@reader)
81
+ else
82
+ render :action => 'edit'
83
+ end
84
+ end
85
+
86
+ protected
87
+
88
+ def i_am_me
89
+ params[:id] = current_reader.id if params[:id] == 'me' && current_reader
90
+ end
91
+
92
+ def restrict_to_self
93
+ flash[:error] = "Sorry. You are not allowed to edit other people's accounts." if params[:id] && params[:id] != current_reader.id
94
+ @reader = current_reader
95
+ end
96
+
97
+ def require_password
98
+ return true if @reader.valid_password?(params[:reader][:current_password])
99
+
100
+ # might as well get any other validation messages while we're at it
101
+ @reader.attributes = params[:reader]
102
+ @reader.valid?
103
+
104
+ flash[:error] = 'Sorry. Wrong password.'
105
+ @reader.errors.add(:current_password, "was not correct")
106
+ render :action => 'edit' and return false
107
+ end
108
+
109
+ def no_removing
110
+ flash[:error] = "You can't delete readers here. Please log in to the admin interface."
111
+ redirect_to admin_readers_url
112
+ end
113
+
114
+ def check_registration_allowed
115
+ unless Radiant::Config['reader.allow_registration?']
116
+ flash[:error] = "Sorry. This site does not allow public registration."
117
+ redirect_to reader_login_url
118
+ false
119
+ end
120
+ end
121
+
122
+ def self.add_form_partial(path)
123
+ extended_form_partials.push(path)
124
+ end
125
+
126
+ private
127
+ def initialize_form_partials
128
+ @form_partials = extended_form_partials
129
+ end
130
+
131
+ end
@@ -0,0 +1,36 @@
1
+ module Admin::ReaderSettingsHelper
2
+
3
+ def editable_setting(setting)
4
+ setting = Radiant::Config.find_by_key(setting) unless setting.is_a? Radiant::Config
5
+ domkey = setting.key.gsub('?', '_')
6
+ containerid = "set_#{domkey}"
7
+ link = link_to_remote setting.value,
8
+ :url => edit_admin_reader_setting_url(setting.id),
9
+ :method => 'get',
10
+ :update => containerid,
11
+ :loading => "$('#{containerid}').addClassName('waiting');",
12
+ :loaded => "$('#{containerid}').removeClassName('waiting');"
13
+ %{
14
+ #{link}
15
+ }
16
+ end
17
+
18
+ def checkbox_for_setting(setting, label)
19
+ setting = Radiant::Config.find_by_key(setting) unless setting.is_a? Radiant::Config
20
+ domkey = setting.key.gsub('?', '_')
21
+ containerid = "set_#{domkey}"
22
+ checkbox = check_box_tag setting.key.to_sym, 1, setting.value, :class => 'fancy', :id => domkey, :onchange => remote_function(
23
+ :url => admin_reader_setting_path(setting.id),
24
+ :with => %{'value=' + (this.checked ? 'true' : 'false')},
25
+ :method => 'put',
26
+ :loading => "$('#{containerid}').addClassName('waiting');",
27
+ :success => "$('#{containerid}').removeClassName('waiting').toggleClassName('true').toggleClassName('false');"
28
+ )
29
+ %{
30
+ #{checkbox}
31
+ <label for="#{domkey}">#{label}</label>
32
+ }
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,108 @@
1
+ class Message < ActiveRecord::Base
2
+
3
+ is_site_scoped if defined? ActiveRecord::SiteNotFound
4
+
5
+ belongs_to :layout
6
+ belongs_to :created_by, :class_name => 'User'
7
+ belongs_to :updated_by, :class_name => 'User'
8
+
9
+ has_many :message_readers
10
+ has_many :readers, :through => :message_readers
11
+
12
+ has_many :deliveries, :class_name => 'MessageReader', :conditions => ["message_readers.sent_at IS NOT NULL and message_readers.sent_at <= ?", Time.now.to_s(:db)]
13
+ has_many :recipients, :through => :deliveries, :source => :reader
14
+
15
+ validates_presence_of :subject
16
+ validates_presence_of :body
17
+
18
+ object_id_attr :filter, TextFilter
19
+
20
+ default_scope :order => 'updated_at DESC, created_at DESC'
21
+ named_scope :for_function, lambda { |f| {:conditions => ["function_id = ?", f.to_s]} }
22
+ named_scope :administrative, { :conditions => "function_id IS NOT NULL" }
23
+ named_scope :ordinary, { :conditions => "function_id IS NULL" }
24
+ named_scope :published, { :conditions => "status_id >= 100" }
25
+
26
+ def filtered_body
27
+ filter.filter(body)
28
+ end
29
+
30
+ # has to return a named_scope for chainability
31
+ def possible_readers
32
+ Reader.active
33
+ end
34
+
35
+ def undelivered_readers
36
+ possible_readers - recipients
37
+ end
38
+
39
+ def inactive_readers
40
+ possible_readers.inactive
41
+ end
42
+
43
+ def active_readers
44
+ possible_readers.active
45
+ end
46
+
47
+ def delivered?
48
+ deliveries.any?
49
+ end
50
+
51
+ def delivered_to?(reader)
52
+ recipients.include?(reader)
53
+ end
54
+
55
+ def preview(reader=nil)
56
+ reader ||= possible_readers.first || Reader.find_or_create_for_user(created_by)
57
+ ReaderNotifier.create_message(reader, self)
58
+ end
59
+
60
+ def function
61
+ MessageFunction[self.function_id]
62
+ end
63
+ def self.functional(function)
64
+ for_function(MessageFunction[function]).first
65
+ end
66
+ def has_function?
67
+ !function.nil?
68
+ end
69
+ def administrative?
70
+ has_function?
71
+ end
72
+
73
+ def status
74
+ Status.find(self.status_id)
75
+ end
76
+ def status=(value)
77
+ self.status_id = value.id
78
+ end
79
+ def published?
80
+ status == Status[:published]
81
+ end
82
+ def published!
83
+ status = Status[:published]
84
+ end
85
+
86
+ def deliver(readers)
87
+ failures = []
88
+ readers.each do |reader|
89
+ failures.push(reader) unless deliver_to(reader)
90
+ end
91
+ self.published!
92
+ failures
93
+ end
94
+
95
+ def deliver_to(reader, sender=nil)
96
+ ReaderNotifier.deliver_message(reader, self, sender)
97
+ record_delivery(reader)
98
+ true
99
+ rescue => e
100
+ logger.warn "@@ delivery failed: #{e.inspect}"
101
+ raise
102
+ end
103
+
104
+ def record_delivery(reader)
105
+ MessageReader.find_or_create_by_message_id_and_reader_id(self.id, reader.id).update_attribute(:sent_at, Time.now)
106
+ end
107
+
108
+ end