Merge branch 'master' of https://github.com/jgoodman/Freeside
[freeside.git] / rt / lib / RT / Interface / Web / Request.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
6 #                                          <sales@bestpractical.com>
7 #
8 # (Except where explicitly superseded by other copyright notices)
9 #
10 #
11 # LICENSE:
12 #
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
16 # from www.gnu.org.
17 #
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 # General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
28 #
29 #
30 # CONTRIBUTION SUBMISSION POLICY:
31 #
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
37 #
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
46 #
47 # END BPS TAGGED BLOCK }}}
48
49 package RT::Interface::Web::Request;
50
51 use strict;
52 use warnings;
53
54 our $VERSION = '0.30';
55 use HTML::Mason::PSGIHandler;
56 use base qw(HTML::Mason::Request::PSGI);
57 use Params::Validate qw(:all);
58
59 sub new {
60     my $class = shift;
61     $class->valid_params( %{ $class->valid_params },cgi_request => { type => OBJECT, optional => 1 } );
62     return $class->SUPER::new(@_);
63 }
64
65
66 =head2 callback
67
68 Method replaces deprecated component C<Element/Callback>.
69
70 Takes hash with optional C<CallbackPage>, C<CallbackName>
71 and C<CallbackOnce> arguments, other arguments are passed
72 throught to callback components.
73
74 =over 4
75
76 =item CallbackPage
77
78 Page path relative to the root, leading slash is mandatory.
79 By default is equal to path of the caller component.
80
81 =item CallbackName
82
83 Name of the callback. C<Default> is used unless specified.
84
85 =item CallbackOnce
86
87 By default is false, otherwise runs callbacks only once per
88 process of the server. Such callbacks can be used to fill
89 structures.
90
91 =back
92
93 Searches for callback components in
94 F<< /Callbacks/<any dir>/CallbackPage/CallbackName >>, for
95 example F</Callbacks/MyExtension/autohandler/Default> would
96 be called as default callback for F</autohandler>.
97
98 =cut
99
100 {
101 my %cache = ();
102 my %called = ();
103 sub callback {
104     my ($self, %args) = @_;
105
106     my $name = delete $args{'CallbackName'} || 'Default';
107     my $page = delete $args{'CallbackPage'} || $self->callers(0)->path;
108     unless ( $page ) {
109         $RT::Logger->error("Couldn't get a page name for callbacks");
110         return;
111     }
112
113     my $CacheKey = "$page--$name";
114     return 1 if delete $args{'CallbackOnce'} && $called{ $CacheKey };
115     $called{ $CacheKey } = 1;
116
117     my $callbacks = $cache{ $CacheKey };
118     unless ( $callbacks ) {
119         $callbacks = [];
120         my $path  = "/Callbacks/*$page/$name";
121         my @roots = RT::Interface::Web->ComponentRoots;
122         my %seen;
123         @$callbacks = (
124             grep defined && length,
125             # Skip backup files, files without a leading package name,
126             # and files we've already seen
127             grep !$seen{$_}++ && !m{/\.} && !m{~$} && m{^/Callbacks/[^/]+\Q$page/$name\E$},
128             map { sort $self->interp->resolver->glob_path($path, $_) }
129             @roots
130         );
131         foreach my $comp (keys %seen) {
132             next unless $seen{$comp} > 1;
133             $RT::Logger->error("Found more than one occurrence of the $comp callback.  This may cause only one of the callbacks to run.  Look for the duplicate Callback in your @roots");
134         }
135
136         $cache{ $CacheKey } = $callbacks unless RT->Config->Get('DevelMode');
137     }
138
139     my @rv;
140     foreach my $cb ( @$callbacks ) {
141         push @rv, scalar $self->comp( $cb, %args );
142     }
143     return @rv;
144 }
145
146 sub clear_callback_cache {
147     %cache = %called = ();
148 }
149 }
150
151 =head2 request_path
152
153 Returns path of the request.
154
155 Very close to C<< $m->request_comp->path >>, but if called in a dhandler returns
156 path of the request without dhandler name, but with dhandler arguments instead.
157
158 =cut
159
160 sub request_path {
161     my $self = shift;
162
163     my $path = $self->request_comp->path;
164     # disabled dhandlers, not RT case, but anyway
165     return $path unless my $dh_name = $self->dhandler_name;
166     # not a dhandler
167     return $path unless substr($path, -length("/$dh_name")) eq "/$dh_name";
168     substr($path, -length $dh_name) = $self->dhandler_arg;
169     return $path;
170 }
171
172 =head2 abort
173
174 Logs any recorded SQL statements for this request before calling the standard
175 abort.
176
177 =cut
178
179 sub abort {
180     my $self = shift;
181     RT::Interface::Web::LogRecordedSQLStatements(
182         RequestData => {
183             Path => $self->request_path,
184         },
185     );
186     return $self->SUPER::abort(@_);
187 }
188
189 1;