]> git.uio.no Git - usit-rt.git/blame - lib/RT/Record/Role/Status.pm
Putting 4.2.0 on top of 4.0.17
[usit-rt.git] / lib / RT / Record / Role / Status.pm
CommitLineData
af59614d
MKG
1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
5# This software is Copyright (c) 1996-2013 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
49use strict;
50use warnings;
51
52package RT::Record::Role::Status;
53use Role::Basic;
54use Scalar::Util qw(blessed);
55
56=head1 NAME
57
58RT::Record::Role::Status - Common methods for records which have a Status column
59
60=head1 DESCRIPTION
61
62Lifecycles are generally set on container records, and Statuses on records
63which belong to one of those containers. L<RT::Record::Role::Lifecycle>
64handles the containers with the I<Lifecycle> column. This role is for the
65records with a I<Status> column within those containers. It includes
66convenience methods for grabbing an L<RT::Lifecycle> object as well setters for
67validating I<Status> and the column which points to the container object.
68
69=head1 REQUIRES
70
71=head2 L<RT::Record::Role>
72
73=head2 LifecycleColumn
74
75Used as a role parameter. Must return a string of the column name which points
76to the container object that consumes L<RT::Record::Role::Lifecycle> (or
77conforms to it). The resulting string is used to construct two method names:
78as-is to fetch the column value and suffixed with "Obj" to fetch the object.
79
80=head2 Status
81
82A Status method which returns a lifecycle name is required. Currently
83unenforced at compile-time due to poor interactions with
84L<DBIx::SearchBuilder::Record/AUTOLOAD>. You'll hit run-time errors if this
85method isn't available in consuming classes, however.
86
87=cut
88
89with 'RT::Record::Role';
90requires 'LifecycleColumn';
91
92=head1 PROVIDES
93
94=head2 Status
95
96Returns the Status for this record, in the canonical casing.
97
98=cut
99
100sub Status {
101 my $self = shift;
102 my $value = $self->_Value( 'Status' );
103 my $lifecycle = $self->LifecycleObj;
104 return $value unless $lifecycle;
105 return $lifecycle->CanonicalCase( $value );
106}
107
108=head2 LifecycleObj
109
110Returns an L<RT::Lifecycle> object for this record's C<Lifecycle>. If called
111as a class method, returns an L<RT::Lifecycle> object which is an aggregation
112of all lifecycles of the appropriate type.
113
114=cut
115
116sub LifecycleObj {
117 my $self = shift;
118 my $obj = $self->LifecycleColumn . "Obj";
119 return $self->$obj->LifecycleObj;
120}
121
122=head2 Lifecycle
123
124Returns the L<RT::Lifecycle/Name> of this record's L</LifecycleObj>.
125
126=cut
127
128sub Lifecycle {
129 my $self = shift;
130 return $self->LifecycleObj->Name;
131}
132
133=head2 ValidateStatus
134
135Takes a status. Returns true if that status is a valid status for this record,
136otherwise returns false.
137
138=cut
139
140sub ValidateStatus {
141 my $self = shift;
142 return $self->LifecycleObj->IsValid(@_);
143}
144
145=head2 ValidateStatusChange
146
147Validates the new status with the current lifecycle. Returns a tuple of (OK,
148message).
149
150Expected to be called from this role's L</SetStatus> or the consuming class'
151equivalent.
152
153=cut
154
155sub ValidateStatusChange {
156 my $self = shift;
157 my $new = shift;
158 my $old = $self->Status;
159
160 my $lifecycle = $self->LifecycleObj;
161
162 unless ( $lifecycle->IsValid( $new ) ) {
163 return (0, $self->loc("Status '[_1]' isn't a valid status for this [_2].", $self->loc($new), $self->loc($lifecycle->Type)));
164 }
165
166 unless ( $lifecycle->IsTransition( $old => $new ) ) {
167 return (0, $self->loc("You can't change status from '[_1]' to '[_2]'.", $self->loc($old), $self->loc($new)));
168 }
169
170 my $check_right = $lifecycle->CheckRight( $old => $new );
171 unless ( $self->CurrentUser->HasRight( Right => $check_right, Object => $self ) ) {
172 return ( 0, $self->loc('Permission Denied') );
173 }
174
175 return 1;
176}
177
178=head2 SetStatus
179
180Validates the status transition before updating the Status column. This method
181may want to be overridden by a more specific method in the consuming class.
182
183=cut
184
185sub SetStatus {
186 my $self = shift;
187 my $new = shift;
188
189 my ($valid, $error) = $self->ValidateStatusChange($new);
190 return ($valid, $error) unless $valid;
191
192 return $self->_SetStatus( Status => $new );
193}
194
195=head2 _SetStatus
196
197Sets the Status column without validating the change. Intended to be used
198as-is by methods provided by the role, or overridden in the consuming class to
199take additional action. For example, L<RT::Ticket/_SetStatus> sets the Started
200and Resolved dates on the ticket as necessary.
201
202Takes a paramhash where the only required key is Status. Other keys may
203include Lifecycle and NewLifecycle when called from L</_SetLifecycleColumn>,
204which may assist consuming classes. NewLifecycle defaults to Lifecycle if not
205provided; this indicates the lifecycle isn't changing.
206
207=cut
208
209sub _SetStatus {
210 my $self = shift;
211 my %args = (
212 Status => undef,
213 Lifecycle => $self->LifecycleObj,
214 @_,
215 );
216 $args{Status} = lc $args{Status} if defined $args{Status};
217 $args{NewLifecycle} ||= $args{Lifecycle};
218
219 return $self->_Set(
220 Field => 'Status',
221 Value => $args{Status},
222 );
223}
224
225=head2 _SetLifecycleColumn
226
227Validates and updates the column named by L</LifecycleColumn>. The Status
228column is also updated if necessary (via lifecycle transition maps).
229
230On success, returns a tuple of (1, I<message>, I<new status>) where I<new
231status> is the status that was transitioned to, if any. On failure, returns
232(0, I<error message>).
233
234Takes a paramhash with keys I<Value> and (optionally) I<RequireRight>.
235I<RequireRight> is a right name which the current user must have on the new
236L</LifecycleColumn> object in order for the method to succeed.
237
238This method is expected to be used from within another method such as
239L<RT::Ticket/SetQueue>.
240
241=cut
242
243sub _SetLifecycleColumn {
244 my $self = shift;
245 my %args = @_;
246
247 my $column = $self->LifecycleColumn;
248 my $column_obj = "${column}Obj";
249
250 my $current = $self->$column_obj;
251 my $class = blessed($current);
252
253 my $new = $class->new( $self->CurrentUser );
254 $new->Load($args{Value});
255
256 return (0, $self->loc("[_1] [_2] does not exist", $self->loc($column), $args{Value}))
257 unless $new->id;
258
259 my $name = eval { $current->Name } || $current->id;
260
261 return (0, $self->loc("[_1] [_2] is disabled", $self->loc($column), $name))
262 if $new->Disabled;
263
264 return (0, $self->loc("[_1] is already set to [_2]", $self->loc($column), $name))
265 if $new->id == $current->id;
266
267 return (0, $self->loc("Permission Denied"))
268 if $args{RequireRight} and not $self->CurrentUser->HasRight(
269 Right => $args{RequireRight},
270 Object => $new,
271 );
272
273 my $new_status;
274 my $old_lifecycle = $current->LifecycleObj;
275 my $new_lifecycle = $new->LifecycleObj;
276 if ( $old_lifecycle->Name ne $new_lifecycle->Name ) {
277 unless ( $old_lifecycle->HasMoveMap( $new_lifecycle ) ) {
278 return ( 0, $self->loc("There is no mapping for statuses between lifecycle [_1] and [_2]. Contact your system administrator.", $old_lifecycle->Name, $new_lifecycle->Name) );
279 }
280 $new_status = $old_lifecycle->MoveMap( $new_lifecycle )->{ lc $self->Status };
281 return ( 0, $self->loc("Mapping between lifecycle [_1] and [_2] is incomplete. Contact your system administrator.", $old_lifecycle->Name, $new_lifecycle->Name) )
282 unless $new_status;
283 }
284
285 my ($ok, $msg) = $self->_Set( Field => $column, Value => $new->id );
286 if ($ok) {
287 if ( $new_status ) {
288 my $as_system = blessed($self)->new( RT->SystemUser );
289 $as_system->Load( $self->Id );
290 unless ( $as_system->Id ) {
291 return ( 0, $self->loc("Couldn't load copy of [_1] #[_2]", blessed($self), $self->Id) );
292 }
293
294 my ($val, $msg) = $as_system->_SetStatus(
295 Lifecycle => $old_lifecycle,
296 NewLifecycle => $new_lifecycle,
297 Status => $new_status,
298 );
299
300 if ($val) {
301 # Pick up the change made by the clone above
302 $self->Load( $self->id );
303 } else {
304 RT->Logger->error("Status change to $new_status failed on $column change: $msg");
305 undef $new_status;
306 }
307 }
308 return (1, $msg, $new_status);
309 } else {
310 return (0, $msg);
311 }
312}
313
3141;