]>
Commit | Line | Data |
---|---|---|
84fb5b46 MKG |
1 | # BEGIN BPS TAGGED BLOCK {{{ |
2 | # | |
3 | # COPYRIGHT: | |
4 | # | |
403d7b0b | 5 | # This software is Copyright (c) 1996-2013 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 | ||
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 | |
c36a7e1d | 97 | ModifyACL => 'Create, modify and delete Access Control List entries', #loc_pair |
84fb5b46 MKG |
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); | |
5b0d0914 | 221 | return undef if $obj->id && ( !$self->id || $self->id != $obj->id ); |
84fb5b46 MKG |
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 |