root/veekun/trunk/lib/Vee/Controller/Forum.pm

Revision 406, 9.6 KB (checked in by eevee, 2 years ago)

Database refactoring. Renamed columns and tables to be more consistent and more readable. (#58)

Line 
1package Vee::Controller::Forum;
2
3use strict;
4use warnings;
5use base 'Catalyst::Controller';
6
7use Scalar::Util qw/weaken/;
8use List::Util qw/min/;
9use Vee::Utils::Forum;
10
11# TODO for flags:
12# - consolidate checking for deletion/hiding and get it out of the main code
13# - styling
14
15=head1 NAME
16
17Vee::Controller::Forum - Forum Controller
18
19=head1 SYNOPSIS
20
21See L<Vee>
22
23=head1 DESCRIPTION
24
25Catalyst Controller for forum viewing.
26
27=head1 METHODS
28
29=cut
30
31=head2 auto
32
33=cut
34
35sub auto : Private {
36    my ($self, $c) = @_;
37    my $s = $c->stash;
38   
39    # TODO: hacky
40    weaken $c;
41    $s->{can_post} = sub { Vee::Utils::Forum::can_post($c, @_) };
42}
43
44=head2 list
45
46List of forums.
47
48=cut
49
50sub list : Path : Args(0) {
51    my ($self, $c) = @_;
52    my $s = $c->stash;
53
54    $s->{page_title} = 'Forums';
55    $s->{link_name}  = 'forum';
56    $s->{crumbs}     = [ '<a href="/forum">Forum Index</a>' ];
57    $s->{extra_css}  = 'forum';
58
59    $s->{announcements_rs} = $c->model('DBIC::Threads')->search_announcements;
60    $s->{forums_rs}        = $c->model('DBIC::Forums')->search(undef, { prefetch => { last_post => 'user' } });
61
62    # Grab a list of forums with unread threads
63    if ($c->user) {
64        my @unread_ids  = $c->model('DBIC::Forums')->unread_ids($c->user->obj);
65        my %unread_hash = map { $_ => 1 } @unread_ids;
66
67        $s->{is_unread} = sub { $unread_hash{$_[0]->id} };
68    }
69
70    $s->{template} = 'forum/index.tt';
71}
72
73=head2 view
74
75Forum view: list of threads.
76
77=cut
78
79sub view : Path : Args(1) {
80    my ($self, $c, $id) = @_;
81    my $s = $c->stash;
82
83    my $forum = $c->model('DBIC::Forums')->find($id)
84        or $c->vee_abort('There is no forum with id ', $id, '.  You may have followed an old link, or might be playing with URLs.');
85
86    my $skip = $c->req->params->{skip} || 0;
87    my $perpage = $c->site_opts->{page_sizes}{threads};
88
89    $s->{page_title}  = $forum->name . ' - Forums';
90    $s->{page_header} = $forum->name;
91    $s->{link_name}   = 'forum';
92    $s->{extra_css}   = 'forum';
93    $s->{crumbs}      = [ '<a href="/forum">Forum Index</a>', '<a href="/forum/'.$forum->id.'">'.$forum->name.'</a>' ];
94
95    $s->{announcements_rs} = $c->model('DBIC::Threads')->search_announcements;
96    $s->{forum}            = $forum;
97    $s->{skip}             = $skip;
98    $s->{threads_rs}       = $forum->search_related('threads', undef, {
99        order_by => 'FIND_IN_SET("sticky", me.flags) > 0 DESC, last_post.time DESC',
100        prefetch => { first_post => 'user', last_post => 'user' },
101        offset   => $skip,
102        rows     => $perpage,
103    } );
104
105    # TODO: ugly but for now I don't know the best way to clean it up
106    $s->{threads_rs}       = $s->{threads_rs}->search(\ 'NOT FIND_IN_SET("deleted", me.flags)') unless $c->can_i(override_thread_deleted => $forum->id);
107
108    # Grab a list of unread threads in this forum
109    if ($c->user) {
110        my @unread_ids  = $forum->search_related('threads')
111                                ->unread_ids($c->user->obj);
112        my %unread_hash = map { $_ => 1 } @unread_ids;
113
114        $s->{is_unread} = sub { $unread_hash{$_[0]->id} };
115    }
116   
117    # form generation stuff
118    my $reply_fields = {
119        content => { type => 'textarea', rows => '10', cols => '100' },
120        id => { type => 'hidden' },
121        subject => { type => 'text', maxlength => 48 },
122        blurb => { type => 'text', maxlength => 96 },
123    };
124   
125    my $form = new Vee::Form(
126        id => 'reply',
127        fields => $reply_fields,
128        params => $c->req->params,
129        copy_params => 1,
130    );
131    $form->force( id => $forum->id );
132   
133    $s->{form}     = $form;
134    $s->{template} = 'forum/view.tt';
135}
136
137=head2 thread
138
139Thread view: list of posts.
140
141=cut
142
143# TODO: spit out an error if we're not actually inside the thread!
144sub thread : Local : Args(1) {
145    my ($self, $c, $id) = @_;
146    my $s = $c->stash;
147
148    my $thread = $c->model('DBIC::Threads')->find($id, { prefetch => 'forum' })
149        or $c->vee_abort('There is no thread with id ', $id, '.');
150    $thread->view_count( $thread->view_count + 1 ); $thread->update;
151
152    if ($thread->flags =~ /deleted/ and not $c->can_i(override_thread_deleted => $thread->forum->id)) {
153        $c->vee_abort('This thread has been deleted.');
154    }
155   
156    my $filter = $c->req->params->{filter};
157    my $skip = $c->req->params->{skip} || 0;
158   
159    my $filter_user;
160    if ($filter) {
161        $filter_user = $c->model('DBIC::Users')->search({ name => $filter })->first;
162        if ($filter_user) {
163            my $posts_shown = $c->model('DBIC::Posts')->count({ thread_id => $id, user_id => $filter_user->id });
164        } else {
165            push @{ $s->{error_msg} }, "Can't find user " . $c->vee_cleanse($filter) . " in the database.";
166            undef $filter;
167        }
168    }
169   
170    my $perpage = $c->site_opts->{page_sizes}{posts};
171    my $lastpage = ($skip + $perpage >= $thread->post_count);
172    my $posts_rs = $c->model('DBIC::Posts')->search(
173        { 'me.thread_id' => $id, ( $filter_user ? ( 'me.user_id' => $filter_user->id ) : () ) },
174        { prefetch => [ 'user', { 'lastedit', 'user' } ], order_by => 'me.time ASC', offset => $skip, rows => $perpage + 1 }
175    );
176    my $post_count = $c->model('DBIC::Posts')->count({ 'me.thread_id' => $id, ( $filter_user ? ( 'me.user_id' => $filter_user->id ) : () ) });
177    my @flags = split /,/, $thread->flags;
178       
179    $s->{physical_lastpost} = $thread->search_related('posts', undef, { order_by => [ 'time DESC', 'id DESC' ] })->single;
180
181    ### Last-viewed time
182
183    if ($c->user and not $filter) {
184        my $thread_view = $c->model('DBIC::ThreadViews')->search({
185            thread_id => $thread->id,
186            user_id   => $c->user->obj->id,
187        })->single;
188
189        if ($thread_view) {
190            $s->{last_viewed} = $thread_view->last_viewed;
191
192            if ($s->{physical_lastpost} and
193                $thread_view->last_viewed < $s->{physical_lastpost}->time)
194            {
195                $thread_view->last_viewed( $s->{physical_lastpost}->time );
196                $thread_view->update;
197            }
198        } elsif ($s->{physical_lastpost}) {
199            # eval'd due to very improbable but possible race condition: user
200            # loads the same thread twice simultaneously
201            eval {
202                $c->model('DBIC::ThreadViews')->create({
203                    thread_id   => $thread->id,
204                    user_id     => $c->user->obj->id,
205                    last_viewed => $s->{physical_lastpost}->time,
206                });
207            };
208        }
209    }
210
211    # form generation stuff
212    my $reply_fields = {
213        content => { type => 'textarea', rows => '10', cols => '100' },
214        id => { type => 'hidden' },
215    };
216   
217    my $form = new Vee::Form(
218        id => 'reply',
219        fields => $reply_fields,
220        params => $c->req->params,
221        copy_params => 1,
222    );
223    $form->force( id => $thread->id );
224
225    $s->{page_title}  = $thread->subject . ' - Forums';
226    $s->{page_header} = $thread->subject;
227    $s->{link_name}   = 'forum';
228    $s->{crumbs}      = [ '<a href="/forum">Forum Index</a>', '<a href="/forum/'.$thread->forum->id.'">'.$thread->forum->name.'</a>', qq'<a href="/forum/thread/'.$thread->id.'">'.$thread->subject.'</a>' ];
229    if ($filter_user) { push @{ $s->{crumbs} }, '<a href="/user/'.$filter_user->id.'">'.$filter_user->name.'</a>\'s posts' }
230    $s->{extra_css}   = [ 'forum', 'bbcode' ];
231    $s->{form}        = $form;
232    $s->{flags}       = \@flags;
233       
234    $s->{forum}       = $thread->forum;
235    $s->{thread}      = $thread;
236    $s->{page_islast} = $lastpage;
237    $s->{posts_rs}    = $posts_rs;
238    $s->{post_count}  = $post_count;
239    $s->{skip}        = $skip;
240    $s->{filter}      = $filter;
241
242    $s->{template} = 'forum/thread/view.tt';
243}
244
245=head2 post
246
247Post "view": link to the corresponding post.
248
249=cut
250
251sub post : Local : Args(1) {
252    my ($self, $c, $id) = @_;
253
254    my $post = $c->model('DBIC::Posts')->find($id, { prefetch => { thread => 'forum' } })
255        or $c->vee_abort('There is no post with id ', $id, '.');
256
257    # slurp up the number of (visible) posts before the requested one
258    my $offset = $c->model('DBIC::Posts')->count({
259        thread_id => $post->thread_id,
260        -or => [
261            { time => $post->time, id => { '<', $post->id } },
262            { time => { '<', $post->time } },
263        ],
264    });
265
266    my $perpage = $c->site_opts->{page_sizes}{posts};
267    my $skip = $offset - $offset % $perpage;
268    $c->res->redirect( $c->uri('Forum', 'thread', $post->thread_id, ($skip ? { skip => $skip } : ()) ) . "#p$id" );
269}
270
271=head2 flags
272
273Set flags for a thread.
274
275=cut
276
277sub thread_flags : LocalRegex('^thread/(\d*)/(announcement|sticky|locked)') : Args(0) {
278    my ($self, $c) = @_;
279    my ($thread_id, $flag) = @{ $c->req->captures };
280
281    my $thread = $c->model('DBIC::Threads')->find($thread_id);
282
283    toggleflag($thread, $flag)
284        or $c->vee_abort("Whoops! Error setting flag '", $flag, "'. Not much more can be said about this.");
285    $c->res->redirect($c->uri('Forum', 'thread', $thread_id));
286}
287
288=head2 mark_read
289
290Mark everything on the whole forum read.
291
292=cut
293
294sub mark_read : Local : Args(0) {
295    my ($self, $c) = @_;
296
297    if ($c->req->method ne 'POST') {
298        $c->vee_abort('This URL may only be requested by POST.');
299    } elsif (not $c->user) {
300        $c->vee_abort('You must be logged in to use this function.');
301    }
302
303    $c->user->thread_view_cutoff(time);
304    $c->user->update;
305
306    $c->model('DBIC::ThreadViews')->search({
307        user_id => $c->user->obj->id
308    })->delete;
309
310    $c->res->redirect( $c->req->referer || $c->uri_for('/') );
311}
312
313=head1 AUTHOR
314
315Maintainer: Alex "Eevee" Munroe (C<veekun@veekun.com>)
316
317See the included F<AUTHORS> file for a full list of contributers.
318
319=head1 LICENSE
320
321See the included F<LICENSE> file.
322
323=cut
324
3251;
Note: See TracBrowser for help on using the browser.