1 package BSE::Edit::Seminar;
3 use base 'BSE::Edit::Product';
5 use BSE::Util::Tags qw(tag_hash tag_hash_mbcs);
6 use BSE::Util::SQL qw(now_sqldatetime);
7 use DevHelp::Date qw(dh_parse_date_sql dh_parse_time_sql);
8 use constant SECT_SEMSESSION_VALIDATION => 'BSE Seminar Session Validation';
9 use DevHelp::HTML qw(escape_html);
10 use BSE::Util::Iterate;
17 $self->SUPER::article_actions(),
18 a_addsemsession => 'req_addsemsession',
19 a_editsemsession => 'req_editsemsession',
20 a_savesemsession => 'req_savesemsession',
21 a_askdelsemsession => 'req_askdelsemsession',
22 a_delsemsession => 'req_delsemsession',
23 a_takesessionrole => 'req_takesessionrole',
24 a_takesessionrolesave => 'req_takesessionrolesave',
29 my ($self, $article, $cgi) = @_;
32 my $t = $cgi->param('_t');
33 if ($t && $t =~ /^\w+$/) {
36 return $self->{cfg}->entry('admin templates', $base,
40 sub generator { "BSE::Generate::Seminar" }
42 sub default_template {
43 my ($self, $article, $cfg, $templates) = @_;
45 my $template = $cfg->entry('seminars', 'template');
47 if $template && grep $_ eq $template, @$templates;
49 return $self->SUPER::default_template($article, $cfg, $templates);
55 return ( 'seminar flags', $self->SUPER::flag_sections );
58 sub type_default_value {
59 my ($self, $req, $col) = @_;
61 my $value = $req->cfg->entry('seminar defaults', $col);
62 defined $value and return $value;
64 return $self->SUPER::type_default_value($req, $col);
68 my ($self, $article, $cgi) = @_;
70 return $self->{cfg}->entry('admin templates', 'add_seminar',
71 'admin/edit_seminar');
75 my ($self, $articles) = @_;
81 my ($self, $acts, $req, $article, $articles, $msg, $errors) = @_;
84 my $mbcs = $cfg->entry('html', 'mbcs', 0);
85 my $tag_hash = $mbcs ? \&tag_hash_mbcs : \&tag_hash;
87 my $it = BSE::Util::Iterate->new;
90 seminar => [ $tag_hash, $article ],
91 $self->SUPER::low_edit_tags($acts, $req, $article, $articles, $msg,
94 ([ \&iter_sessions, $article, $req ], 'session', 'sessions',
95 undef, undef, undef, \$cur_session),
97 ([ \&iter_locations, $article ], 'location', 'locations'),
98 ifSessionRemovable => [ \&tag_ifSessionRemovable, \$cur_session ],
102 sub tag_ifSessionRemovable {
103 my ($rcur_session) = @_;
105 $$rcur_session or return 0;
107 $$rcur_session->{when_at} gt now_sqldatetime();
111 my ($seminar, $req, $args) = @_;
113 my $which = $args || $req->cgi->param('s') || '';
115 $seminar->{id} or return;
117 my @sessions = $seminar->session_info;
119 # synthesize the past entry
120 my $sql_now = now_sqldatetime();
121 for my $session (@sessions) {
122 $session->{past} = $session->{when_at} lt $sql_now ? 1 : 0;
125 if ($which ne 'all') {
126 @sessions = grep !$_->{past}, @sessions;
133 my ($seminar, $req, $args) = @_;
137 require BSE::TB::Locations;
138 my @locations = BSE::TB::Locations->all;
139 unless ($args eq 'all') {
140 @locations = grep !$_->{disabled}, @locations;
147 my ($self, $articles, $article) = @_;
149 return BSE::TB::Seminars->getByPkey($article->{id});
158 my ($self, $req, $article, $col) = @_;
160 my $value = $self->SUPER::default_value($req, $article, $col);
161 defined $value and return $value;
163 exists $defaults{$col} and return $defaults{$col};
168 sub _fill_seminar_data {
169 my ($self, $req, $data, $src) = @_;
171 if (exists $src->{duration}) {
172 $data->{duration} = $src->{duration};
177 my ($self, $req, $data, $articles) = @_;
179 $self->_fill_seminar_data($req, $data, $data);
181 return $self->SUPER::fill_new_data($req, $data, $articles);
185 my ($self, $req, $article, $src) = @_;
187 $self->_fill_seminar_data($req, $article, $src);
189 return $self->SUPER::fill_old_data($req, $article, $src);
192 sub _validate_common {
193 my ($self, $data, $articles, $errors) = @_;
195 my $duration = $data->{duration};
196 if (defined $duration && $duration !~ /^\d+\s*$/) {
197 $errors->{duration} = "Duration invalid";
200 return $self->SUPER::_validate_common($data, $articles, $errors);
205 location_id => { description => "Location",
206 rules=>"required;positiveint" },
207 when_at_date => { description => "Date",
208 rules => "required;futuredate" },
209 when_at_time => { description => "Time",
210 rules => "required;time" },
213 sub req_addsemsession {
214 my ($self, $req, $article, $articles) = @_;
218 my %fields = %session_fields;
220 $req->validate(errors=>\%errors,
222 section=>SECT_SEMSESSION_VALIDATION);
225 unless ($errors{location_id}) {
226 require BSE::TB::Locations;
228 $location_id = $cgi->param('location_id');
229 $location = BSE::TB::Locations->getByPkey($location_id)
230 or $errors{location_id} = "Unknown location";
233 unless (keys %errors) {
234 require BSE::TB::SeminarSessions;
235 my $date = dh_parse_date_sql($cgi->param('when_at_date'));
236 my $time = dh_parse_time_sql($cgi->param('when_at_time'));
237 $when = "$date $time";
239 my ($existing) = BSE::TB::SeminarSessions->getBy(location_id=>$location_id,
242 $errors{location_id} = $errors{when_at_date} =
243 $errors{when_at_time} = "A session is already booked for that date and time at this location";
247 and return $self->edit_form($req, $article, $articles, undef, \%errors);
249 my $session = $article->add_session($when, $location);
251 return $self->refresh($article, $cgi, undef, 'Session added');
255 my ($req, $article, $rmsg) = @_;
258 my $session_id = $cgi->param('session_id');
259 defined $session_id && $session_id =~ /^\d+$/
260 or do { $$rmsg = "Missing or invalid session id"; return; };
261 require BSE::TB::SeminarSessions;
262 my $session = BSE::TB::SeminarSessions->getByPkey($session_id)
263 or do { $$rmsg = "Unknown session $session_id"; return; };
264 $session->{seminar_id} == $article->{id}
265 or do { $$rmsg = "Session does not belong to this seminar"; return };
270 sub req_editsemsession {
271 my ($self, $req, $article, $articles, $errors) = @_;
275 my $session = _get_session($req, $article, \$msg)
276 or return $self->edit_form($req, $article, $articles, $msg);
278 my %fields = %session_fields;
279 my $cfg_fields = $req->configure_fields(\%fields, SECT_SEMSESSION_VALIDATION);
284 $self->low_edit_tags(\%acts, $req, $article, undef, $errors),
285 field => [ \&tag_field, $cfg_fields ],
286 session => [ \&tag_hash, $session ],
289 return $req->dyn_response('admin/semsessionedit.tmpl', \%acts);
292 sub req_savesemsession {
293 my ($self, $req, $article, $articles) = @_;
297 my $session = _get_session($req, $article, \$msg)
298 or return edit_form($req, $article, $articles, $msg);
300 my %fields = %session_fields;
302 $req->validate(errors=>\%errors,
304 section=>SECT_SEMSESSION_VALIDATION);
307 unless ($errors{location_id}) {
308 require BSE::TB::Locations;
310 $location_id = $cgi->param('location_id');
311 $location = BSE::TB::Locations->getByPkey($location_id)
312 or $errors{location_id} = "Unknown location";
315 unless (keys %errors) {
316 require BSE::TB::SeminarSessions;
317 my $date = dh_parse_date_sql($cgi->param('when_at_date'));
318 my $time = dh_parse_time_sql($cgi->param('when_at_time'));
319 $when = "$date $time";
321 my ($existing) = BSE::TB::SeminarSessions->getBy(location_id=>$location_id,
323 if ($existing && $existing->{session_id} != $session->{session_id}) {
324 $errors{location_id} = $errors{when_at_date} =
325 $errors{when_at_time} = "A session is already booked for that date and time at this location";
329 and return $self->edit_form($req, $article, $articles, undef, \%errors);
331 my $old_location_id = $session->{location_id};
332 my $old_when = $session->{when_at};
333 $session->{location_id} = $location_id;
334 $session->{when_at} = $when;
337 my @msgs = 'Seminar session saved';
339 if ($cgi->param('notify')
340 && ($session->{location_id} != $old_location_id
341 || $session->{when_at} ne $old_when)) {
342 my $old_location = BSE::TB::Locations->getByPkey($old_location_id);
343 my @bookings = $session->booked_users();
344 my $notify_sect = 'Session Change Notification';
347 my $mailer = BSE::Mail->new(cfg=>$cfg);
348 my $from = $cfg->entry($notify_sect, 'from',
349 $cfg->entry('shop', 'from', $Constants::SHOP_FROM));
352 for my $user (@bookings) {
356 session => [ \&tag_hash_plain, $session ],
357 seminar => [ \&tag_hash_plain, $article ],
358 old_when => $old_when,
359 old_location => [ \&tag_hash_plain, $old_location ],
360 location => [ \&tag_hash_plain, $location ],
363 if ($mailer->complex_mail(from=>$from,
365 template=>'user/sessionchangenotify',
367 section=>$notify_sect,
368 subject=>'Session Rescheduled')) {
372 push @errors, "Error sending notification to $user->{email}:"
379 $msgs[0] .= " ($sent users notified by email about the change)";
381 # something really wrong, dump them to the error log and trim the list
382 print STDERR $_ for @errors;
385 push @errors, "(more errors omitted - total of $total errors)";
391 $msgs[0] .= ' (No users were booked for this session to be notified)';
395 return $self->refresh($article, $cgi, undef, \@msgs);
398 sub iter_other_sessions {
399 my ($seminar, $session) = @_;
401 grep $_->{id} != $session->{id}, $seminar->future_sessions;
404 sub tag_other_location {
405 my ($rcur_session, $arg) = @_;
407 $$rcur_session or return '';
408 my $location = $$rcur_session->location;
410 my $value = $location->{$arg};
411 defined $value or return '';
416 sub req_askdelsemsession {
417 my ($self, $req, $article, $articles, $errors) = @_;
421 my $session = _get_session($req, $article, \$msg)
422 or return $self->edit_form($req, $article, $articles, $msg);
424 my %fields = %session_fields;
425 my $cfg_fields = $req->configure_fields(\%fields, SECT_SEMSESSION_VALIDATION);
426 my $location = $session->location;
428 my $it = BSE::Util::Iterate->new;
433 $self->low_edit_tags(\%acts, $req, $article, $articles, undef, $errors),
434 field => [ \&tag_field, $cfg_fields ],
435 session => [ \&tag_hash, $session ],
436 location => [ \&tag_hash, $location ],
438 ([ \&iter_other_sessions, $article, $session ],
439 'other_session', 'other_sessions', undef, undef, undef, \$cur_session),
440 other_location => [ \&tag_other_location, \$cur_session ],
443 return $req->dyn_response('admin/semsessiondel', \%acts);
446 sub req_delsemsession {
447 my ($self, $req, $article, $articles) = @_;
451 my $session = _get_session($req, $article, \$msg)
452 or return $self->edit_form($req, $article, $articles, $msg);
456 # which session are bookings moving to
457 my $other_session_id = $cgi->param('othersession_id');
459 if ($other_session_id) {
460 if ($other_session_id != -1) {
461 $other_session = BSE::TB::SeminarSession->getByPkey($other_session_id);
463 || $other_session->{seminar_id} != $article->{id}
464 || $other_session->{id} == $session->{id}) {
465 $errors{othersession_id} = "Invalid alternate section selected";
470 $errors{othersession_id} = "Please select cancel or the session to move bookings to";
474 and return $self->req_askdelsemsession($req, $article, $articles, \%errors);
476 my %session = %$session;
478 my @msgs = 'Seminar session deleted';
480 if ($cgi->param('notify')) {
481 my $location = $session->location;
482 my @bookings = $session->booked_users();
483 my $notify_sect = 'Session Change Notification';
486 my $mailer = BSE::Mail->new(cfg=>$cfg);
487 my $from = $cfg->entry($notify_sect, 'from',
488 $cfg->entry('shop', 'from', $Constants::SHOP_FROM));
491 for my $user (@bookings) {
495 session => [ \&tag_hash_plain, $session ],
496 seminar => [ \&tag_hash_plain, $article ],
497 location => [ \&tag_hash_plain, $location ],
498 ifCancelled => $other_session_id == -1,
501 if ($other_session) {
502 $subject = "Session Merged";
503 $acts{new_session} = [ \&tag_hash_plain, $other_session ];
504 $acts{new_location} = [ \&tag_hash_plain, $other_session->location ],
507 $subject = "Session Cancelled";
510 if ($mailer->complex_mail(from=>$from,
512 template=>'user/sessiondeletenotify',
514 section=>$notify_sect,
515 subject=>$subject)) {
519 push @errors, "Error sending notification to $user->{email}:"
526 $msgs[0] .= " ($sent users notified by email about the change)";
528 # something really wrong, dump them to the error log and trim the list
529 print STDERR $_ for @errors;
532 push @errors, "(more errors omitted - total of $total errors)";
538 $msgs[0] .= ' (No users were booked for this session to be notified)';
542 if ($other_session) {
543 $session->replace_with($other_session_id);
549 return $self->refresh($article, $cgi, undef, \@msgs);
552 sub req_takesessionrole {
553 my ($self, $req, $article, $articles, $errors) = @_;
557 my $session = _get_session($req, $article, \$msg)
558 or return $self->edit_form($req, $article, $articles, $msg);
560 my @roll_call = $session->roll_call_entries;
562 my $it = BSE::Util::Iterate->new;
565 $self->low_edit_tags(\%acts, $req, $article, $articles, undef, $errors),
566 $it->make_iterator(undef, 'rolluser', 'rollusers', \@roll_call),
567 session=>[ \&tag_hash, $session ],
570 return $req->dyn_response('admin/semsessionrollcall', \%acts);
573 sub req_takesessionrolesave {
574 my ($self, $req, $article, $articles) = @_;
578 my $session = _get_session($req, $article, \$msg)
579 or return $self->edit_form($req, $article, $articles, $msg);
581 my @roll_call = $session->roll_call_entries;
583 for my $userid (map $_->{id}, @roll_call) {
584 my $there = $cgi->param("roll_present_$userid");
585 $session->set_roll_present($userid, $there);
587 $session->{roll_taken} = 1;
590 return $self->refresh($article, $cgi, undef, "Roll saved");
593 sub base_template_dirs {
594 return ( "seminars" );
597 sub extra_templates {
598 my ($self, $article) = @_;
602 my $extras = $self->{cfg}->entry('seminars', 'extra_templates');
603 push @extras, grep /\.(tmpl|html)$/i, split /,/, $extras