Upgrade to 4.2.8
[usit-rt.git] / lib / RT / Class.pm
CommitLineData
84fb5b46
MKG
1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
3ffc5f4f 5# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
84fb5b46
MKG
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
49package RT::Class;
50
51use strict;
52use warnings;
53use base 'RT::Record';
54
55
56use RT::System;
57use RT::CustomFields;
58use RT::ACL;
59use RT::Articles;
60use RT::ObjectClass;
61use RT::ObjectClasses;
62
3ffc5f4f
MKG
63use Role::Basic 'with';
64with "RT::Record::Role::Rights";
65
84fb5b46
MKG
66sub Table {'Classes'}
67
68=head2 Load IDENTIFIER
69
70Loads a class, either by name or by id
71
72=cut
73
74sub Load {
75 my $self = shift;
76 my $id = shift ;
77
78 return unless $id;
79 if ( $id =~ /^\d+$/ ) {
80 $self->SUPER::Load($id);
81 }
82 else {
83 $self->LoadByCols( Name => $id );
84 }
85}
86
3ffc5f4f
MKG
87__PACKAGE__->AddRight( Staff => SeeClass => 'See that this class exists'); # loc
88__PACKAGE__->AddRight( Staff => CreateArticle => 'Create articles in this class'); # loc
89__PACKAGE__->AddRight( General => ShowArticle => 'See articles in this class'); # loc
90__PACKAGE__->AddRight( Staff => ShowArticleHistory => 'See changes to articles in this class'); # loc
91__PACKAGE__->AddRight( General => SeeCustomField => 'View custom field values' ); # loc
92__PACKAGE__->AddRight( Staff => ModifyArticle => 'Modify or delete articles in this class'); # loc
93__PACKAGE__->AddRight( Staff => ModifyArticleTopics => 'Modify topics for articles in this class'); # loc
94__PACKAGE__->AddRight( Staff => ModifyCustomField => 'Modify custom field values' ); # loc
95__PACKAGE__->AddRight( Admin => AdminClass => 'Modify metadata and custom fields for this class'); # loc
96__PACKAGE__->AddRight( Admin => AdminTopics => 'Modify topic hierarchy associated with this class'); # loc
97__PACKAGE__->AddRight( Admin => ShowACL => 'Display Access Control List'); # loc
98__PACKAGE__->AddRight( Admin => ModifyACL => 'Create, modify and delete Access Control List entries'); # loc
99__PACKAGE__->AddRight( Staff => DeleteArticle => 'Delete articles in this class'); # loc
84fb5b46
MKG
100
101# {{{ Create
102
103=head2 Create PARAMHASH
104
105Create takes a hash of values and creates a row in the database:
106
107 varchar(255) 'Name'.
108 varchar(255) 'Description'.
109 int(11) 'SortOrder'.
110
111=cut
112
113sub Create {
114 my $self = shift;
115 my %args = (
116 Name => '',
117 Description => '',
118 SortOrder => '0',
119 HotList => 0,
120 @_
121 );
122
123 unless (
124 $self->CurrentUser->HasRight(
125 Right => 'AdminClass',
126 Object => $RT::System
127 )
128 )
129 {
130 return ( 0, $self->loc('Permission Denied') );
131 }
132
133 $self->SUPER::Create(
134 Name => $args{'Name'},
135 Description => $args{'Description'},
136 SortOrder => $args{'SortOrder'},
137 HotList => $args{'HotList'},
138 );
139
140}
141
142sub ValidateName {
143 my $self = shift;
144 my $newval = shift;
145
146 return undef unless ($newval);
147 my $obj = RT::Class->new($RT::SystemUser);
148 $obj->Load($newval);
5b0d0914 149 return undef if $obj->id && ( !$self->id || $self->id != $obj->id );
84fb5b46
MKG
150 return $self->SUPER::ValidateName($newval);
151
152}
153
154# }}}
155
156# }}}
157
158# {{{ ACCESS CONTROL
159
160# {{{ sub _Set
161sub _Set {
162 my $self = shift;
163
164 unless ( $self->CurrentUserHasRight('AdminClass') ) {
165 return ( 0, $self->loc('Permission Denied') );
166 }
167 return ( $self->SUPER::_Set(@_) );
168}
169
170# }}}
171
172# {{{ sub _Value
173
174sub _Value {
175 my $self = shift;
176
177 unless ( $self->CurrentUserHasRight('SeeClass') ) {
178 return (undef);
179 }
180
181 return ( $self->__Value(@_) );
182}
183
184# }}}
185
84fb5b46
MKG
186sub ArticleCustomFields {
187 my $self = shift;
188
189
190 my $cfs = RT::CustomFields->new( $self->CurrentUser );
191 if ( $self->CurrentUserHasRight('SeeClass') ) {
192 $cfs->SetContextObject( $self );
193 $cfs->LimitToGlobalOrObjectId( $self->Id );
194 $cfs->LimitToLookupType( RT::Article->CustomFieldLookupType );
195 $cfs->ApplySortOrder;
196 }
197 return ($cfs);
198}
199
200
201=head1 AppliedTo
202
203Returns collection of Queues this Class is applied to.
204Doesn't takes into account if object is applied globally.
205
206=cut
207
208sub AppliedTo {
209 my $self = shift;
210
211 my ($res, $ocfs_alias) = $self->_AppliedTo;
212 return $res unless $res;
213
214 $res->Limit(
215 ALIAS => $ocfs_alias,
216 FIELD => 'id',
217 OPERATOR => 'IS NOT',
218 VALUE => 'NULL',
219 );
220
221 return $res;
222}
223
224=head1 NotAppliedTo
225
226Returns collection of Queues this Class is not applied to.
227
228Doesn't takes into account if object is applied globally.
229
230=cut
231
232sub NotAppliedTo {
233 my $self = shift;
234
235 my ($res, $ocfs_alias) = $self->_AppliedTo;
236 return $res unless $res;
237
238 $res->Limit(
239 ALIAS => $ocfs_alias,
240 FIELD => 'id',
241 OPERATOR => 'IS',
242 VALUE => 'NULL',
243 );
244
245 return $res;
246}
247
248sub _AppliedTo {
249 my $self = shift;
250
251 my $res = RT::Queues->new( $self->CurrentUser );
252
253 $res->OrderBy( FIELD => 'Name' );
254 my $ocfs_alias = $res->Join(
255 TYPE => 'LEFT',
256 ALIAS1 => 'main',
257 FIELD1 => 'id',
258 TABLE2 => 'ObjectClasses',
259 FIELD2 => 'ObjectId',
260 );
261 $res->Limit(
262 LEFTJOIN => $ocfs_alias,
263 ALIAS => $ocfs_alias,
264 FIELD => 'Class',
265 VALUE => $self->id,
266 );
267 return ($res, $ocfs_alias);
268}
269
270=head2 IsApplied
271
272Takes object id and returns corresponding L<RT::ObjectClass>
273record if this Class is applied to the object. Use 0 to check
274if Class is applied globally.
275
276=cut
277
278sub IsApplied {
279 my $self = shift;
280 my $id = shift;
281 return unless defined $id;
282 my $oc = RT::ObjectClass->new( $self->CurrentUser );
283 $oc->LoadByCols( Class=> $self->id, ObjectId => $id,
284 ObjectType => ( $id ? 'RT::Queue' : 'RT::System' ));
285 return undef unless $oc->id;
286 return $oc;
287}
288
289=head2 AddToObject OBJECT
290
291Apply this Class to a single object, to start with we support Queues
292
293Takes an object
294
295=cut
296
297
298sub AddToObject {
299 my $self = shift;
300 my $object = shift;
301 my $id = $object->Id || 0;
302
303 unless ( $object->CurrentUserHasRight('AdminClass') ) {
304 return ( 0, $self->loc('Permission Denied') );
305 }
306
307 my $queue = RT::Queue->new( $self->CurrentUser );
308 if ( $id ) {
309 my ($ok, $msg) = $queue->Load( $id );
310 unless ($ok) {
311 return ( 0, $self->loc('Invalid Queue, unable to apply Class: [_1]',$msg ) );
312 }
313
314 }
315
316 if ( $self->IsApplied( $id ) ) {
317 return ( 0, $self->loc("Class is already applied to [_1]",$queue->Name) );
318 }
319
320 if ( $id ) {
321 # applying locally
322 return (0, $self->loc("Class is already applied Globally") )
323 if $self->IsApplied( 0 );
324 }
325 else {
326 my $applied = RT::ObjectClasses->new( $self->CurrentUser );
327 $applied->LimitToClass( $self->id );
328 while ( my $record = $applied->Next ) {
329 $record->Delete;
330 }
331 }
332
333 my $oc = RT::ObjectClass->new( $self->CurrentUser );
334 my ( $oid, $msg ) = $oc->Create(
335 ObjectId => $id, Class => $self->id,
336 ObjectType => ( $id ? 'RT::Queue' : 'RT::System' ),
337 );
338 return ( $oid, $msg );
339}
340
341
342=head2 RemoveFromObject OBJECT
343
344Remove this class from a single queue object
345
346=cut
347
348sub RemoveFromObject {
349 my $self = shift;
350 my $object = shift;
351 my $id = $object->Id || 0;
352
353 unless ( $object->CurrentUserHasRight('AdminClass') ) {
354 return ( 0, $self->loc('Permission Denied') );
355 }
356
357 my $ocf = $self->IsApplied( $id );
358 unless ( $ocf ) {
359 return ( 0, $self->loc("This class does not apply to that object") );
360 }
361
362 # XXX: Delete doesn't return anything
363 my ( $oid, $msg ) = $ocf->Delete;
364 return ( $oid, $msg );
365}
366
3ffc5f4f
MKG
367sub SubjectOverride {
368 my $self = shift;
369 my $override = $self->FirstAttribute('SubjectOverride');
370 return $override ? $override->Content : 0;
371}
84fb5b46 372
3ffc5f4f
MKG
373sub SetSubjectOverride {
374 my $self = shift;
375 my $override = shift;
376
377 if ( $override == $self->SubjectOverride ) {
378 return (0, "SubjectOverride is already set to that");
379 }
380
381 my $cf = RT::CustomField->new($self->CurrentUser);
382 $cf->Load($override);
383
384 if ( $override ) {
385 my ($ok, $msg) = $self->SetAttribute( Name => 'SubjectOverride', Content => $override );
386 return ($ok, $ok ? $self->loc('Added Subject Override: [_1]', $cf->Name) :
387 $self->loc('Unable to add Subject Override: [_1] [_2]', $cf->Name, $msg));
388 } else {
389 my ($ok, $msg) = $self->DeleteAttribute('SubjectOverride');
390 return ($ok, $ok ? $self->loc('Removed Subject Override') :
391 $self->loc('Unable to add Subject Override: [_1] [_2]', $cf->Name, $msg));
392 }
393}
84fb5b46
MKG
394
395=head2 id
396
397Returns the current value of id.
398(In the database, id is stored as int(11).)
399
400
401=cut
402
403
404=head2 Name
405
406Returns the current value of Name.
407(In the database, Name is stored as varchar(255).)
408
409
410
411=head2 SetName VALUE
412
413
414Set Name to VALUE.
415Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
416(In the database, Name will be stored as a varchar(255).)
417
418
419=cut
420
421
422=head2 Description
423
424Returns the current value of Description.
425(In the database, Description is stored as varchar(255).)
426
427
428
429=head2 SetDescription VALUE
430
431
432Set Description to VALUE.
433Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
434(In the database, Description will be stored as a varchar(255).)
435
436
437=cut
438
439
440=head2 SortOrder
441
442Returns the current value of SortOrder.
443(In the database, SortOrder is stored as int(11).)
444
445
446
447=head2 SetSortOrder VALUE
448
449
450Set SortOrder to VALUE.
451Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
452(In the database, SortOrder will be stored as a int(11).)
453
454
455=cut
456
457
458=head2 Disabled
459
460Returns the current value of Disabled.
461(In the database, Disabled is stored as int(2).)
462
463
464
465=head2 SetDisabled VALUE
466
467
468Set Disabled to VALUE.
469Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
470(In the database, Disabled will be stored as a int(2).)
471
472
473=cut
474
475
476=head2 HotList
477
478Returns the current value of HotList.
479(In the database, HotList is stored as int(2).)
480
481
482
483=head2 SetHotList VALUE
484
485
486Set HotList to VALUE.
487Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
488(In the database, HotList will be stored as a int(2).)
489
490
491=cut
492
493
494=head2 Creator
495
496Returns the current value of Creator.
497(In the database, Creator is stored as int(11).)
498
499
500=cut
501
502
503=head2 Created
504
505Returns the current value of Created.
506(In the database, Created is stored as datetime.)
507
508
509=cut
510
511
512=head2 LastUpdatedBy
513
514Returns the current value of LastUpdatedBy.
515(In the database, LastUpdatedBy is stored as int(11).)
516
517
518=cut
519
520
521=head2 LastUpdated
522
523Returns the current value of LastUpdated.
524(In the database, LastUpdated is stored as datetime.)
525
526
527=cut
528
529
530
531sub _CoreAccessible {
532 {
533
534 id =>
3ffc5f4f 535 {read => 1, type => 'int(11)', default => ''},
84fb5b46 536 Name =>
3ffc5f4f 537 {read => 1, write => 1, type => 'varchar(255)', default => ''},
84fb5b46 538 Description =>
3ffc5f4f 539 {read => 1, write => 1, type => 'varchar(255)', default => ''},
84fb5b46 540 SortOrder =>
3ffc5f4f 541 {read => 1, write => 1, type => 'int(11)', default => '0'},
84fb5b46 542 Disabled =>
3ffc5f4f 543 {read => 1, write => 1, type => 'int(2)', default => '0'},
84fb5b46 544 HotList =>
3ffc5f4f 545 {read => 1, write => 1, type => 'int(2)', default => '0'},
84fb5b46 546 Creator =>
3ffc5f4f 547 {read => 1, auto => 1, type => 'int(11)', default => '0'},
84fb5b46 548 Created =>
3ffc5f4f 549 {read => 1, auto => 1, type => 'datetime', default => ''},
84fb5b46 550 LastUpdatedBy =>
3ffc5f4f 551 {read => 1, auto => 1, type => 'int(11)', default => '0'},
84fb5b46 552 LastUpdated =>
3ffc5f4f 553 {read => 1, auto => 1, type => 'datetime', default => ''},
84fb5b46
MKG
554
555 }
556};
557
3ffc5f4f
MKG
558sub FindDependencies {
559 my $self = shift;
560 my ($walker, $deps) = @_;
561
562 $self->SUPER::FindDependencies($walker, $deps);
563
564 my $articles = RT::Articles->new( $self->CurrentUser );
565 $articles->Limit( FIELD => "Class", VALUE => $self->Id );
566 $deps->Add( in => $articles );
567
568 my $topics = RT::Topics->new( $self->CurrentUser );
569 $topics->LimitToObject( $self );
570 $deps->Add( in => $topics );
571
572 my $objectclasses = RT::ObjectClasses->new( $self->CurrentUser );
573 $objectclasses->LimitToClass( $self->Id );
574 $deps->Add( in => $objectclasses );
575
576 # Custom Fields on things _in_ this class (CFs on the class itself
577 # have already been dealt with)
578 my $ocfs = RT::ObjectCustomFields->new( $self->CurrentUser );
579 $ocfs->Limit( FIELD => 'ObjectId',
580 OPERATOR => '=',
581 VALUE => $self->id,
582 ENTRYAGGREGATOR => 'OR' );
583 $ocfs->Limit( FIELD => 'ObjectId',
584 OPERATOR => '=',
585 VALUE => 0,
586 ENTRYAGGREGATOR => 'OR' );
587 my $cfs = $ocfs->Join(
588 ALIAS1 => 'main',
589 FIELD1 => 'CustomField',
590 TABLE2 => 'CustomFields',
591 FIELD2 => 'id',
592 );
593 $ocfs->Limit( ALIAS => $cfs,
594 FIELD => 'LookupType',
595 OPERATOR => 'STARTSWITH',
596 VALUE => 'RT::Class-' );
597 $deps->Add( in => $ocfs );
598}
599
600sub PreInflate {
601 my $class = shift;
602 my ($importer, $uid, $data) = @_;
603
604 $class->SUPER::PreInflate( $importer, $uid, $data );
605
606 return if $importer->MergeBy( "Name", $class, $uid, $data );
607
608 return 1;
609}
610
84fb5b46
MKG
611RT::Base->_ImportOverlays();
612
6131;
614