1acf891f2de6280de57ddcc4a3eacf42944a54d2
[freeside.git] / rt / lib / RT / ObjectCustomField.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::ObjectCustomField;
50
51 use strict;
52 use warnings;
53
54
55 use RT::CustomField;
56 use base 'RT::Record';
57
58 sub Table {'ObjectCustomFields'}
59
60
61
62
63
64
65 sub Create {
66     my $self = shift;
67     my %args = (
68         CustomField => 0,
69         ObjectId    => 0,
70         SortOrder   => undef,
71         @_
72     );
73
74     my $cf = $self->CustomFieldObj( $args{'CustomField'} );
75     unless ( $cf->id ) {
76         $RT::Logger->error("Couldn't load '$args{'CustomField'}' custom field");
77         return 0;
78     }
79
80     #XXX: Where is ACL check for 'AssignCustomFields'?
81
82     my $ObjectCFs = RT::ObjectCustomFields->new($self->CurrentUser);
83     $ObjectCFs->LimitToObjectId( $args{'ObjectId'} );
84     $ObjectCFs->LimitToCustomField( $cf->id );
85     $ObjectCFs->LimitToLookupType( $cf->LookupType );
86     if ( my $first = $ObjectCFs->First ) {
87         $self->Load( $first->id );
88         return $first->id;
89     }
90
91     unless ( defined $args{'SortOrder'} ) {
92         my $ObjectCFs = RT::ObjectCustomFields->new( RT->SystemUser );
93         $ObjectCFs->LimitToObjectId( $args{'ObjectId'} );
94         $ObjectCFs->LimitToObjectId( 0 ) if $args{'ObjectId'};
95         $ObjectCFs->LimitToLookupType( $cf->LookupType );
96         $ObjectCFs->OrderBy( FIELD => 'SortOrder', ORDER => 'DESC' );
97         if ( my $first = $ObjectCFs->First ) {
98             $args{'SortOrder'} = $first->SortOrder + 1;
99         } else {
100             $args{'SortOrder'} = 0;
101         }
102     }
103
104     return $self->SUPER::Create(
105         CustomField => $args{'CustomField'},
106         ObjectId    => $args{'ObjectId'},
107         SortOrder   => $args{'SortOrder'},
108     );
109 }
110
111 sub Delete {
112     my $self = shift;
113
114     my $ObjectCFs = RT::ObjectCustomFields->new($self->CurrentUser);
115     $ObjectCFs->LimitToObjectId($self->ObjectId);
116     $ObjectCFs->LimitToLookupType($self->CustomFieldObj->LookupType);
117
118     # Move everything below us up
119     my $sort_order = $self->SortOrder;
120     while (my $OCF = $ObjectCFs->Next) {
121         my $this_order = $OCF->SortOrder;
122         next if $this_order <= $sort_order; 
123         $OCF->SetSortOrder($this_order - 1);
124     }
125
126     $self->SUPER::Delete;
127 }
128
129
130 =head2 CustomFieldObj
131
132 Returns the CustomField Object which has the id returned by CustomField
133
134
135 =cut
136
137 sub CustomFieldObj {
138     my $self = shift;
139     my $id = shift || $self->CustomField;
140
141     # To find out the proper context object to load the CF with, we need
142     # data from the CF -- namely, the record class.  Go find that as the
143     # system user first.
144     my $system_CF = RT::CustomField->new( RT->SystemUser );
145     $system_CF->Load( $id );
146     my $class = $system_CF->RecordClassFromLookupType;
147
148     my $obj = $class->new( $self->CurrentUser );
149     $obj->Load( $self->ObjectId );
150
151     my $CF = RT::CustomField->new( $self->CurrentUser );
152     $CF->SetContextObject( $obj );
153     $CF->Load( $id );
154     return $CF;
155 }
156
157 =head2 Sorting custom fields applications
158
159 Custom fields sorted on multiple layers. First of all custom
160 fields with different lookup type are sorted independently. All
161 global custom fields have fixed order for all objects, but you
162 can insert object specific custom fields between them. Object
163 specific custom fields can be applied to several objects and
164 be on different place. For example you have GCF1, GCF2, LCF1,
165 LCF2 and LCF3 that applies to tickets. You can place GCF2
166 above GCF1, but they will be in the same order in all queues.
167 However, LCF1 and other local can be placed at any place
168 for particular queue: above global, between them or below.
169
170 =head3 MoveUp
171
172 Moves custom field up. See </Sorting custom fields applications>.
173
174 =cut
175
176 sub MoveUp {
177     my $self = shift;
178
179     my $ocfs = RT::ObjectCustomFields->new( $self->CurrentUser );
180
181     my $oid = $self->ObjectId;
182     $ocfs->LimitToObjectId( $oid );
183     if ( $oid ) {
184         $ocfs->LimitToObjectId( 0 );
185     }
186
187     my $cf = $self->CustomFieldObj;
188     $ocfs->LimitToLookupType( $cf->LookupType );
189
190     $ocfs->Limit( FIELD => 'SortOrder', OPERATOR => '<', VALUE => $self->SortOrder );
191     $ocfs->OrderByCols( { FIELD => 'SortOrder', ORDER => 'DESC' } );
192
193     my @above = ($ocfs->Next, $ocfs->Next);
194     unless ($above[0]) {
195         return (0, "Can not move up. It's already at the top");
196     }
197
198     my $new_sort_order;
199     if ( $above[0]->ObjectId == $self->ObjectId ) {
200         $new_sort_order = $above[0]->SortOrder;
201         my ($status, $msg) = $above[0]->SetSortOrder( $self->SortOrder );
202         unless ( $status ) {
203             return (0, "Couldn't move custom field");
204         }
205     }
206     elsif ( $above[1] && $above[0]->SortOrder == $above[1]->SortOrder + 1 ) {
207         my $move_ocfs = RT::ObjectCustomFields->new( RT->SystemUser );
208         $move_ocfs->LimitToLookupType( $cf->LookupType );
209         $move_ocfs->Limit(
210             FIELD => 'SortOrder',
211             OPERATOR => '>=',
212             VALUE => $above[0]->SortOrder,
213         );
214         $move_ocfs->OrderByCols( { FIELD => 'SortOrder', ORDER => 'DESC' } );
215         while ( my $record = $move_ocfs->Next ) {
216             my ($status, $msg) = $record->SetSortOrder( $record->SortOrder + 1 );
217             unless ( $status ) {
218                 return (0, "Couldn't move custom field");
219             }
220         }
221         $new_sort_order = $above[0]->SortOrder;
222     } else {
223         $new_sort_order = $above[0]->SortOrder - 1;
224     }
225
226     my ($status, $msg) = $self->SetSortOrder( $new_sort_order );
227     unless ( $status ) {
228         return (0, "Couldn't move custom field");
229     }
230
231     return (1,"Moved custom field up");
232 }
233
234 =head3 MoveDown
235
236 Moves custom field down. See </Sorting custom fields applications>.
237
238 =cut
239
240 sub MoveDown {
241     my $self = shift;
242
243     my $ocfs = RT::ObjectCustomFields->new( $self->CurrentUser );
244
245     my $oid = $self->ObjectId;
246     $ocfs->LimitToObjectId( $oid );
247     if ( $oid ) {
248         $ocfs->LimitToObjectId( 0 );
249     }
250
251     my $cf = $self->CustomFieldObj;
252     $ocfs->LimitToLookupType( $cf->LookupType );
253
254     $ocfs->Limit( FIELD => 'SortOrder', OPERATOR => '>', VALUE => $self->SortOrder );
255     $ocfs->OrderByCols( { FIELD => 'SortOrder', ORDER => 'ASC' } );
256
257     my @below = ($ocfs->Next, $ocfs->Next);
258     unless ($below[0]) {
259         return (0, "Can not move down. It's already at the bottom");
260     }
261
262     my $new_sort_order;
263     if ( $below[0]->ObjectId == $self->ObjectId ) {
264         $new_sort_order = $below[0]->SortOrder;
265         my ($status, $msg) = $below[0]->SetSortOrder( $self->SortOrder );
266         unless ( $status ) {
267             return (0, "Couldn't move custom field");
268         }
269     }
270     elsif ( $below[1] && $below[0]->SortOrder + 1 == $below[1]->SortOrder ) {
271         my $move_ocfs = RT::ObjectCustomFields->new( RT->SystemUser );
272         $move_ocfs->LimitToLookupType( $cf->LookupType );
273         $move_ocfs->Limit(
274             FIELD => 'SortOrder',
275             OPERATOR => '<=',
276             VALUE => $below[0]->SortOrder,
277         );
278         $move_ocfs->OrderByCols( { FIELD => 'SortOrder', ORDER => 'ASC' } );
279         while ( my $record = $move_ocfs->Next ) {
280             my ($status, $msg) = $record->SetSortOrder( $record->SortOrder - 1 );
281             unless ( $status ) {
282                 return (0, "Couldn't move custom field");
283             }
284         }
285         $new_sort_order = $below[0]->SortOrder;
286     } else {
287         $new_sort_order = $below[0]->SortOrder + 1;
288     }
289
290     my ($status, $msg) = $self->SetSortOrder( $new_sort_order );
291     unless ( $status ) {
292         return (0, "Couldn't move custom field");
293     }
294
295     return (1,"Moved custom field down");
296 }
297
298
299 =head2 id
300
301 Returns the current value of id.
302 (In the database, id is stored as int(11).)
303
304
305 =cut
306
307
308 =head2 CustomField
309
310 Returns the current value of CustomField.
311 (In the database, CustomField is stored as int(11).)
312
313
314
315 =head2 SetCustomField VALUE
316
317
318 Set CustomField to VALUE.
319 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
320 (In the database, CustomField will be stored as a int(11).)
321
322
323 =cut
324
325
326 =head2 ObjectId
327
328 Returns the current value of ObjectId.
329 (In the database, ObjectId is stored as int(11).)
330
331
332
333 =head2 SetObjectId VALUE
334
335
336 Set ObjectId to VALUE.
337 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
338 (In the database, ObjectId will be stored as a int(11).)
339
340
341 =cut
342
343
344 =head2 SortOrder
345
346 Returns the current value of SortOrder.
347 (In the database, SortOrder is stored as int(11).)
348
349
350
351 =head2 SetSortOrder VALUE
352
353
354 Set SortOrder to VALUE.
355 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
356 (In the database, SortOrder will be stored as a int(11).)
357
358
359 =cut
360
361
362 =head2 Creator
363
364 Returns the current value of Creator.
365 (In the database, Creator is stored as int(11).)
366
367
368 =cut
369
370
371 =head2 Created
372
373 Returns the current value of Created.
374 (In the database, Created is stored as datetime.)
375
376
377 =cut
378
379
380 =head2 LastUpdatedBy
381
382 Returns the current value of LastUpdatedBy.
383 (In the database, LastUpdatedBy is stored as int(11).)
384
385
386 =cut
387
388
389 =head2 LastUpdated
390
391 Returns the current value of LastUpdated.
392 (In the database, LastUpdated is stored as datetime.)
393
394
395 =cut
396
397
398
399 sub _CoreAccessible {
400     {
401
402         id =>
403                 {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
404         CustomField =>
405                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
406         ObjectId =>
407                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
408         SortOrder =>
409                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
410         Creator =>
411                 {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
412         Created =>
413                 {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
414         LastUpdatedBy =>
415                 {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
416         LastUpdated =>
417                 {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
418
419  }
420 };
421
422 RT::Base->_ImportOverlays();
423
424 1;