19110e18b654b6db03a6bda96c62fcb0c0b66f85
[freeside.git] / httemplate / edit / process / elements / process.html
1 <%doc>
2
3 Example:
4
5  <& elements/process.html,
6
7    ###
8    # required
9    ###
10
11   'table' => 'tablename',
12
13    #? 'primary_key' => #required when the dbdef doesn't know...???
14    #? 'fields' => []   #""
15
16    ###
17    # optional
18    ###
19
20    'viewall_dir'  => '', #'search' or 'browse', defaults to 'search'
21    'viewall_ext'  => 'html', #'cgi' or 'html', defaults to 'html'
22    OR
23    'redirect'     => 'view/table.cgi?', # value of primary key is appended
24                                         # (string or coderef returning a string)
25    OR
26    'popup_reload' => 'Momentary success message', #will reload parent window
27
28    'error_redirect' => popurl(2).'edit/table.cgi?', #query string appended
29
30    'edit_ext' => 'html', #defaults to 'html', you might want 'cgi' while the
31                          #naming is still inconsistent
32
33    'copy_on_empty'  => [ 'old_field_name', 'another_old_field', ... ],
34
35    'clear_on_error' => [ 'form_field1', 'form_field2', ... ],
36
37    ## message will be returned to redirect page as cookie and read by header.html
38    ## append =status, =warning, =error to change color of message box.  Default is status
39    ## status is green, warning is yellow, and error is red
40    'message'        => [ 'My message=status', 'Please restart=warning' ],
41
42                   #pass an arrayref of hashrefs for multiple m2ms or m2names
43                   #be certain you incorporate m2m_Common if you see error: param
44
45    'process_m2m' => { 'link_table'   => 'link_table_name',
46                       'target_table' => 'target_table_name',
47                       #optional (see m2m_Common::process_m2m), if not specified
48                       # all CGI params will be passed)
49                       'params'       => 
50                     },
51    'process_m2name' => { 'link_table'   => 'link_table_name',
52                          'link_static' => { 'column' => 'value' },
53                          'num_col' => 'column', #if column name is different in
54                                                 #link_table than source_table 
55                          'name_col' => 'name_column',
56                          'names_list' => [ 'list', 'names' ],
57                          
58                          'param_style' => 'link_table.value checkboxes',
59                          #or#
60                          'param_style' => 'name_colN values',
61
62
63                        },
64    'process_o2m' => { 'table' => table_name',
65                       'num_col' => 'column', #if column name is different in
66                                              #link_table than source_table 
67                       'fields' => [qw( fieldname fieldname2 )],
68                     },
69
70    'process_locale' => 'fieldname', # update entries in the _msgcat table
71
72    'process_upload' => {
73                          'process'  => 'misc/mytable-import.html',
74                           # fields to pass to the back end job, besides the 
75                           # primary key of the object
76                          'fields'   => [qw( fieldname fieldname2 )],
77                        },
78
79    'skip_process' => 0, #boolean, if set true, will skip the main table
80                         #add/edit processing and only run any linked table
81                         #process_ items
82
83    #checks CGI params and whatever else before much else runs
84    #return an error string or empty for no error
85    'precheck_callback' => sub { my( $cgi ) = @_; },
86
87    #after the new object is created
88    #return an error string or empty for no error
89    'post_new_object_callback' => sub { my( $cgi, $object ) = @_; },
90
91    #run right before replacing (not run for inserts)
92    'edit_callback' => sub { my( $new, $old ) = @_; },
93
94    #after everything's inserted
95    'noerror_callback' => sub { my( $cgi, $object ) = @_; },
96
97    #supplies arguments to insert() and replace()
98    # for use with tables that are FS::option_Common (among other things)
99    'args_callback' => sub { my( $cgi, $object ) = @_; },
100
101    # if no errors after package insert or replace will update services attached to package.
102    'update_svc' => sub { my( $cgi, $object ) = @_; },
103
104    'debug' => 1, #turns on debugging output
105
106    #agent virtualization
107    'agent_virt'       => 1,
108    'agent_null_right' => 'Access Right Name',
109
110    #special bulk insert mode
111    'bulk' => 'field',
112
113  &>
114
115 </%doc>
116 %if ( $error ) {
117 %  ## flatten any array refs so multi selects are sticky on error
118 %  for my $param ($cgi->param) {
119 %    if (ref($cgi->param($param)) eq "ARRAY") {
120 %      $cgi->param($param, (join ",", @{$cgi->param($param)}));
121 %    }
122 %  }
123 %
124 %  #my $query = $m->scomp('/elements/create_uri_query');
125 %  #$cgi->redirect("$url?$query");
126 %  if ( length($cgi->query_string) > 1920 ) { #stupid IE 2083 URL limit
127
128 %    my $session = int(rand(4294967296)); #XXX
129 %    my $pref = new FS::access_user_pref({
130 %      'usernum'    => $FS::CurrentUser::CurrentUser->usernum,
131 %      'prefname'   => "redirect$session",
132 %      'prefvalue'  => $cgi->query_string,
133 %      'expiration' => time + 3600, #1h?  1m?
134 %    });
135 %    my $pref_error = $pref->insert;
136 %    if ( $pref_error ) {
137 %      die "FATAL: couldn't even set redirect cookie: $pref_error".
138 %          " attempting to set redirect$session to ". $cgi->query_string."\n";
139 %    }
140 %
141 <% $cgi->redirect("$error_redirect?redirect=$session") %>
142 %
143 %  } else {
144 %
145 <% $cgi->redirect("$error_redirect?". $cgi->query_string ) %>
146 %
147 %  }
148 %
149 % #different ways of handling success
150 %
151 %} elsif ( $opt{'process_upload'} and @uploaded_files ) {
152 %
153 % # construct a form to pass all the requested fields, the 
154 %
155 <& /elements/header.html &>
156 <form name="UploadForm">
157 %   my $job_fields = $opt{'process_upload'}{'fields'};
158 %   foreach my $field ( @$job_fields ) {
159   <input type="hidden" name="<% $field %>" value="<% $cgi->param($field) |h %>">
160 %   }
161 %   push @$job_fields, 'uploaded_files', $pkey;
162 %
163   <input type="hidden" name="uploaded_files" value="<% join(',', @uploaded_files) %>">
164   <input type="hidden" name="<% $pkey %>" value="<% $new->get($pkey) %>">
165 <& /elements/progress-init.html,
166   'UploadForm',
167   $job_fields,
168   $fsurl . $opt{'process_upload'}{'process'},
169   {
170     url => $redirect,
171     error_url => "$error_redirect?$new_pkey"
172   },
173 &>
174 <input type="submit" style="display:none">
175 </form>
176 <script>
177 <&| /elements/onload.js &>
178 process();
179 </&>
180 </script>
181 <& /elements/footer.html &>
182
183 % } elsif ( $opt{'progress_init'} ) {
184 %   # some false laziness with the above
185 %   my ($form_name, $job_fields) = @{ $opt{'progress_init'} };
186 <form name="<% $form_name %>">
187   <input type="hidden" name="<% $pkey %>" value="<% $new->get($pkey) %>">
188 %   foreach my $field (@$job_fields) {
189 %     next if $field eq $pkey;
190   <input type="hidden" name="<% $field %>" value="<% $cgi->param($field) |h %>">
191 %   }
192 <& /elements/progress-init.html,
193   @{ $opt{'progress_init'} }
194 &>
195 <input type="submit" style="display:none">
196 </form>
197 <script>
198 <&| /elements/onload.js &>
199 process();
200 </&>
201 </script>
202 <& /elements/footer.html &>
203
204 % } elsif ( $opt{'popup_reload'} ) {
205
206   <% include('/elements/header-popup.html', $opt{'popup_reload'} ) %>
207
208   <SCRIPT TYPE="text/javascript">
209     topreload();
210   </SCRIPT>
211
212   </BODY>
213   </HTML>
214
215 %} else {
216 %  my $cookie;
217 %  $cookie = CGI::Cookie->new(
218 %                        -name    => 'freeside_status',
219 %                        -value   => $messages,
220 %                        -expires => '+5m',
221 %                      ) if $messages;
222    <% $cgi->redirect( -uri    => $redirect,
223                       -cookie => $cookie,
224       )
225    %>
226 %}
227 <%init>
228
229 my $me = 'process.html:';
230
231 my(%opt) = @_;
232
233 my $curuser = $FS::CurrentUser::CurrentUser;
234
235 my $error = '';
236 if ( $opt{'precheck_callback'} ) {
237   $error = &{ $opt{'precheck_callback'} }( $cgi );
238 }
239
240 #false laziness w/edit.html
241 my $table = $opt{'table'};
242 my $class = "FS::$table";
243 my $pkey = dbdef->table($table)->primary_key; #? $opt{'primary_key'} || 
244 my $fields = $opt{'fields'}
245              #|| [ grep { $_ ne $pkey } dbdef->table($table)->columns ];
246              || [ fields($table) ];
247
248 my $old_pkey = $cgi->param($pkey);
249
250 my $old = '';
251 if ( $old_pkey ) {
252   $old = qsearchs({
253     'table'   => $table,
254     'hashref' => { $pkey => $old_pkey },
255     'extra_sql' => ( $opt{'agent_virt'}
256                        ? ' AND '. $curuser->agentnums_sql(
257                                     'null_right' => $opt{'agent_null_right'}
258                                   )
259                        : ''
260                    ),
261   });
262 }
263
264 my $bfield = $opt{'bulk'};
265
266 my %hash =
267   map { my @entry = ( $_ => scalar($cgi->param($_)) );
268         $opt{'value_callback'} ? ( $_ => &{ $opt{'value_callback'} }( @entry ))
269                                : ( @entry )
270       } grep { $_ ne $bfield } @$fields;
271
272 my @values = ( 1 );
273 if ( $bfield ) {
274   @values = $cgi->param($bfield);
275   #warn join(',', @values);
276 }
277
278 my @uploaded_files;
279
280 my $new;
281 my $new_pkey = '';
282 foreach my $value ( @values ) {
283
284   if ($opt{'skip_process'}) {
285
286     $new = $old;
287     $new_pkey = $old_pkey;
288
289   } else {
290
291     $new = $class->new( \%hash );
292
293     $new->$bfield($value) if $bfield;
294
295     if ($old && exists($opt{'copy_on_empty'})) {
296       foreach my $field (@{$opt{'copy_on_empty'}}) {
297         $new->set($field, $old->get($field))
298           unless scalar($cgi->param($field));
299       }
300     }
301
302     if ( $opt{'post_new_object_callback'} ) {
303       $error ||= &{ $opt{'post_new_object_callback'} }( $cgi, $new );
304     }
305
306     if ( $opt{'agent_virt'} ) {
307
308       if ( ! $new->agentnum
309            && (    ! $opt{'agent_null_right'}
310                 || ! $curuser->access_right($opt{'agent_null_right'})
311               )
312          )
313       {
314
315         $error ||= 'Select an agent';
316
317       } else {
318
319         die "illegal agentnum"
320           unless $curuser->agentnums_href->{$new->agentnum}
321               or $curuser->access_right('View customers of all agents')
322               or $opt{'agent_null_right'}
323                  && ! $new->agentnum
324                  && $curuser->access_right($opt{'agent_null_right'});
325
326       }
327
328     }
329
330     my @args = ();
331     if ( !$error && $opt{'args_callback'} ) {
332       @args = &{ $opt{'args_callback'} }( $cgi, $new );
333     }
334
335     if ( !$error && $opt{'debug'} ) {
336       warn "$me updating record in $table table using $class class\n";
337       warn Dumper(\%hash);
338       warn "with args: \n". Dumper(\@args) if @args;
339     }
340
341     if ( !$error ) {
342       if ( $old_pkey ) {
343
344         &{ $opt{'edit_callback'} }( $new, $old ) if $opt{'edit_callback'};
345
346         $error = $new->replace($old, @args);
347       } else {
348         $error = $new->insert(@args);
349       }
350       $new_pkey = $new->getfield($pkey);
351     }
352
353   } #unless $opt{'skip_process'}
354
355   if ( !$error && $opt{'process_m2m'} ) {
356
357     my @process_m2m = ref($opt{'process_m2m'}) eq 'ARRAY'
358                         ? @{ $opt{'process_m2m'} }
359                         :  ( $opt{'process_m2m'} );
360
361     foreach my $process_m2m (@process_m2m) {
362
363       $process_m2m->{'params'} ||= scalar($cgi->Vars);
364
365       warn "$me processing m2m:\n". Dumper( %$process_m2m )
366         if $opt{'debug'};
367
368       $error ||= $new->process_m2m( %$process_m2m );
369     }
370
371   }
372
373   if ( !$error && $opt{'process_m2name'} ) {
374
375     my @process_m2name = ref($opt{'process_m2name'}) eq 'ARRAY'
376                            ? @{ $opt{'process_m2name'} }
377                            :  ( $opt{'process_m2name'} );
378
379
380     foreach my $process_m2name (@process_m2name) {
381
382       if ( $opt{'debug'} ) {
383         warn "$me processing m2name:\n". Dumper( %{ $process_m2name },
384                                                  'params' => scalar($cgi->Vars),
385                                                );
386       }
387
388       $error = $new->process_m2name( %{ $process_m2name },
389                                      'params' => scalar($cgi->Vars),
390                                    );
391     }
392
393   }
394
395   my @process_o2m;
396   if ( $opt{'process_o2m'} ) {
397     @process_o2m = ref($opt{'process_o2m'}) eq 'ARRAY'
398                         ? @{ $opt{'process_o2m'} }
399                         :  ( $opt{'process_o2m'} );
400   }
401   if ( $opt{'process_locale'} ) {
402     push @process_o2m,
403     {
404       'table'  => $table . '_msgcat',
405       'fields' => [ 'locale', $opt{'process_locale'} ],
406     };
407   }
408
409   if ( !$error ) {
410
411     foreach my $process_o2m (@process_o2m) {
412
413       if ( $opt{'debug'} ) {
414         warn "$me processing o2m:\n". Dumper( %{ $process_o2m },
415                                                  'params' => scalar($cgi->Vars),
416                                                );
417       }
418
419       $error ||= $new->process_o2m( %{ $process_o2m },
420                                      'params' => scalar($cgi->Vars),
421                                    );
422     }
423
424   }
425
426
427   if ( !$error and $opt{'process_upload'} ) {
428     # mostly duplicates misc/file-upload.html
429     $cgi->param('upload_fields') =~ /^([,\w]+)$/
430       or $error = "invalid upload_fields";
431     my $fields = $1;
432
433     my $dir = $FS::UID::cache_dir. "/cache.". $FS::UID::datasrc;
434
435     foreach my $field (split /,/, $fields) {
436       next if $error;
437       my $source_name = $cgi->param($field);
438
439       if ( $opt{'debug'} ) {
440         warn "$me processing upload: $source_name\n";
441       }
442       my $fh = $cgi->upload( $field ); # if no file provided, that's fine
443       next if !$fh;
444
445       my $suffix = '';
446       if ( $source_name =~ /(\.\w+)$/ ) {
447         $suffix = lc($1);
448       }
449
450       my $sh = File::Temp->new( TEMPLATE => 'upload.XXXXXXXX',
451                                 SUFFIX   => $suffix,
452                                 DIR      => $dir,
453                                 UNLINK   => 0,
454                               )
455         or $error ||= "can't open temporary file to store upload: $!\n";
456       next if $error;
457       while(<$fh>) {
458         print $sh $_;
459       }
460       $sh->filename =~ m!.*/([.\w]+)$!;
461       push @uploaded_files, "$field:$1";
462       if ( $opt{'debug'} ) {
463         warn "uploaded as $1\n";
464       }
465     }
466   }
467
468   if ( !$error and $opt{'update_svc'} ) {
469     my @args = ();
470     @args = &{ $opt{'args_callback'} }( $cgi, $new ) if $opt{'args_callback'};
471    $error = &{ $opt{'update_svc'} }( $cgi, $new, @args );
472   }
473
474   if ( $error ) {
475
476     $cgi->param('error', $error);
477     if ( $opt{'clear_on_error'} && scalar(@{$opt{'clear_on_error'}}) ) {
478       foreach my $field (@{$opt{'clear_on_error'}}) {
479         $cgi->param($field, '')
480       }
481     }
482
483   } else {
484
485     if ( $opt{'noerror_callback'} ) {
486       &{ $opt{'noerror_callback'} }( $cgi, $new );
487     }
488
489   }
490
491   last if $error;
492
493 }
494
495 if ($class eq "FS::tower") {
496   foreach my $part_svc_broadband_export ( FS::tower_sector->part_export_svc_broadband ) {
497     if ($part_svc_broadband_export and $part_svc_broadband_export->can('export_tower_sector')) {
498       my $export_tower = $part_svc_broadband_export->export_tower_sector($new);
499       $error = $export_tower->{'error'} if $export_tower;
500     }
501   }
502   $cgi->param('error', $error) if $error;
503 }
504
505 # set up redirect URLs
506
507 my $redirect;
508 if ( !$error ) {
509   $opt{'redirect'} = &{$opt{'redirect'}}($cgi, $new)
510     if ref($opt{'redirect'}) eq 'CODE';
511
512   if ( $opt{'redirect'} ) {
513
514     $redirect = $opt{'redirect'} . $new_pkey;
515
516   } else { 
517
518     my $ext = $opt{'viewall_ext'} || 'html';
519     my $viewall_dir = $opt{'viewall_dir'} || 'search';
520     my $viewall_url = $opt{'viewall_url'} || ($viewall_dir . "/$table.$ext");
521
522     $redirect = popurl(3) . $viewall_url;
523   
524   }
525 }
526
527 my $messages = $opt{'message'} ? $opt{'message'} : '';
528
529 my $edit_ext = $opt{'edit_ext'} || 'html';
530 my $error_redirect = $opt{'error_redirect'}
531                      || popurl(2)."$table.$edit_ext";
532 </%init>