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