radiant-reader-extension 2.0.0.rc4 → 3.0.0.rc3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +9 -5
- data/app/controllers/{readers_controller.rb → accounts_controller.rb} +31 -30
- data/app/controllers/groups_controller.rb +35 -0
- data/app/controllers/messages_controller.rb +1 -0
- data/app/controllers/password_resets_controller.rb +6 -6
- data/app/controllers/reader_action_controller.rb +8 -0
- data/app/controllers/reader_activations_controller.rb +3 -2
- data/app/controllers/reader_sessions_controller.rb +6 -2
- data/app/helpers/reader_helper.rb +61 -17
- data/app/models/group.rb +33 -5
- data/app/models/message.rb +7 -4
- data/app/models/message_reader.rb +4 -0
- data/app/models/reader.rb +71 -3
- data/app/models/reader_page.rb +56 -0
- data/app/views/{readers → accounts}/_contributions.html.haml +0 -0
- data/app/views/{readers → accounts}/_controls.html.haml +1 -2
- data/app/views/accounts/_description.html.haml +2 -0
- data/app/views/{readers → accounts}/_extra_controls.html.haml +0 -0
- data/app/views/{readers → accounts}/_flasher.html.haml +0 -0
- data/app/views/{readers → accounts}/_form.html.haml +10 -15
- data/app/views/accounts/_gravatar.html.haml +3 -0
- data/app/views/accounts/_groups.html.haml +9 -0
- data/app/views/accounts/_links.html.haml +12 -0
- data/app/views/accounts/_list.html.haml +17 -0
- data/app/views/{readers → accounts}/_memberships.html.haml +0 -0
- data/app/views/accounts/_profile.html.haml +29 -0
- data/app/views/accounts/_profile_form.html.haml +86 -0
- data/app/views/accounts/_reader.html.haml +10 -0
- data/app/views/accounts/dashboard.html.haml +28 -0
- data/app/views/{readers → accounts}/edit.html.haml +12 -10
- data/app/views/accounts/edit_profile.html.haml +34 -0
- data/app/views/accounts/index.html.haml +23 -0
- data/app/views/{readers → accounts}/login.html.haml +0 -0
- data/app/views/{readers → accounts}/new.html.haml +0 -0
- data/app/views/{readers → accounts}/permission_denied.html.haml +0 -0
- data/app/views/{readers → accounts}/show.html.haml +9 -12
- data/app/views/dashboard/_description.html.haml +3 -0
- data/app/views/dashboard/_directory.html.haml +3 -0
- data/app/views/dashboard/_groups.html.haml +8 -0
- data/app/views/dashboard/_messages.html.haml +11 -0
- data/app/views/dashboard/_profile.html.haml +3 -0
- data/app/views/dashboard/_welcome.html.haml +5 -0
- data/app/views/groups/_all.html.haml +10 -0
- data/app/views/groups/index.html.haml +21 -0
- data/app/views/groups/show.html.haml +31 -0
- data/app/views/messages/show.html.haml +12 -3
- data/app/views/reader_sessions/new.html.haml +1 -1
- data/app/views/shared/_standard_reader_parts.html.haml +9 -3
- data/config/initializers/formats.rb +2 -0
- data/config/initializers/radiant_config.rb +3 -0
- data/config/locales/en.yml +90 -25
- data/config/routes.rb +8 -6
- data/db/migrate/20110707101339_group_slugs.rb +9 -0
- data/db/migrate/20110711150605_snail_addresses.rb +21 -0
- data/db/migrate/20110712081159_directory_permissions.rb +11 -0
- data/db/migrate/20110712141134_name_parts.rb +9 -0
- data/db/migrate/20110728112254_current_login_at.rb +9 -0
- data/lib/grouped_model.rb +16 -12
- data/lib/grouped_page.rb +1 -0
- data/lib/radiant-reader-extension.rb +1 -1
- data/lib/reader_admin_ui.rb +32 -1
- data/lib/reader_tags.rb +119 -52
- data/lib/site_controller_extensions.rb +2 -2
- data/public/stylesheets/sass/reader.sass +49 -0
- data/radiant-reader-extension.gemspec +4 -1
- data/reader_extension.rb +9 -9
- data/spec/controllers/{readers_controller_spec.rb → accounts_controller_spec.rb} +9 -5
- data/spec/controllers/groups_controller_spec.rb +17 -0
- data/spec/controllers/reader_activations_controller_spec.rb +3 -3
- data/spec/datasets/readers_dataset.rb +3 -0
- data/spec/lib/reader_tags_spec.rb +55 -0
- data/spec/matchers/reader_login_system_matcher.rb +2 -2
- data/spec/models/group_spec.rb +6 -0
- data/spec/models/reader_page_spec.rb +106 -0
- metadata +109 -26
- data/app/views/readers/index.html.haml +0 -38
data/README.md
CHANGED
@@ -2,30 +2,34 @@
|
|
2
2
|
|
3
3
|
This is a framework that takes care of all the dull bits of registering, activating, reminding, logging in and editing preferences for your site visitors.
|
4
4
|
|
5
|
-
It uses authlogic to handle sessions and provides complete interfaces both for the administrator and the visitor. The admin interface is basic and fits in with radiant. The visitor interface is more friendly (and incidentally includes a trick email field - so-called inverse captcha - that should prevent spam signups).
|
5
|
+
It uses authlogic to handle sessions and provides complete interfaces both for the administrator and the visitor. The admin interface is very basic and fits in with radiant. The visitor interface is more friendly (and incidentally includes a trick email field - so-called inverse captcha - that should prevent spam signups).
|
6
6
|
|
7
7
|
The visitors are referred to as 'readers' here. Readers never see the admin interface, but your site authors and admins are automatically given reader status.
|
8
8
|
|
9
|
-
The purpose of this extension is to provide a common core that supports other visitor-facing machinery. See for example our [forum extension](http://github.com/spanner/radiant-forum-extension) for discussions and page/blog comments
|
9
|
+
The purpose of this extension is to provide a common core that supports other visitor-facing machinery. See for example our [forum extension](http://github.com/spanner/radiant-forum-extension) for discussions and page/blog comments and [downloads extension](http://github.com/spanner/radiant-downloads-extension) for secure access-controlled file downloads. More will follow and I hope other people will make use of this framework.
|
10
10
|
|
11
11
|
## Latest
|
12
12
|
|
13
13
|
This version requires edge radiant, or radiant 1 when it becomes available. We are using a lot of the new configuration and sheets code.
|
14
14
|
|
15
|
+
New ReaderPages provide flexible directory services with configurable access control. The old controller and page parts mechanism is going to be phased out gradually both here and in the forum in favour of more orthodox radiant page-types. We will always need to use the layout-wrapper approach for login and registration forms, though.
|
16
|
+
|
15
17
|
Right now we are **not compatible with multi_site or the sites extension**: that's mostly because neither is radiant edge: it will all be sorted out in time for the release of v1, which isn't far away.
|
16
18
|
|
19
|
+
Also:
|
20
|
+
|
17
21
|
* public interface internationalized;
|
18
22
|
* Uses the new configuration interface;
|
19
23
|
* Messaging much simplified and now intended to be purely administrative.
|
20
|
-
* ajaxable status panel returned by `reader_session_url`
|
24
|
+
* ajaxable status panel returned by `reader_session_url` (ie. you just have to call /reader_session.js over xmlhttp to get a sensible welcome and control block)
|
21
25
|
|
22
26
|
## Status
|
23
27
|
|
24
|
-
Compatible with radiant
|
28
|
+
Compatible with radiant 1, which isn't out yet. You can use radiant edge to try this out. Expect small changes in support of the new forum and group releases. Multi-site compatibility will follow soon.
|
25
29
|
|
26
30
|
## Note on internationalisation and customisation
|
27
31
|
|
28
|
-
The locale strings here are generally defined in a functional rather than grammatical way. That is, they have labels like `activation_required_explanation` rather than being assembled out of
|
32
|
+
The locale strings here are generally defined in a functional rather than grammatical way. That is, they have labels like `activation_required_explanation` rather than being assembled out of lexical units. This is partly because for flexibility of translation, but also because it gives you an easy way to change the text on functional pages like reader-preferences and registration forms.
|
29
33
|
|
30
34
|
## Requirements
|
31
35
|
|
@@ -1,23 +1,40 @@
|
|
1
|
-
class
|
1
|
+
class AccountsController < ReaderActionController
|
2
2
|
helper :reader
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
before_filter :check_registration_allowed, :only => [:new, :create]
|
7
|
-
before_filter :initialize_partials
|
8
|
-
before_filter :i_am_me, :only => [:show, :edit]
|
4
|
+
before_filter :check_registration_allowed, :only => [:new, :create, :activate]
|
5
|
+
before_filter :i_am_me, :only => [:show, :edit, :edit_profile]
|
9
6
|
before_filter :require_reader, :except => [:new, :create, :activate]
|
10
7
|
before_filter :default_to_self, :only => [:show]
|
11
|
-
before_filter :restrict_to_self, :only => [:edit, :update, :resend_activation]
|
8
|
+
before_filter :restrict_to_self, :only => [:edit, :edit_profile, :update, :resend_activation]
|
12
9
|
before_filter :no_removing, :only => [:remove, :destroy]
|
13
10
|
before_filter :ensure_groups_subscribable, :only => [:update, :create]
|
14
11
|
|
15
12
|
def index
|
16
|
-
@readers = Reader.
|
13
|
+
@readers = Reader.visible_to(current_reader)
|
14
|
+
respond_to do |format|
|
15
|
+
format.html {}
|
16
|
+
format.csv {
|
17
|
+
send_data generate_csv(@readers), :type => 'text/csv; charset=utf-8; header=present', :filename => "everyone.csv"
|
18
|
+
}
|
19
|
+
format.vcard {
|
20
|
+
send_data @readers.map(&:vcard).join("\n"), :filename => "everyone.vcf"
|
21
|
+
}
|
22
|
+
end
|
17
23
|
end
|
18
24
|
|
19
25
|
def show
|
20
26
|
@reader = Reader.find(params[:id])
|
27
|
+
respond_to do |format|
|
28
|
+
format.html
|
29
|
+
format.vcard {
|
30
|
+
send_data @reader.vcard.to_s, :filename => "#{@reader.filename}.vcf"
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def dashboard
|
36
|
+
# @reader = current_reader
|
37
|
+
expires_now
|
21
38
|
end
|
22
39
|
|
23
40
|
def new
|
@@ -33,7 +50,11 @@ class ReadersController < ReaderActionController
|
|
33
50
|
def edit
|
34
51
|
expires_now
|
35
52
|
end
|
36
|
-
|
53
|
+
|
54
|
+
def edit_profile
|
55
|
+
expires_now
|
56
|
+
end
|
57
|
+
|
37
58
|
def create
|
38
59
|
@reader = Reader.new(params[:reader])
|
39
60
|
@reader.clear_password = params[:reader][:password]
|
@@ -67,7 +88,7 @@ class ReadersController < ReaderActionController
|
|
67
88
|
@reader.clear_password = params[:reader][:password] if params[:reader][:password]
|
68
89
|
if @reader.save
|
69
90
|
flash[:notice] = t('reader_extension.account_updated')
|
70
|
-
redirect_to
|
91
|
+
redirect_to dashboard_url
|
71
92
|
else
|
72
93
|
render :action => 'edit'
|
73
94
|
end
|
@@ -113,27 +134,7 @@ protected
|
|
113
134
|
end
|
114
135
|
end
|
115
136
|
|
116
|
-
def self.add_edit_partial(path)
|
117
|
-
@@edit_partials ||= []
|
118
|
-
edit_partials.push(path)
|
119
|
-
end
|
120
|
-
|
121
|
-
def self.add_show_partial(path)
|
122
|
-
@@show_partials ||= []
|
123
|
-
show_partials.push(path)
|
124
|
-
end
|
125
|
-
|
126
|
-
def self.add_index_partial(path)
|
127
|
-
@@index_partials ||= []
|
128
|
-
index_partials.push(path)
|
129
|
-
end
|
130
|
-
|
131
137
|
private
|
132
|
-
def initialize_partials
|
133
|
-
@show_partials = show_partials
|
134
|
-
@edit_partials = edit_partials
|
135
|
-
@index_partials = index_partials
|
136
|
-
end
|
137
138
|
|
138
139
|
def ensure_groups_subscribable
|
139
140
|
if params[:reader] && params[:reader][:group_ids]
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class GroupsController < ReaderActionController
|
2
|
+
helper :reader
|
3
|
+
|
4
|
+
before_filter :require_reader
|
5
|
+
before_filter :get_group_or_groups
|
6
|
+
before_filter :require_group_visibility, :only => [:show]
|
7
|
+
|
8
|
+
def index
|
9
|
+
end
|
10
|
+
|
11
|
+
def show
|
12
|
+
@readers = @group.readers.uniq
|
13
|
+
respond_to do |format|
|
14
|
+
format.html
|
15
|
+
format.csv {
|
16
|
+
send_data generate_csv(@readers), :type => 'text/csv; charset=utf-8; header=present', :filename => "#{@group.filename}.csv"
|
17
|
+
}
|
18
|
+
format.vcard {
|
19
|
+
send_data @readers.map(&:vcard).join("\n"), :filename => "#{@group.filename}.vcf"
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def get_group_or_groups
|
27
|
+
@groups = Group.visible_to(current_reader)
|
28
|
+
@group = @groups.find(params[:id]).first if params[:id]
|
29
|
+
end
|
30
|
+
|
31
|
+
def require_group_visibility
|
32
|
+
raise ReaderError::AccessDenied if @group && !@group.visible_to?(current_reader)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -13,13 +13,13 @@ class PasswordResetsController < ReaderActionController
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def create
|
16
|
-
@
|
17
|
-
if @
|
18
|
-
if @
|
19
|
-
@
|
16
|
+
@forgetter = Reader.find_by_email(params[:email])
|
17
|
+
if @forgetter
|
18
|
+
if @forgetter.activated?
|
19
|
+
@forgetter.send_password_reset_message
|
20
20
|
render
|
21
21
|
else
|
22
|
-
@
|
22
|
+
@forgetter.send_activation_message
|
23
23
|
redirect_to new_reader_activation_url
|
24
24
|
end
|
25
25
|
else
|
@@ -42,7 +42,7 @@ class PasswordResetsController < ReaderActionController
|
|
42
42
|
if @reader.save
|
43
43
|
self.current_reader = @reader
|
44
44
|
flash[:notice] = t('reader_extension.password_updated_notice')
|
45
|
-
redirect_to
|
45
|
+
redirect_to dashboard_url
|
46
46
|
else
|
47
47
|
flash[:error] = t('reader_extension.password_mismatch')
|
48
48
|
render :action => :edit
|
@@ -99,4 +99,12 @@ protected
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
|
+
def generate_csv(readers=[])
|
103
|
+
columns = %w{forename surname email phone mobile postal_address}
|
104
|
+
table = FasterCSV.generate do |csv|
|
105
|
+
csv << columns.map { |f| t("activerecord.attributes.reader.#{f}") }
|
106
|
+
readers.each { |r| csv << columns.map{ |f| r.send(f.to_sym) } }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
102
110
|
end
|
@@ -31,11 +31,12 @@ class ReaderActivationsController < ReaderActionController
|
|
31
31
|
if @reader
|
32
32
|
@reader.activate!
|
33
33
|
self.current_reader = @reader
|
34
|
+
redirect_to dashboard_url
|
34
35
|
else
|
35
36
|
@error = t("reader_extension.please_check_message")
|
37
|
+
expires_now
|
38
|
+
render :action => 'show'
|
36
39
|
end
|
37
|
-
expires_now
|
38
|
-
render :action => 'show'
|
39
40
|
end
|
40
41
|
|
41
42
|
protected
|
@@ -17,7 +17,7 @@ class ReaderSessionsController < ReaderActionController
|
|
17
17
|
end
|
18
18
|
}
|
19
19
|
format.js {
|
20
|
-
render :partial => '
|
20
|
+
render :partial => 'accounts/controls', :layout => false
|
21
21
|
}
|
22
22
|
end
|
23
23
|
end
|
@@ -26,7 +26,7 @@ class ReaderSessionsController < ReaderActionController
|
|
26
26
|
if current_reader
|
27
27
|
if current_reader.activated?
|
28
28
|
cookies[:error] = t('reader_extension.already_logged_in')
|
29
|
-
redirect_to
|
29
|
+
redirect_to default_welcome_url(current_reader)
|
30
30
|
else
|
31
31
|
cookies[:error] = t('reader_extension.account_requires_activation')
|
32
32
|
redirect_to reader_activation_url
|
@@ -72,4 +72,8 @@ class ReaderSessionsController < ReaderActionController
|
|
72
72
|
redirect_to reader_login_url
|
73
73
|
end
|
74
74
|
|
75
|
+
def default_welcome_url(reader=nil)
|
76
|
+
reader.home_url || dashboard_url
|
77
|
+
end
|
78
|
+
|
75
79
|
end
|
@@ -1,7 +1,11 @@
|
|
1
1
|
require 'sanitize'
|
2
2
|
require "sanitize/config/generous"
|
3
|
+
require "fastercsv"
|
3
4
|
|
4
5
|
module ReaderHelper
|
6
|
+
include SnailHelpers
|
7
|
+
include Admin::RegionsHelper
|
8
|
+
|
5
9
|
def standard_gravatar_for(reader=nil, url=nil)
|
6
10
|
size = Radiant::Config['forum.gravatar_size'] || 40
|
7
11
|
url ||= reader_url(reader)
|
@@ -20,6 +24,26 @@ module ReaderHelper
|
|
20
24
|
image_tag gravatar_url(reader.email, gravatar_options), img_options
|
21
25
|
end
|
22
26
|
end
|
27
|
+
|
28
|
+
def link_to_reader(reader)
|
29
|
+
if page = ReaderPage.first
|
30
|
+
page.url_for(reader)
|
31
|
+
else
|
32
|
+
reader_url(reader)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def link_to_group(group)
|
37
|
+
if page = group.homepage
|
38
|
+
link_to group.name, page.url
|
39
|
+
else
|
40
|
+
link_to group.name, group_url(group)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def link_to_message(message)
|
45
|
+
link_to message.subject, message_url(message)
|
46
|
+
end
|
23
47
|
|
24
48
|
def home_page_link(options={})
|
25
49
|
home_page = Page.find_by_parent_id(nil)
|
@@ -34,11 +58,14 @@ module ReaderHelper
|
|
34
58
|
Sanitize.clean(textilize(text), Sanitize::Config::GENEROUS)
|
35
59
|
end
|
36
60
|
|
37
|
-
def truncate_words(text='',
|
61
|
+
def truncate_words(text='', options={})
|
38
62
|
return '' if text.blank?
|
63
|
+
options = {:length => options} unless options.is_a? Hash
|
64
|
+
options.reverse_merge!(:length => 30, :omission => '…')
|
39
65
|
words = text.split
|
40
|
-
|
41
|
-
|
66
|
+
length = options[:length].to_i
|
67
|
+
options[:omission] = '' unless words.size > length
|
68
|
+
words[0..(length-1)].join(" ") + options[:omission]
|
42
69
|
end
|
43
70
|
|
44
71
|
def pagination_and_summary_for(list, name='')
|
@@ -53,20 +80,9 @@ module ReaderHelper
|
|
53
80
|
|
54
81
|
def pagination_summary(list, name='')
|
55
82
|
total = list.total_entries
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
name ||= t(list.first.class.to_s.underscore.gsub('_', ' '))
|
60
|
-
if total == 1
|
61
|
-
%{#{t('reader_extension.showing')} #{t('reader_extension.one')} #{name}}
|
62
|
-
elsif list.current_page == 1 && total < list.per_page
|
63
|
-
%{#{t('reader_extension.all')} #{total} #{name.pluralize}}
|
64
|
-
else
|
65
|
-
start = list.offset + 1
|
66
|
-
finish = ((list.offset + list.per_page) < list.total_entries) ? list.offset + list.per_page : list.total_entries
|
67
|
-
%{#{start} #{t('reader_extension.to')} #{finish} #{t('reader_extension.of')} #{total} #{name.pluralize}}
|
68
|
-
end
|
69
|
-
end
|
83
|
+
start = list.offset + 1
|
84
|
+
finish = ((list.offset + list.per_page) < list.total_entries) ? list.offset + list.per_page : list.total_entries
|
85
|
+
t("reader_extension.showing_of_total", :count => total, :start => start, :finish => finish, :name => name, :names => name.pluralize)
|
70
86
|
end
|
71
87
|
|
72
88
|
def message_preview(subject, body, reader)
|
@@ -101,5 +117,33 @@ EOM
|
|
101
117
|
end
|
102
118
|
options
|
103
119
|
end
|
120
|
+
|
121
|
+
def friendly_date(datetime)
|
122
|
+
I18n.l(datetime, :format => friendly_date_format(datetime)) if datetime
|
123
|
+
end
|
124
|
+
|
125
|
+
def friendly_date_format(datetime)
|
126
|
+
if datetime && date = datetime.to_date
|
127
|
+
if (date.to_datetime == Date.today)
|
128
|
+
:today
|
129
|
+
elsif (date.to_datetime == Date.yesterday)
|
130
|
+
:yesterday
|
131
|
+
elsif (date.to_datetime > 6.days.ago)
|
132
|
+
:recently
|
133
|
+
elsif (date.year == Date.today.year)
|
134
|
+
:this_year
|
135
|
+
else
|
136
|
+
:standard
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def country_options_for_select(selected = nil, default_selected = Snail.home_country)
|
142
|
+
usps_country_options_for_select(selected, default_selected)
|
143
|
+
end
|
144
|
+
|
145
|
+
def email_link(address)
|
146
|
+
mail_to address, nil, :encode => :hex, :replace_at => ' at ', :class => 'mailto'
|
147
|
+
end
|
104
148
|
|
105
149
|
end
|
data/app/models/group.rb
CHANGED
@@ -11,10 +11,11 @@ class Group < ActiveRecord::Base
|
|
11
11
|
has_many :permissions
|
12
12
|
has_many :pages, :through => :permissions
|
13
13
|
has_many :memberships
|
14
|
-
has_many :readers, :through => :memberships
|
14
|
+
has_many :readers, :through => :memberships, :uniq => true
|
15
15
|
|
16
|
-
|
17
|
-
|
16
|
+
before_validation :set_slug
|
17
|
+
validates_presence_of :name, :slug, :allow_blank => false
|
18
|
+
validates_uniqueness_of :name, :slug
|
18
19
|
|
19
20
|
named_scope :with_home_page, { :conditions => "homepage_id IS NOT NULL", :include => :homepage }
|
20
21
|
named_scope :subscribable, { :conditions => "public = 1" }
|
@@ -24,6 +25,14 @@ class Group < ActiveRecord::Base
|
|
24
25
|
{ :conditions => ["groups.id IN (#{ids.map{"?"}.join(',')})", *ids] }
|
25
26
|
}
|
26
27
|
|
28
|
+
named_scope :containing, lambda { |reader|
|
29
|
+
{
|
30
|
+
:joins => "INNER JOIN memberships as mb on mb.group_id = groups.id",
|
31
|
+
:conditions => ["mb.reader_id = ?", reader.id],
|
32
|
+
:group => column_names.map { |n| 'groups.' + n }.join(',')
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
27
36
|
named_scope :attached_to, lambda { |objects|
|
28
37
|
conditions = objects.map{|o| "(pp.permitted_type = ? AND pp.permitted_id = ?)" }.join(" OR ")
|
29
38
|
binds = objects.map{|o| [o.class.to_s, o.id]}.flatten
|
@@ -32,15 +41,30 @@ class Group < ActiveRecord::Base
|
|
32
41
|
:joins => "INNER JOIN permissions as pp on pp.group_id = groups.id",
|
33
42
|
:conditions => [conditions, *binds],
|
34
43
|
:having => "pcount > 0", # otherwise attached_to([]) returns all groups
|
35
|
-
:group => column_names.map { |n|
|
44
|
+
:group => column_names.map { |n| 'groups.' + n }.join(','),
|
36
45
|
:readonly => false
|
37
46
|
}
|
38
47
|
}
|
39
48
|
|
49
|
+
def self.visible_to(reader=nil)
|
50
|
+
return all if Radiant.config['readers.public?']
|
51
|
+
return scoped({:conditions => "1 = 0"}) unless reader # nasty but chainable
|
52
|
+
return containing(reader) if Radiant.config['readers.confine_to_groups?']
|
53
|
+
return all
|
54
|
+
end
|
55
|
+
|
56
|
+
def visible_to?(reader=nil)
|
57
|
+
self.class.visible_to(reader).include? self
|
58
|
+
end
|
59
|
+
|
40
60
|
def url
|
41
61
|
homepage.url if homepage
|
42
62
|
end
|
43
63
|
|
64
|
+
def filename
|
65
|
+
name.downcase.gsub(/\W/, '_')
|
66
|
+
end
|
67
|
+
|
44
68
|
def send_welcome_to(reader)
|
45
69
|
if reader.activated? # welcomes are also triggered on activation
|
46
70
|
if message = Message.belonging_to(self).for_function('group_welcome').first # only if a group_welcome message exists *belonging to this group*
|
@@ -74,7 +98,11 @@ class Group < ActiveRecord::Base
|
|
74
98
|
define_method("#{classname.downcase.pluralize}") { self.send("#{classname.to_s.downcase}_permissions".intern).map(&:permitted) }
|
75
99
|
end
|
76
100
|
|
77
|
-
|
101
|
+
private
|
102
|
+
|
103
|
+
def set_slug
|
104
|
+
self.slug ||= self.name.slugify.to_s
|
105
|
+
end
|
78
106
|
|
79
107
|
end
|
80
108
|
|
data/app/models/message.rb
CHANGED
@@ -50,10 +50,6 @@ class Message < ActiveRecord::Base
|
|
50
50
|
deliveries.any?
|
51
51
|
end
|
52
52
|
|
53
|
-
def delivered_to?(reader)
|
54
|
-
recipients.include?(reader)
|
55
|
-
end
|
56
|
-
|
57
53
|
def preview(reader=nil)
|
58
54
|
reader ||= possible_readers.first || Reader.for_user(created_by)
|
59
55
|
ReaderNotifier.create_message(reader, self)
|
@@ -112,4 +108,11 @@ class Message < ActiveRecord::Base
|
|
112
108
|
MessageReader.find_or_create_by_message_id_and_reader_id(self.id, reader.id).update_attribute(:sent_at, Time.now)
|
113
109
|
end
|
114
110
|
|
111
|
+
def delivered_to?(reader)
|
112
|
+
recipients.include?(reader)
|
113
|
+
end
|
114
|
+
|
115
|
+
def delivery_to(reader)
|
116
|
+
deliveries.to_reader(reader).first if delivered_to?(reader)
|
117
|
+
end
|
115
118
|
end
|
data/app/models/reader.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
require 'authlogic'
|
2
2
|
require 'digest/sha1'
|
3
|
+
require 'snail'
|
4
|
+
require 'vcard'
|
3
5
|
|
4
6
|
class Reader < ActiveRecord::Base
|
5
7
|
@@user_columns = [:name, :email, :login, :created_at, :password, :notes]
|
6
8
|
cattr_accessor :user_columns
|
7
9
|
cattr_accessor :current
|
8
|
-
attr_accessor :email_field
|
10
|
+
attr_accessor :email_field, :newly_activated
|
9
11
|
|
10
12
|
acts_as_authentic do |config|
|
11
13
|
config.validations_scope = :site_id if defined? Site
|
@@ -21,7 +23,7 @@ class Reader < ActiveRecord::Base
|
|
21
23
|
has_many :message_readers
|
22
24
|
has_many :messages, :through => :message_readers
|
23
25
|
has_many :memberships
|
24
|
-
has_many :groups, :through => :memberships
|
26
|
+
has_many :groups, :through => :memberships, :uniq => true
|
25
27
|
accepts_nested_attributes_for :memberships
|
26
28
|
|
27
29
|
before_update :update_user
|
@@ -79,13 +81,67 @@ class Reader < ActiveRecord::Base
|
|
79
81
|
end
|
80
82
|
reader
|
81
83
|
end
|
84
|
+
|
85
|
+
def self.visible_to(reader=nil)
|
86
|
+
return self.all if Radiant.config['readers.public?']
|
87
|
+
return self.scoped({:conditions => "1 = 0"}) unless reader # nasty but chainable
|
88
|
+
return self.in_groups(reader.groups) if Radiant.config['readers.confine_to_groups?']
|
89
|
+
return self.all
|
90
|
+
end
|
91
|
+
|
92
|
+
def visible_to?(reader=nil)
|
93
|
+
self.class.visible_to(reader).include? self
|
94
|
+
end
|
82
95
|
|
96
|
+
# not very i18nal, this
|
83
97
|
def forename
|
84
|
-
read_attribute(:forename) || name.split(/\s
|
98
|
+
read_attribute(:forename) || name.split(/\s+/).first
|
85
99
|
end
|
86
100
|
|
101
|
+
def surname
|
102
|
+
read_attribute(:surname) || name.split(/\s+/).last
|
103
|
+
end
|
104
|
+
|
105
|
+
def postal_address
|
106
|
+
Snail.new(
|
107
|
+
:name => name,
|
108
|
+
:line_1 => post_line1,
|
109
|
+
:line_2 => post_line2,
|
110
|
+
:city => post_city,
|
111
|
+
:region => post_province,
|
112
|
+
:postal_code => postcode,
|
113
|
+
:country => post_country
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
def vcard
|
118
|
+
@vcard ||= Vpim::Vcard::Maker.make2 do |maker|
|
119
|
+
maker.add_name do |n|
|
120
|
+
n.prefix = honorific || ""
|
121
|
+
n.given = forename || ""
|
122
|
+
n.family = surname || ""
|
123
|
+
end
|
124
|
+
maker.add_addr {|a|
|
125
|
+
a.location = 'home' # until we do this properly with multiple contact sets
|
126
|
+
a.country = post_country || ""
|
127
|
+
a.region = post_province || ""
|
128
|
+
a.locality = post_city || ""
|
129
|
+
a.street = [post_line1, post_line2].compact.join("\n")
|
130
|
+
a.postalcode = postcode || ""
|
131
|
+
}
|
132
|
+
maker.add_tel phone { |t| t.location = 'home' } unless phone.blank?
|
133
|
+
maker.add_tel mobile { |t| t.location = 'cell' } unless mobile.blank?
|
134
|
+
maker.add_email email { |e| t.location = 'home' }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def filename
|
139
|
+
name.downcase.gsub(/\W/, '_')
|
140
|
+
end
|
141
|
+
|
87
142
|
def activate!
|
88
143
|
self.activated_at = Time.now.utc
|
144
|
+
self.newly_activated = true
|
89
145
|
self.save!
|
90
146
|
send_welcome_message
|
91
147
|
send_group_welcomes
|
@@ -94,6 +150,10 @@ class Reader < ActiveRecord::Base
|
|
94
150
|
def activated?
|
95
151
|
!inactive?
|
96
152
|
end
|
153
|
+
|
154
|
+
def newly_activated?
|
155
|
+
!!newly_activated
|
156
|
+
end
|
97
157
|
|
98
158
|
def inactive?
|
99
159
|
self.activated_at.nil?
|
@@ -139,6 +199,14 @@ class Reader < ActiveRecord::Base
|
|
139
199
|
homegroup.homepage
|
140
200
|
end
|
141
201
|
end
|
202
|
+
|
203
|
+
def home_url
|
204
|
+
if homepage = self.find_homepage
|
205
|
+
homepage.url
|
206
|
+
else
|
207
|
+
nil
|
208
|
+
end
|
209
|
+
end
|
142
210
|
|
143
211
|
def can_see? (this)
|
144
212
|
permitted_groups = this.permitted_groups
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class ReaderPage < Page
|
2
|
+
include WillPaginate::ViewHelpers
|
3
|
+
attr_accessor :reader, :group
|
4
|
+
|
5
|
+
description %{ Presents readers and groups with configurable access control. }
|
6
|
+
|
7
|
+
def current_reader
|
8
|
+
Reader.current
|
9
|
+
end
|
10
|
+
|
11
|
+
def readers
|
12
|
+
if group
|
13
|
+
group.readers.visible_to(current_reader)
|
14
|
+
else
|
15
|
+
Reader.visible_to(current_reader)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def groups
|
20
|
+
Group.visible_to(current_reader)
|
21
|
+
end
|
22
|
+
|
23
|
+
def cache?
|
24
|
+
!!Radiant.config['readers.public?']
|
25
|
+
end
|
26
|
+
|
27
|
+
def visible?
|
28
|
+
Radiant.config['readers.public?'] || current_reader
|
29
|
+
end
|
30
|
+
|
31
|
+
def url_for(thing)
|
32
|
+
if thing.is_a?(Reader)
|
33
|
+
File.join(self.url, thing.id)
|
34
|
+
elsif thing.is_a?(Group)
|
35
|
+
File.join(self.url, thing.slug)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_by_url(url, live = true, clean = false)
|
40
|
+
url = clean_url(url) if clean
|
41
|
+
my_url = self.url
|
42
|
+
return false unless url =~ /^#{Regexp.quote(my_url)}(.*)/
|
43
|
+
raise ReaderError::AccessDenied unless visible?
|
44
|
+
|
45
|
+
params = $1.split('/').compact
|
46
|
+
self.group = Group.find_by_slug(params.first) if params.first =~ /\w/
|
47
|
+
self.reader = Reader.find_by_id(params.last) if params.last !~ /\D/
|
48
|
+
|
49
|
+
raise ReaderError::AccessDenied if group && !group.visible_to?(current_reader)
|
50
|
+
raise ReaderError::AccessDenied if reader && !reader.visible_to?(current_reader)
|
51
|
+
raise ActiveRecord::RecordNotFound if reader && group && !reader.is_in?(group)
|
52
|
+
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
File without changes
|
@@ -1,6 +1,5 @@
|
|
1
1
|
- if !current_reader
|
2
|
-
=
|
3
|
-
= link_to t('reader_extension.navigation.register'), new_reader_url
|
2
|
+
= t('reader_extension.navigation.welcome_please_log_in', :login_url => reader_login_url, :register_url => new_reader_url)
|
4
3
|
- else
|
5
4
|
%strong
|
6
5
|
= link_to t('reader_extension.navigation.greeting', :name => current_reader.name), reader_profile_url
|