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