root/veekun/trunk/lib/Vee.pm

Revision 440, 5.9 KB (checked in by eevee, 22 months ago)

Fixed uri_for encoding its query args more and more on successive calls with the same hashref containing an arrayref. This is actually a Catalyst "problem", but it seems to have "working as intended" status. (#312)

Line 
1package Vee;
2
3#BEGIN { use Carp; *CORE::GLOBAL::die = \&Carp::confess; }
4
5use strict;
6use warnings;
7
8use Image::Size;
9use LWP::UserAgent;
10use Storable qw/dclone/;
11use Time::Out qw/timeout/;
12use YAML qw//;
13
14# load crap
15# first group are DEBUG-ONLY!
16use Catalyst qw/
17    -Debug
18    StackTrace
19    Static::Simple
20   
21    ConfigLoader
22    Unicode
23   
24    Authentication
25    Authentication::Store::DBIC
26    Authentication::Credential::Password
27   
28    Session
29    Session::Store::DBIC
30    Session::State::Cookie
31/;
32use Vee::BBCode;
33use Vee::Authorization;
34use Vee::Utils;
35use Vee::Dex;
36
37our $VERSION = '0.01';
38
39=head1 NAME
40
41Vee - Code for veekun.com
42
43=head1 SYNOPSIS
44
45    script/vee_server.pl
46
47=head1 DESCRIPTION
48
49Codebase for veekun.com; includes a forum, Pokedex, gallery, and other such
50generic website things.
51
52=cut
53
54
55# have to get site config before setup so controller init can play with options
56my $siteopt_source = 'site_options.yml';
57__PACKAGE__->config->{site} = YAML::LoadFile( __PACKAGE__->path_to($siteopt_source) );
58
59__PACKAGE__->setup;
60
61# grab the current revision number, if applicable
62__PACKAGE__->cache->{svn_revision} = timeout 5, sub {
63    open my $fh, '-|', 'svn', 'info'
64        or return;
65    while (my $line = <$fh>) {
66        my ($revision) = ($line =~ /^ Revision: [ ] (\d+) $/smx);
67        if ($revision) {
68            return $revision;
69        }
70    }
71    return;
72};
73
74{
75    # WARNING: HERE BE DRAGONS, AND BY DRAGONS I MEAN HACKS
76    no warnings 'redefine';
77
78    # I find myself doing this a lot and it's a bit cumbersome
79    *DBIx::Class::ResultSet::is_empty = sub {
80        my ($self) = @_;
81        my $first = $self->first;
82        $self->reset;
83        return not $first;
84    };
85
86    # DBIx::Class's find_or_create is incredibly stupid and allows for race
87    # conditions with critical code like this.
88    # CAVEAT EMPTOR: may break if you upgrade
89    *DBIx::Class::ResultSet::find_or_create = sub {
90        my $self     = shift;
91        my $attrs    = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
92        my $hash     = ref $_[0] eq 'HASH' ? shift : {@_};
93        my $exists   = $self->find($hash, $attrs);
94        # this bit is different
95        return $exists if defined $exists;
96        $exists = eval { $self->create($hash) };
97        if ($@ and $@ =~ /Duplicate entry/) {
98            return $self->find($hash, $attrs);
99        } elsif ($@) {
100            die $@;
101        } else {
102            return $exists;
103        }
104    };
105}
106
107=head1 METHODS
108
109=head2 site_opts
110
111Extremely lazy method for getting site options.  Seriously, seriously lazy.
112
113=cut
114
115sub site_opts {
116    return shift->config->{site};
117}
118
119=head2 cache
120
121Returns a cache hash for holding small bits of data useful between requests
122but not worth a database table/row/hit.
123
124=cut
125
126my $cache;
127sub cache {
128    $cache ||= {};  # so we can call this function above
129    return $cache;
130}
131
132=head2 uri
133
134Shortcut for generating URIs.
135
136    $c->uri( $controller, $action, @arguments )
137
138=cut
139
140sub uri {
141    my ($self, $con, $sub, @args) = @_;
142
143    my $url = $self->uri_for(
144        $self->controller($con)->action_for($sub),
145        @args
146    );
147    $url =~ tr/&/;/; # fuck ampersands
148
149    return $url;
150}
151
152=head2 uri_for
153
154Override of Catalyst's uri_for that clones parameters before passing them
155along, so I don't have to worry about e.g. reusing the same query args hashref
156several times in a template.
157
158=cut
159
160sub uri_for {
161    my ($self, @etc) = @_;
162
163    # Only the last parameter can be a query arg hashref
164    if (@etc and ref $etc[-1] eq 'HASH') {
165        $etc[-1] = dclone $etc[-1];
166    }
167
168    return $self->next::method(@etc);
169}
170
171=head2 vee_abort
172
173Stops execution and reports error(s).  Only use this for errors caused by user
174input that prevent a controller from executing normally, such as a thread that
175doesn't exist; internal errors should use die().
176
177If you pass an array of scalars, every EVEN one is assumed to be untrusted user
178input that must be escaped.  This is so you can do the following:
179
180    $c->vee_abort("There is no thingy with id ", $user_input, "!  Sorry.");
181
182C<$user_input> is the second argument, so it will be run through C<vee_cleanse>
183before being sent off to the browser.
184
185If you want multiple errors, make each one an arrayref.
186
187=cut
188
189# BEGIN THE CLEANSING
190sub _final_solution {
191    my $self = shift;
192    my @ret;
193
194    if (ref $_[0] eq 'ARRAY') {
195        @ret = @_;
196    } else {
197        @ret = [ @_ ];
198    }
199
200    for my $row (@ret) {
201        for my $i (0 .. ($#$row - 1) / 2) {
202            $row->[$i * 2 + 1] = $self->vee_cleanse( $row->[$i * 2 + 1] );
203        }
204        $row = join '', @$row;
205    }
206
207    return @ret;
208}
209
210sub vee_abort {
211    my $self = shift;
212    my $s = $self->stash;
213    my @errors = $self->_final_solution(@_);
214
215    $s->{template} = 'blank.tt';
216    $s->{error_msg} = \@errors;
217    $s->{page_title} = $s->{page_title} ? "Error: " . $s->{page_title} : "Error";
218    $self->detach;
219}
220
221=head2 vee_stop
222
223Stops execution and reports a message.  Use this when, for whatever reason, a
224controller has nothing to do but hasn't actually encountered any problems:
225e.g., a search with no results.
226
227Arguments work the same way as with C<vee_abort>.
228
229=cut
230
231sub vee_stop {
232    my $self = shift;
233    my $s = $self->stash;
234    my @msgs = $self->_final_solution(@_);
235
236    $s->{template} = 'blank.tt';
237    $s->{info_msg} = \@msgs;
238    $s->{page_title} ||= "Message";
239    $self->detach;
240}
241
242=head2 vee_cleanse
243
244Format a string from some unknown source so it is suitable for inserting into
245an XHTML document.  This consists of escaping special characters, converting
246newlines to <br/>, and wrapping the result in <code> tags.
247
248=cut
249
250sub vee_cleanse {
251    my $self = shift;
252    my $text = shift || '';
253    return '<code>' . Vee::Utils::cleanse($text) . '</code>';
254}
255
256=head1 SEE ALSO
257
258L<Vee::Controller::Root>, L<Catalyst>
259
260=head1 AUTHOR
261
262Maintainer: Alex "Eevee" Munroe (C<veekun@veekun.com>)
263
264See the included F<AUTHORS> file for a full list of contributers.
265
266=head1 LICENSE
267
268See the included F<LICENSE> file.
269
270=cut
271
2721;
Note: See TracBrowser for help on using the browser.