Merge branch 'master' of https://github.com/jgoodman/Freeside
[freeside.git] / rt / lib / RT / Class.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::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           => 'Create, modify and delete Access Control List entries',         #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 && ( !$self->id || $self->id != $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->SetContextObject( $self );
279         $cfs->LimitToGlobalOrObjectId( $self->Id );
280         $cfs->LimitToLookupType( RT::Article->CustomFieldLookupType );
281         $cfs->ApplySortOrder;
282     }
283     return ($cfs);
284 }
285
286
287 =head1 AppliedTo
288
289 Returns collection of Queues this Class is applied to.
290 Doesn't takes into account if object is applied globally.
291
292 =cut
293
294 sub AppliedTo {
295     my $self = shift;
296
297     my ($res, $ocfs_alias) = $self->_AppliedTo;
298     return $res unless $res;
299
300     $res->Limit(
301         ALIAS     => $ocfs_alias,
302         FIELD     => 'id',
303         OPERATOR  => 'IS NOT',
304         VALUE     => 'NULL',
305     );
306
307     return $res;
308 }
309
310 =head1 NotAppliedTo
311
312 Returns collection of Queues this Class is not applied to.
313
314 Doesn't takes into account if object is applied globally.
315
316 =cut
317
318 sub NotAppliedTo {
319     my $self = shift;
320
321     my ($res, $ocfs_alias) = $self->_AppliedTo;
322     return $res unless $res;
323
324     $res->Limit(
325         ALIAS     => $ocfs_alias,
326         FIELD     => 'id',
327         OPERATOR  => 'IS',
328         VALUE     => 'NULL',
329     );
330
331     return $res;
332 }
333
334 sub _AppliedTo {
335     my $self = shift;
336
337     my $res = RT::Queues->new( $self->CurrentUser );
338
339     $res->OrderBy( FIELD => 'Name' );
340     my $ocfs_alias = $res->Join(
341         TYPE   => 'LEFT',
342         ALIAS1 => 'main',
343         FIELD1 => 'id',
344         TABLE2 => 'ObjectClasses',
345         FIELD2 => 'ObjectId',
346     );
347     $res->Limit(
348         LEFTJOIN => $ocfs_alias,
349         ALIAS    => $ocfs_alias,
350         FIELD    => 'Class',
351         VALUE    => $self->id,
352     );
353     return ($res, $ocfs_alias);
354 }
355
356 =head2 IsApplied
357
358 Takes object id and returns corresponding L<RT::ObjectClass>
359 record if this Class is applied to the object. Use 0 to check
360 if Class is applied globally.
361
362 =cut
363
364 sub IsApplied {
365     my $self = shift;
366     my $id = shift;
367     return unless defined $id;
368     my $oc = RT::ObjectClass->new( $self->CurrentUser );
369     $oc->LoadByCols( Class=> $self->id, ObjectId => $id,
370                      ObjectType => ( $id ? 'RT::Queue' : 'RT::System' ));
371     return undef unless $oc->id;
372     return $oc;
373 }
374
375 =head2 AddToObject OBJECT
376
377 Apply this Class to a single object, to start with we support Queues
378
379 Takes an object
380
381 =cut
382
383
384 sub AddToObject {
385     my $self  = shift;
386     my $object = shift;
387     my $id = $object->Id || 0;
388
389     unless ( $object->CurrentUserHasRight('AdminClass') ) {
390         return ( 0, $self->loc('Permission Denied') );
391     }
392
393     my $queue = RT::Queue->new( $self->CurrentUser );
394     if ( $id ) {
395         my ($ok, $msg) = $queue->Load( $id );
396         unless ($ok) {
397             return ( 0, $self->loc('Invalid Queue, unable to apply Class: [_1]',$msg ) );
398         }
399
400     }
401
402     if ( $self->IsApplied( $id ) ) {
403         return ( 0, $self->loc("Class is already applied to [_1]",$queue->Name) );
404     }
405
406     if ( $id ) {
407         # applying locally
408         return (0, $self->loc("Class is already applied Globally") )
409             if $self->IsApplied( 0 );
410     }
411     else {
412         my $applied = RT::ObjectClasses->new( $self->CurrentUser );
413         $applied->LimitToClass( $self->id );
414         while ( my $record = $applied->Next ) {
415             $record->Delete;
416         }
417     }
418
419     my $oc = RT::ObjectClass->new( $self->CurrentUser );
420     my ( $oid, $msg ) = $oc->Create(
421         ObjectId => $id, Class => $self->id,
422         ObjectType => ( $id ? 'RT::Queue' : 'RT::System' ),
423     );
424     return ( $oid, $msg );
425 }
426
427
428 =head2 RemoveFromObject OBJECT
429
430 Remove this class from a single queue object
431
432 =cut
433
434 sub RemoveFromObject {
435     my $self = shift;
436     my $object = shift;
437     my $id = $object->Id || 0;
438
439     unless ( $object->CurrentUserHasRight('AdminClass') ) {
440         return ( 0, $self->loc('Permission Denied') );
441     }
442
443     my $ocf = $self->IsApplied( $id );
444     unless ( $ocf ) {
445         return ( 0, $self->loc("This class does not apply to that object") );
446     }
447
448     # XXX: Delete doesn't return anything
449     my ( $oid, $msg ) = $ocf->Delete;
450     return ( $oid, $msg );
451 }
452
453
454
455 =head2 id
456
457 Returns the current value of id. 
458 (In the database, id is stored as int(11).)
459
460
461 =cut
462
463
464 =head2 Name
465
466 Returns the current value of Name. 
467 (In the database, Name is stored as varchar(255).)
468
469
470
471 =head2 SetName VALUE
472
473
474 Set Name to VALUE. 
475 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
476 (In the database, Name will be stored as a varchar(255).)
477
478
479 =cut
480
481
482 =head2 Description
483
484 Returns the current value of Description. 
485 (In the database, Description is stored as varchar(255).)
486
487
488
489 =head2 SetDescription VALUE
490
491
492 Set Description to VALUE. 
493 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
494 (In the database, Description will be stored as a varchar(255).)
495
496
497 =cut
498
499
500 =head2 SortOrder
501
502 Returns the current value of SortOrder. 
503 (In the database, SortOrder is stored as int(11).)
504
505
506
507 =head2 SetSortOrder VALUE
508
509
510 Set SortOrder to VALUE. 
511 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
512 (In the database, SortOrder will be stored as a int(11).)
513
514
515 =cut
516
517
518 =head2 Disabled
519
520 Returns the current value of Disabled. 
521 (In the database, Disabled is stored as int(2).)
522
523
524
525 =head2 SetDisabled VALUE
526
527
528 Set Disabled to VALUE. 
529 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
530 (In the database, Disabled will be stored as a int(2).)
531
532
533 =cut
534
535
536 =head2 HotList
537
538 Returns the current value of HotList. 
539 (In the database, HotList is stored as int(2).)
540
541
542
543 =head2 SetHotList VALUE
544
545
546 Set HotList to VALUE. 
547 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
548 (In the database, HotList will be stored as a int(2).)
549
550
551 =cut
552
553
554 =head2 Creator
555
556 Returns the current value of Creator. 
557 (In the database, Creator is stored as int(11).)
558
559
560 =cut
561
562
563 =head2 Created
564
565 Returns the current value of Created. 
566 (In the database, Created is stored as datetime.)
567
568
569 =cut
570
571
572 =head2 LastUpdatedBy
573
574 Returns the current value of LastUpdatedBy. 
575 (In the database, LastUpdatedBy is stored as int(11).)
576
577
578 =cut
579
580
581 =head2 LastUpdated
582
583 Returns the current value of LastUpdated. 
584 (In the database, LastUpdated is stored as datetime.)
585
586
587 =cut
588
589
590
591 sub _CoreAccessible {
592     {
593      
594         id =>
595                 {read => 1, type => 'int(11)', default => ''},
596         Name => 
597                 {read => 1, write => 1, type => 'varchar(255)', default => ''},
598         Description => 
599                 {read => 1, write => 1, type => 'varchar(255)', default => ''},
600         SortOrder => 
601                 {read => 1, write => 1, type => 'int(11)', default => '0'},
602         Disabled => 
603                 {read => 1, write => 1, type => 'int(2)', default => '0'},
604         HotList => 
605                 {read => 1, write => 1, type => 'int(2)', default => '0'},
606         Creator => 
607                 {read => 1, auto => 1, type => 'int(11)', default => '0'},
608         Created => 
609                 {read => 1, auto => 1, type => 'datetime', default => ''},
610         LastUpdatedBy => 
611                 {read => 1, auto => 1, type => 'int(11)', default => '0'},
612         LastUpdated => 
613                 {read => 1, auto => 1, type => 'datetime', default => ''},
614
615  }
616 };
617
618 RT::Base->_ImportOverlays();
619
620 1;
621