]>
Commit | Line | Data |
---|---|---|
84fb5b46 MKG |
1 | # BEGIN BPS TAGGED BLOCK {{{ |
2 | # | |
3 | # COPYRIGHT: | |
4 | # | |
320f0092 | 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 | ||
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 | ||
af59614d MKG |
63 | use Role::Basic 'with'; |
64 | with "RT::Record::Role::Rights"; | |
65 | ||
84fb5b46 MKG |
66 | sub Table {'Classes'} |
67 | ||
68 | =head2 Load IDENTIFIER | |
69 | ||
70 | Loads a class, either by name or by id | |
71 | ||
72 | =cut | |
73 | ||
74 | sub 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 | ||
320f0092 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 | ||
105 | Create 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 | ||
113 | sub 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 | ||
142 | sub 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 | |
161 | sub _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 | ||
174 | sub _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 |
186 | sub 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 | ||
203 | Returns collection of Queues this Class is applied to. | |
204 | Doesn't takes into account if object is applied globally. | |
205 | ||
206 | =cut | |
207 | ||
208 | sub 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 | ||
226 | Returns collection of Queues this Class is not applied to. | |
227 | ||
228 | Doesn't takes into account if object is applied globally. | |
229 | ||
230 | =cut | |
231 | ||
232 | sub 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 | ||
248 | sub _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 | ||
272 | Takes object id and returns corresponding L<RT::ObjectClass> | |
273 | record if this Class is applied to the object. Use 0 to check | |
274 | if Class is applied globally. | |
275 | ||
276 | =cut | |
277 | ||
278 | sub 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 | ||
291 | Apply this Class to a single object, to start with we support Queues | |
292 | ||
293 | Takes an object | |
294 | ||
295 | =cut | |
296 | ||
297 | ||
298 | sub 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 | ||
344 | Remove this class from a single queue object | |
345 | ||
346 | =cut | |
347 | ||
348 | sub 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 | ||
af59614d MKG |
367 | sub SubjectOverride { |
368 | my $self = shift; | |
369 | my $override = $self->FirstAttribute('SubjectOverride'); | |
370 | return $override ? $override->Content : 0; | |
371 | } | |
84fb5b46 | 372 | |
af59614d MKG |
373 | sub 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 | ||
397 | Returns 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 | ||
406 | Returns the current value of Name. | |
407 | (In the database, Name is stored as varchar(255).) | |
408 | ||
409 | ||
410 | ||
411 | =head2 SetName VALUE | |
412 | ||
413 | ||
414 | Set Name to VALUE. | |
415 | Returns (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 | ||
424 | Returns the current value of Description. | |
425 | (In the database, Description is stored as varchar(255).) | |
426 | ||
427 | ||
428 | ||
429 | =head2 SetDescription VALUE | |
430 | ||
431 | ||
432 | Set Description to VALUE. | |
433 | Returns (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 | ||
442 | Returns the current value of SortOrder. | |
443 | (In the database, SortOrder is stored as int(11).) | |
444 | ||
445 | ||
446 | ||
447 | =head2 SetSortOrder VALUE | |
448 | ||
449 | ||
450 | Set SortOrder to VALUE. | |
451 | Returns (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 | ||
460 | Returns the current value of Disabled. | |
461 | (In the database, Disabled is stored as int(2).) | |
462 | ||
463 | ||
464 | ||
465 | =head2 SetDisabled VALUE | |
466 | ||
467 | ||
468 | Set Disabled to VALUE. | |
469 | Returns (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 | ||
478 | Returns the current value of HotList. | |
479 | (In the database, HotList is stored as int(2).) | |
480 | ||
481 | ||
482 | ||
483 | =head2 SetHotList VALUE | |
484 | ||
485 | ||
486 | Set HotList to VALUE. | |
487 | Returns (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 | ||
496 | Returns 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 | ||
505 | Returns the current value of Created. | |
506 | (In the database, Created is stored as datetime.) | |
507 | ||
508 | ||
509 | =cut | |
510 | ||
511 | ||
512 | =head2 LastUpdatedBy | |
513 | ||
514 | Returns 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 | ||
523 | Returns the current value of LastUpdated. | |
524 | (In the database, LastUpdated is stored as datetime.) | |
525 | ||
526 | ||
527 | =cut | |
528 | ||
529 | ||
530 | ||
531 | sub _CoreAccessible { | |
532 | { | |
533 | ||
534 | id => | |
af59614d | 535 | {read => 1, type => 'int(11)', default => ''}, |
84fb5b46 | 536 | Name => |
af59614d | 537 | {read => 1, write => 1, type => 'varchar(255)', default => ''}, |
84fb5b46 | 538 | Description => |
af59614d | 539 | {read => 1, write => 1, type => 'varchar(255)', default => ''}, |
84fb5b46 | 540 | SortOrder => |
af59614d | 541 | {read => 1, write => 1, type => 'int(11)', default => '0'}, |
84fb5b46 | 542 | Disabled => |
af59614d | 543 | {read => 1, write => 1, type => 'int(2)', default => '0'}, |
84fb5b46 | 544 | HotList => |
af59614d | 545 | {read => 1, write => 1, type => 'int(2)', default => '0'}, |
84fb5b46 | 546 | Creator => |
af59614d | 547 | {read => 1, auto => 1, type => 'int(11)', default => '0'}, |
84fb5b46 | 548 | Created => |
af59614d | 549 | {read => 1, auto => 1, type => 'datetime', default => ''}, |
84fb5b46 | 550 | LastUpdatedBy => |
af59614d | 551 | {read => 1, auto => 1, type => 'int(11)', default => '0'}, |
84fb5b46 | 552 | LastUpdated => |
af59614d | 553 | {read => 1, auto => 1, type => 'datetime', default => ''}, |
84fb5b46 MKG |
554 | |
555 | } | |
556 | }; | |
557 | ||
af59614d MKG |
558 | sub 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 | ||
600 | sub 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 |
611 | RT::Base->_ImportOverlays(); |
612 | ||
613 | 1; | |
614 |